4

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>
Pouya Jabbarisani
  • 1,084
  • 3
  • 16
  • 29
  • How are elements selected? – s.kuznetsov Jan 15 '21 at 13:47
  • @s.kuznetsov actually, I'm building this as a complex React component, but other things are straight forward, the only problem I have is the explained one above. – Pouya Jabbarisani Jan 15 '21 at 13:48
  • 1
    You may be better off just handling events on the "outside" layer and adding `event.preventDefault()` to that handler. Or, on the "inside" layer handler, add `event.stopPropagation()`. – Scott Marcus Jan 15 '21 at 13:49
  • look it - https://openbase.com/categories/js/best-react-onboarding-tour-libraries?orderBy=RECOMMENDED& – s.kuznetsov Jan 15 '21 at 14:03

2 Answers2

1

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>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • This is one possible solution specified for the explained situation, so I'm giving a + for your answer BUT it's not the answer I'm looking for because since I'm going to use transition animation and multi-steps (which some of them will have square focus and some of them will have circle focus) I need to implement it with BORDER-RADIUS. – Pouya Jabbarisani Jan 15 '21 at 14:38
  • @PouyaJabbarisani add such detail to your question because it's important to avoid us wasting time giving answers ... you said *circle* in your question so ... – Temani Afif Jan 15 '21 at 14:39
  • Yes, you're right, I edited my question just now, but it's ok, your answer can help someone else with their searches. ;) – Pouya Jabbarisani Jan 15 '21 at 14:43
1

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>
  • It's a good idea to change the z-index for having the item top of the overlaying layout so you will have my +1, but consider it in a more general way, I mean consider different divs with different background colors and z-indexes on a complex layout. if we can find a way to somehow implement it without changing the properties of items it would be great. – Pouya Jabbarisani Jan 15 '21 at 16:25
  • I'm still thinking about your solution :) if there wasn't a background problem, it's actually the best possible way so far... – Pouya Jabbarisani Jan 15 '21 at 17:48