如何检测鼠标指针事件是否在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,<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,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"/></svg>'); } .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
相关产品推荐
相关产品推荐

