<main>
  <header>
    <h1>Custom Cursor Pop-up</h1>
  </header>
  <article>
    <p>
      Here, a <code>popup</code> displays a custom cursor. Using the top layer, we can put the cursor above everything else. If we need to display another pop-up, we can listen for the event and hide and reshow the cursor popup. Try showing a pop-up above it.
    </p>
    <div>
      <button class="button ripple" popovertoggletarget="overlay-pop-up">Show Pop-up</button>
    </div>
  </article>
</main>
<div id="overlay-pop-up" popover>
  <div class="card elevated">
    You can still see the cursor above me and my <code>::backdrop</code>!
  </div>
</div>
<canvas id="custom-cursor" class="custom-cursor" popover="manual"></canvas>
@layer demo {
  #overlay-pop-up {
    border: 0;
    background: none;
    max-width: calc(100% - (2 * var(--size-4)));
    min-width: 0;
  }

  #overlay-pop-up::backdrop {
    background: hsl(0 0% 0% / 0.25);
    backdrop-filter: blur(4px);
  }

  #overlay-pop-up .card {
    padding: var(--size-4);
    display: block;
    min-width: 0;
    line-height: 1.5;
    background: var(--surface-1);
    color: var(--text-1);
  }
}

@layer base {
  *,
  *:after,
  *:before {
    box-sizing: border-box;
    /*cursor: none;*/
  }

  body {
    min-height: 100vh;
    background: var(--surface-1);
    display: block;
    font-family: "Google Sans", sans-serif, system-ui;
  }

  :where([popover]) {
    margin: auto;
    border-width: 0;
    border-style: none;
    background: transparent;
  }

  canvas {
    height: 100vh;
    width: 100vw;
    pointer-events: none;
    display: none;
    position: fixed;
  }

  canvas:open {
    display: block;
  }

  h1 {
    margin: 0;
    color: var(--gray-0);
  }

  .button {
    /*cursor: none;*/
  }

  header {
    height: 25vmin;
    min-height: 200px;
    background: var(--gradient-23);
    display: grid;
    place-items: center;
    color: var(--gray-0);
    padding: var(--size-4);
    grid-template-columns: 1fr;
  }

  main {
    margin: 0 auto;
  }

  article {
    padding: var(--size-4);
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  article > * + * {
    margin-top: var(--size-4);
  }
}
const mapRange = (inputLower, inputUpper, outputLower, outputUpper) => {
  const INPUT_RANGE = inputUpper - inputLower;
  const OUTPUT_RANGE = outputUpper - outputLower;
  return (value) =>
    outputLower + (((value - inputLower) / INPUT_RANGE) * OUTPUT_RANGE || 0);
};

const clamp = (min, max, value) => Math.min(Math.max(value, min), max);

let canvas;
let context;
const blocks = [];

const createBlock = ({ x, y, movementX, movementY }) => {
  const inactiveBlocks = blocks.filter((block) => block.active === false);
  const size = Math.floor(
    mapRange(0, 100, 10, 50)(Math.max(Math.abs(movementX), Math.abs(movementY)))
  );
  if (inactiveBlocks.length !== 0) {
    // Reuse a block
    inactiveBlocks[0].active = true;
    inactiveBlocks[0].size = size;
    inactiveBlocks[0].x = x - size * 0.5;
    inactiveBlocks[0].y = y - size * 0.5;
    inactiveBlocks[0].hue = Math.floor(Math.random() * 359);
  } else {
    const block = {
      active: true,
      hue: Math.floor(Math.random() * 359),
      x: x - size * 0.5,
      y: y - size * 0.5,
      size
    };
    blocks.push(block);
  }
};

const drawBlocks = () => {
  context.clearRect(0, 0, canvas.width, canvas.height);
  for (const block of blocks.filter((block) => block.active)) {
    context.strokeStyle = context.fillStyle = `hsl(${block.hue}, 80%, 80%)`;
    context.beginPath();
    context.arc(block.x, block.y, block.size * 0.5, 0, 2 * Math.PI);
    context.stroke();
    context.fill();

    block.size -= 1;
    if (block.size === 0) {
      block.active = false;
    }
  }
  requestAnimationFrame(drawBlocks);
};

const setUp = () => {
  canvas = document.querySelector("canvas");
  context = canvas.getContext("2d");
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  document.body.addEventListener("pointermove", createBlock);
  // Listen for any pop-ups that are opened and make sure the canvas sits higher
  document.body.addEventListener("beforetoggle", (e) => {
    if (e.newState === "open" && e.target !== canvas) {
      requestAnimationFrame(() => {
        canvas.hidePopover();
        canvas.showPopover();
      });
    }
  });
  requestAnimationFrame(drawBlocks);
};

setUp();
window.addEventListener("resize", () => {
  if (canvas) {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
  }
});

canvas.showPopover();

External CSS

  1. https://codepen.io/web-dot-dev/pen/XWqWYgB.css
  2. https://codepen.io/web-dot-dev/pen/ZExZWBQ.css

External JavaScript

  1. https://codepen.io/web-dot-dev/pen/XWqWYgB.js
  2. https://codepen.io/web-dot-dev/pen/ZExZWBQ.js