<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 })
);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.19/lodash.min.js