<figure class="plane">
<img class="image" src="https://images.unsplash.com/photo-1492370284958-c20b15c692d2?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=387&q=80" alt="">
<svg class="svg-overlay" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 387 501">
<defs>
<mask id="holes" class="holes-mask">
<rect fill="#fff" width="100%" height="100%" />
<path class="polygon mustache-polygon" d="M216,146c11-8,60.5,17.5,68.5,27.5S245,197,236,192c-7-3.89-24-36.84-24-40C212,149,216,146,216,146Zm-12,0c-5.25-4.74-47.61,14.87-50,28-2,11,8,22,16,22,7.28,0,33.34-40.5,35-43A5.31,5.31,0,0,0,204,146Z" />
<path class="polygon paws-polygon" d="M185.5,234.5c-4,29,16,121,16,140s-4,21-8,30,0,20,7,21,25,0,25,0,11,6,19,6,15,1,16-6-4-23,0-41,23-73,25-81,0-25,1-31,6-26,7-29S188.11,215.57,185.5,234.5Z" />
<path class="polygon tail-polygon" d="M166.5,327.5c-7.82-2-63.41-2.08-68,22-4,21,15.14,22.63,22,24,10,2,16-1,17-5,1.71-6.86-13-12-4-15,9.53-3.18,32,2,35-1S170.5,328.5,166.5,327.5Z" />
</mask>
</defs>
<rect class="shade" width="100%" height="100%" fill="#000" fill-opacity="0.4" mask="url(#holes)" />
<g class="shapes">
<path class="polygon mustache-polygon" d="M216,146c11-8,60.5,17.5,68.5,27.5S245,197,236,192c-7-3.89-24-36.84-24-40C212,149,216,146,216,146Zm-12,0c-5.25-4.74-47.61,14.87-50,28-2,11,8,22,16,22,7.28,0,33.34-40.5,35-43A5.31,5.31,0,0,0,204,146Z" />
<path class="polygon paws-polygon" d="M185.5,234.5c-4,29,16,121,16,140s-4,21-8,30,0,20,7,21,25,0,25,0,11,6,19,6,15,1,16-6-4-23,0-41,23-73,25-81,0-25,1-31,6-26,7-29S188.11,215.57,185.5,234.5Z" />
<path class="polygon tail-polygon" d="M166.5,327.5c-7.82-2-63.41-2.08-68,22-4,21,15.14,22.63,22,24,10,2,16-1,17-5,1.71-6.86-13-12-4-15,9.53-3.18,32,2,35-1S170.5,328.5,166.5,327.5Z" />
</g>
</svg>
</figure>
<div class="tooltips">
<div class="tooltip mustache-tooltip">Mustache</div>
<div class="tooltip paws-tooltip">Paws</div>
<div class="tooltip tail-tooltip">Tail</div>
</div>
body {
margin: 0;
font-family: sans-serif;
}
.plane {
position: relative;
}
.image {
display: block;
width: 100%;
}
.svg-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.shapes .polygon {
fill: none;
stroke: #fff;
stroke-width: 2px;
stroke-opacity: 0.35;
vector-effect: non-scaling-stroke;
transition: stroke-opacity 0.5s;
pointer-events: all;
}
.shapes .polygon-show {
stroke-opacity: 1;
}
.tooltip {
position: fixed;
top: 0;
left: 0;
background-color: #f44336;
color: #fff;
padding: 5px 10px;
border-radius: 9999px;
pointer-events: none;
opacity: 0;
visibility: hidden;
will-change: transform, opacity;
transition: opacity 0.5s;
}
.tooltip-show {
visibility: visible;
opacity: 1;
}
.holes-mask .polygon {
fill: #333;
transition: fill 0.5s;
}
.holes-mask .mask-show {
fill: #000;
}
.shade {
transition: fill-opacity 0.5s;
}
const plane = document.querySelector(".plane");
const tooltips = document.querySelector(".tooltips");
const shade = plane.querySelector(".shade");
const parts = ["mustache", "paws", "tail"].reduce((acc, name) => {
const polygon = plane.querySelector(`.shapes .${name}-polygon`);
const tooltip = tooltips.querySelector(`.tooltips .${name}-tooltip`);
const mask = plane.querySelector(`.holes-mask .${name}-polygon`);
acc.set(polygon, { tooltip, mask });
return acc;
}, new Map());
const state = {
activeTooltip: null,
activePolygon: null,
activeMask: null,
clientX: 0,
clientY: 0
};
window.addEventListener("mousemove", (e) => {
if (state.activeTooltip && state.activePolygon && state.activeMask) {
state.activeTooltip.classList.remove("tooltip-show");
state.activePolygon.classList.remove("polygon-show");
state.activeMask.classList.remove("mask-show");
shade.style.fillOpacity = 0.4;
}
let polygon;
if (e.detail) {
polygon = e.detail.target;
} else {
polygon = e.target;
state.clientX = e.clientX;
state.clientY = e.clientY;
}
const part = parts.get(polygon);
if (!part) {
state.activeTooltip = null;
state.activePolygon = null;
state.activeMask = null;
return;
}
const { tooltip, mask } = part;
polygon.classList.add("polygon-show");
mask.classList.add("mask-show");
tooltip.classList.add("tooltip-show");
tooltip.style.transform = `translate(${state.clientX + 10}px, ${state.clientY}px)`;
shade.style.fillOpacity = 0.8;
state.activeTooltip = tooltip;
state.activePolygon = polygon;
state.activeMask = mask;
});
function forceMouseMoveOnScroll(e) {
const target = document.elementFromPoint(state.clientX, state.clientY);
const event = new CustomEvent("mousemove", { detail: { target } });
window.dispatchEvent(event);
}
window.addEventListener(
"scroll",
_.debounce(forceMouseMoveOnScroll, 200, { leading: true })
);
This Pen doesn't use any external CSS resources.