You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何检测鼠标指针事件是否在SVG遮罩(mask)范围内?

实现SVG遮罩拼图块的精准鼠标悬停高亮

拼图效果示意图

我需要实现鼠标悬停时高亮拼图块的效果。每个拼图是一个div元素,通过SVG进行遮罩(mask)处理。目前只能检测事件是否在遮罩的边界矩形内,无法判断鼠标是否在复杂SVG形状的内部,求解决方法。

原实现代码:

<style>
  .tt-puzzle-container {
    position: relative;
    display: -webkit-box;
    display: -webkit-flex;
    display: -ms-flexbox;
    display: flex;
    width: 500px;
    height: 500px;
    -webkit-box-orient: vertical;
    -webkit-box-direction: normal;
    -webkit-flex-direction: column;
    -ms-flex-direction: column;
    flex-direction: column;
    -webkit-box-pack: end;
    -webkit-justify-content: flex-end;
    -ms-flex-pack: end;
    justify-content: flex-end;
    pointer-events: auto; /* added */
  }

  .tt-puzzle-piece {
    position: absolute;
    width: 100%;
    height: 50%;
    padding-right: 0px;
    padding-bottom: 0px;
    background-color: #ff0606;
    -webkit-transform-origin: 50% 0%;
    -ms-transform-origin: 50% 0%;
    transform-origin: 50% 0%;
    -webkit-transition: background-color 200ms ease;
    transition: background-color 200ms ease;
  }

  .tt-puzzle-piece:hover {
    background-color: #50ff06;
  }

  .tt-puzzle-piece._2 {
    z-index: 10;
    -webkit-transform: rotate(72deg);
    -ms-transform: rotate(72deg);
    transform: rotate(72deg);
    mask: var(--mask-url);
  }

  .tt-puzzle-piece._3 {
    z-index: 20;
    -webkit-transform: rotate(144deg);
    -ms-transform: rotate(144deg);
    transform: rotate(144deg);
    mask: var(--mask-url);
  }

  .tt-puzzle-piece._4 {
    z-index: 30;
    -webkit-transform: rotate(216deg);
    -ms-transform: rotate(216deg);
    transform: rotate(216deg);
    mask: var(--mask-url);
  }

  .tt-puzzle-piece._5 {
    z-index: 40;
    -webkit-transform: rotate(288deg);
    -ms-transform: rotate(288deg);
    transform: rotate(288deg);
    mask: var(--mask-url);
  }
</style>
<body>
  <div class="tt-puzzle-container">
    <div class="w-embed">
      <style>
        :root {
          --mask-url: url('data:image/svg+xml;utf8,&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 238.5 209.5&quot;&gt;&lt;path d=&quot;M0.3,170.6c-0.5,0.7-0.3,1.6,0.4,2.1c14.8,10.1,30.8,18.3,47.5,24.3c0,0,0.1,0,0.1,0.1c1.1,0.4,2.1,0.8,3.2,1.1c0.3,0.1,0.6,0.2,0.9,0.3c0.9,0.3,1.8,0.6,2.7,0.9c0.6,0.2,1.1,0.4,1.7,0.5c0.7,0.2,1.3,0.4,2,0.6c1.3,0.4,2.6,0.8,3.8,1.1c1.4,0.4,2.8,0.8,4.2,1.1c0.5,0.1,1,0.2,1.5,0.4c0.5,0.1,1.1,0.3,1.6,0.4c0.5,0.1,1.1,0.3,1.6,0.4c1.6,0.4,3.2,0.7,4.8,1.1c0.5,0.1,1.1,0.2,1.6,0.3c1.1,0.2,2.2,0.4,3.2,0.6c0.5,0.1,1.1,0.2,1.6,0.3c1.1,0.2,2.2,0.4,3.3,0.6c0.6,0.1,1.1,0.2,1.6,0.3c1.6,0.2,3.2,0.5,4.8,0.7c0.6,0.1,1.2,0.2,1.8,0.2c0.4,0.1,0.8,0.1,1.3,0.1c0.4,0.1,0.9,0.1,1.3,0.1c0.7,0.1,1.3,0.1,1.9,0.2c0.2,0,0.4,0,0.6,0.1c30,2.8,60-0.9,87.9-10.5c12.1-4.1,23.7-9.4,34.9-15.7c3.5-2,6.9-4,10.3-6.2c1.8-1.1,3.5-2.3,5.3-3.5c0.7-0.5,0.9-1.4,0.4-2.1l-25.1-34.6c-0.8-1.1-2.5-1.1-3.2,0.1c-1.2,2-2.8,3.8-4.8,5.2c-0.8,0.5-1.6,1-2.4,1.5c-7.6,3.9-16.9,1.8-22.1-4.9c-3-3.8-4.2-8.6-3.6-13.4c0.7-4.8,3.2-9,7.1-11.9c2-1.4,4.1-2.4,6.4-2.9c1.3-0.3,1.9-1.9,1.1-3l-29.4-40.4c-0.5-0.6-1.3-0.8-2-0.4c0,0,0,0,0,0c-0.5,0.3-0.9,0.6-1.4,0.9c-0.4,0.2-0.8,0.4-1.1,0.7c-0.8,0.5-1.6,0.9-2.4,1.3c-0.3,0.1-0.5,0.3-0.8,0.4c-9.7,5-20.4,7.8-31.2,8.4c-0.3,0-0.6,0-0.9,0.1c-1,0-2,0.1-2.9,0.1c-0.4,0-0.9,0-1.3,0c-13.7-0.2-27.4-3.9-39.7-11.5c0,0-0.1,0-0.1,0c-0.1-0.1-0.3-0.2-0.4-0.2c-0.8,0.2-1.2,0.2-2,0.4l-34.5,47.5c-1.5,2.1-4.2,2.8-6.5,2c-0.5-0.2-1-0.4-1.4-0.8c-1.6-1.2-2.5-3.4-2.3-5.9c0-0.2,0-0.4-0.1-0.7c-3.4-9.2-14.2-9.1-18-2.1c-1.9,3.4-1.4,7.7,1.1,10.7c2.5,2.9,6.2,4.1,9.8,3.2c1.9-0.5,3.9-0.2,5.2,0.8c0.5,0.3,0.8,0.7,1.2,1.1c1.5,1.9,1.6,4.7,0.1,6.8L0.3,170.6z&quot;/&gt;&lt;/svg&gt;');
        }
        .tt-puzzle-piece {
          -webkit-mask: var(--mask-url) center/contain no-repeat;
          mask: var(--mask-url) center/contain no-repeat;
        }
      </style>
    </div>
    <div class="tt-puzzle-piece"></div>
    <div class="tt-puzzle-piece _2"></div>
    <div class="tt-puzzle-piece _3"></div>
    <div class="tt-puzzle-piece _4"></div>
    <div class="tt-puzzle-piece _5"></div>
  </div>
</body>

<script>
  const puzzleContainer = document.querySelector(".tt-puzzle-container");
  const puzzlePieces = document.querySelectorAll(".tt-puzzle-piece");

  function isEventWithinPuzzlePiece(event) {
    // Get the bounding rect of the puzzle container
    const puzzleContainerRect = puzzleContainer.getBoundingClientRect();

    // Check if the event occurred within the puzzle container rect
    return (
      event.clientX >= puzzleContainerRect.left &&
      event.clientX <= puzzleContainerRect.right &&
      event.clientY >= puzzleContainerRect.top &&
      event.clientY <= puzzleContainerRect.bottom
    );
  }

  function isEventWithinMask(event, puzzlePiece) {
    // Get the bounding rect of the mask
    const maskRect = puzzlePiece.getBoundingClientRect();

    // Check if the event occurred within the mask rect
    return (
      event.clientX >= maskRect.left &&
      event.clientX <= maskRect.right &&
      event.clientY >= maskRect.top &&
      event.clientY <= maskRect.bottom
    );
  }

  puzzleContainer.addEventListener("click", (event) => {
    // Check if the event occurred within a puzzle piece
    if (!isEventWithinPuzzlePiece(event)) {
      return;
    }

    // Get the element under the mouse
    const elementUnderMouse = document.elementFromPoint(
      event.clientX,
      event.clientY
    );
    if (!elementUnderMouse) {
      return;
    }

    // Check if the element under the mouse is a puzzle piece
    const puzzlePiece = elementUnderMouse.closest(".tt-puzzle-piece");
    if (!puzzlePiece) {
      return;
    }

    // Check if the event occurred within the mask of the puzzle piece
    if (isEventWithinMask(event, puzzlePiece)) {
      // Prevent the event from being handled
      event.stopPropagation();
    }
  });
</script>

解决方案:利用SVG路径的原生事件检测

核心思路是给每个拼图块匹配一个隐藏的SVG路径,借助SVG原生的形状命中检测能力,实现精准的悬停判断:

修改后的完整代码

<style>
  .tt-puzzle-container {
    position: relative;
    display: flex;
    width: 500px;
    height: 500px;
    flex-direction: column;
    justify-content: flex-end;
    pointer-events: auto;
  }

  .tt-puzzle-piece {
    position: absolute;
    width: 100%;
    height: 50%;
    background-color: #ff0606;
    transform-origin: 50% 0%;
    transition: background-color 200ms ease;
    -webkit-mask: var(--mask-url) center/contain no-repeat;
    mask: var(--mask-url) center/contain no-repeat;
  }

  .tt-puzzle-piece:hover {
    background-color: #50ff06;
  }

  .tt-puzzle-piece._2 {
    z-index: 10;
    transform: rotate(72deg);
  }

  .tt-puzzle-piece._3 {
    z-index: 20;
    transform: rotate(144deg);
  }

  .tt-puzzle-piece._4 {
    z-index: 30;
    transform: rotate(216deg);
  }

  .tt-puzzle-piece._5 {
    z-index: 40;
    transform: rotate(288deg);
  }

  /* 隐藏的命中检测SVG层 */
  .hitbox-svg {
    position: absolute;
    width: 100%;
    height: 100%;
    pointer-events: none;
  }

  .hitbox-path {
    fill: transparent;
    pointer-events: fill;
  }
</style>
<body>
  <div class="tt-puzzle-container">
    <style>
      :root {
        --mask-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 238.5 209.5"><path d="M0.3,170.6c-0.5,0.7-0.3,1.6,0.4,2.1c14.8,10.1,30.8,18.3,47.5,24.3c0,0,0.1,0,0.1,0.1c1.1,0.4,2.1,0.8,3.2,1.1c0.3,0.1,0.6,0.2,0.9,0.3c0.9,0.3,1.8,0.6,2.7,0.9c0.6,0.2,1.1,0.4,1.7,0.5c0.7,0.2,1.3,0.4,2,0.6c1.3,0.4,2.6,0.8,3.8,1.1c1.4,0.4,2.8,0.8,4.2,1.1c0.5,0.1,1,0.2,1.5,0.4c0.5,0.1,1.1,0.3,1.6,0.4c0.5,0.1,1.1,0.3,1.6,0.4c1.6,0.4,3.2,0.7,4.8,1.1c0.5,0.1,1.1,0.2,1.6,0.3c1.1,0.2,2.2,0.4,3.2,0.6c0.5,0.1,1.1,0.2,1.6,0.3c1.1,0.2,2.2,0.4,3.3,0.6c0.6,0.1,1.1,0.2,1.6,0.3c1.6,0.2,3.2,0.5,4.8,0.7c0.6,0.1,1.2,0.2,1.8,0.2c0.4,0.1,0.8,0.1,1.3,0.1c0.4,0.1,0.9,0.1,1.3,0.1c0.7,0.1,1.3,0.1,1.9,0.2c0.2,0,0.4,0,0.6,0.1c30,2.8,60-0.9,87.9-10.5c12.1-4.1,23.7-9.4,34.9-15.7c3.5-2
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 13:41:23