pointer-events: none for inside of div and pointer-events: all for outside (reverse mode of normal div) - walkthrough tour container

Viewed 686

I am making a walkthrough tour for a panel, for this walkthrough guide, I need to create a hole on an overlying layer, so the underlying elements will be only clickable from that hole.

I found a way to make it somehow for inside but I also need to prevent from targeting underlying elements outside of that circle.

NOTE: The example below is a simplified version of the React component I'm making, I need to use border-radius for the focus area because the focus area will be a dynamic area and could change from circle to square (and with transition) according to the target element on the next step of the guide.

Any solution based on CSS or JavaScript would be very helpful. (but please don't use jQuery!)

Thank you in advance.

.content{
  width: 100%;
  height: 100%;
  padding: 3rem;
  box-sizing: border-box;
}
button{
  padding: 1rem;
  margin: 1rem;
}

.guide{
  width: 9rem;
  height: 9rem;
  position: absolute;
  z-index: 10;
  top: 1.7rem;
  left: 11.5rem;
  border-radius: 9rem;
  box-shadow: 0 0 0 1000rem rgba(0,0,0,0.5);
  pointer-events: none;
}
<div class="content">
  <button>test button</button>
  <button>test button</button>
  <button>test button</button>
  <p>The user should only <br/>able to click on second button</p>
</div>

<div class="guide"></div>

2 Answers

An idea using clip-path. You create a big element with a circle in the middle and then you translate it where you want:

let precision = 64;
let radius = 1.7; /* control the radius here */
let c = [...Array(precision)].map((_, i) => {
  let a = -i/(precision-1)*Math.PI*2;
  let x = Math.cos(a)*radius + 50;
  let y = Math.sin(a)*radius + 50;
  return `${x}% ${y}%`
})

document.querySelector('.guide').style.clipPath = 
 `polygon(100% 50%, 100% 100%, 0 100%, 0 0, 100% 0, 100% 50%, ${c.join(',')})`;

/* credit to https://stackoverflow.com/a/63739677/8620333 for the code above */
.content{
  padding: 3rem;
  box-sizing: border-box;
}
button{
  padding: 1rem;
  margin: 1rem;
}

.guide{
  width: 4000px;
  height: 4000px;
  position: fixed;
  top:-2000px;
  left:-2000px;
  transform:translate(16rem,5.8rem); /* adjust this*/
  background:rgba(0,0,0,0.5);
}
<div class="content">
  <button>test button</button>
  <button>test button</button>
  <button>test button</button>
  <p>The user should only <br/>able to click on second button</p>
</div>

<div class="guide"></div>

Try this css code. Maybe it could help.

.content{
  width: 100%;
  height: 100%;
  padding: 3rem;
  box-sizing: border-box;
}
button{
  padding: 1rem;
  margin: 1rem;
  position:relative;
  z-index:1;
}
button:nth-child(2){
  z-index:3;
}

.guide{
 width:100%;
  height:100%;
  position:absolute;
  top:0;
  left:0;
/*   box-shadow: 0 0 0 1000rem rgba(0,0,0,0.5); 
  pointer-events: none;*/
  background: rgba(.5,.5,.5,.5);
    z-index: 2;
}
.guide:after{
   width: 9rem;
  height: 9rem;
  position: absolute;
  z-index: 10;
  top: 1.7rem;
  left: 11.5rem;
    border-radius: 50%;
  content:''; 
  background: white;

}
<div class="content">
  <button>test button</button>
  <button>test button</button>
  <button>test button</button>
  <p>The user should only <br/>able to click on second button</p>
</div>

<div class="guide"></div>

Related