You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

请求帮助:实现围绕鼠标指针的SVG元素缩放平移功能

实现SVG围绕鼠标指针的缩放效果

我来帮你推导这个数学逻辑并修改代码。核心思路是:缩放时,先把鼠标的屏幕位置转换为当前viewBox下的图形坐标,然后计算缩放后需要调整的viewBox偏移量,让这个图形坐标始终对应鼠标的屏幕位置。

关键数学推导

假设:

  • SVG的显示尺寸是固定的400x400
  • 当前viewBox的宽度/高度为 viewBoxZ,偏移量为 -viewBoxX-viewBoxY(对应viewBox的x和y属性)
  • 鼠标在SVG元素上的相对屏幕坐标为 (sx, sy)(即 event.clientX - rect.leftevent.clientY - rect.top
  1. 将屏幕坐标转换为当前viewBox的图形坐标
    屏幕上的1像素对应viewBox中的 viewBoxZ / 400 个单位,所以图形坐标为:

    gx = -viewBoxX + (sx / 400) * viewBoxZ
    gy = -viewBoxY + (sy / 400) * viewBoxZ
    
  2. 计算缩放后的viewBox偏移量
    缩放后viewBox的新尺寸为 newViewBoxZ,我们需要新的偏移量让图形坐标 (gx, gy) 仍然对应屏幕坐标 (sx, sy),推导得:

    newViewBoxX = viewBoxX + (sx / 400) * (newViewBoxZ - viewBoxZ)
    newViewBoxY = viewBoxY + (sy / 400) * (newViewBoxZ - viewBoxZ)
    

修改后的完整代码

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>ZoomPanView</title>
    <style>
        svg { background-color: silver; }
    </style>
    <script>
        const MOUSE_EVENT_PRIMARY_PHASE = 2;
        let viewBoxX = 0;
        let viewBoxY = 0;
        let viewBoxZ = 400;
        const viewBoxMinZ = 400;
        const viewBoxMaxZ = 1200;
        const VIEWBOX_INCREMENTS = viewBoxZ / 10;
        let isDragging = false;
        let dragOffsetX, dragOffsetY;

        function mouseDown() {
            if(event.eventPhase != MOUSE_EVENT_PRIMARY_PHASE) return;
            if(event.currentTarget.id != "view") return;
            isDragging = true;
            dragOffsetX = event.clientX;
            dragOffsetY = event.clientY;
        }

        function mouseUp() {
            isDragging = false;
        }

        function mouseMove() {
            if(isDragging) {
                const deltaX = event.clientX - dragOffsetX;
                const deltaY = event.clientY - dragOffsetY;
                viewBoxX += deltaX;
                viewBoxY += deltaY;
                dragOffsetX = event.clientX;
                dragOffsetY = event.clientY;
                updateViewBox();
            }
        }

        function mouseWheel() {
            event.preventDefault(); // 阻止页面滚动干扰缩放
            const view = event.currentTarget;
            const rect = view.getBoundingClientRect();
            const sx = event.clientX - rect.left;
            const sy = event.clientY - rect.top;
            
            const oldViewBoxZ = viewBoxZ;
            // 计算新的缩放值
            if(event.deltaY < 0) {
                viewBoxZ -= VIEWBOX_INCREMENTS;
                if(viewBoxZ < viewBoxMinZ) viewBoxZ = viewBoxMinZ;
            } else {
                viewBoxZ += VIEWBOX_INCREMENTS;
                if(viewBoxZ > viewBoxMaxZ) viewBoxZ = viewBoxMaxZ;
            }
            
            // 如果缩放值未变化,直接返回
            if(oldViewBoxZ === viewBoxZ) return;
            
            // 应用公式调整偏移量,实现围绕鼠标缩放
            viewBoxX += (sx / 400) * (viewBoxZ - oldViewBoxZ);
            viewBoxY += (sy / 400) * (viewBoxZ - oldViewBoxZ);
            
            updateViewBox();
        }

        // 封装viewBox更新逻辑,避免重复代码
        function updateViewBox() {
            const view = document.getElementById('view');
            view.setAttribute("viewBox", `${-viewBoxX} ${-viewBoxY} ${viewBoxZ} ${viewBoxZ}`);
        }
    </script>
</head>
<body>
    <div>
        <svg id="view" width="400" height="400" viewBox="0 0 400 400"
             onmousedown="mouseDown()" onmouseup="mouseUp()"
             onmousemove="mouseMove()" onmousewheel="mouseWheel()">
            <circle cx="100" cy="100" r="50" stroke="green" stroke-width="2" fill="yellow"></circle>
        </svg>
    </div>
</body>
</html>

关键修改点说明

  1. 新增updateViewBox函数:把重复的viewBox更新逻辑抽离,让代码更简洁易维护。
  2. 鼠标相对坐标计算:通过getBoundingClientRect()获取SVG元素的屏幕位置,计算鼠标在SVG内部的相对坐标(sx, sy)
  3. 偏移量公式应用:按照推导的数学公式调整viewBoxXviewBoxY,确保缩放后鼠标指向的图形点始终停留在指针下方。
  4. 阻止默认滚动行为:添加event.preventDefault()避免鼠标滚轮触发页面滚动,提升缩放体验。

现在你可以测试代码,缩放时鼠标指向的位置会稳定保持在指针处,完全符合你的需求。

内容的提问来源于stack exchange,提问作者Zefugi

火山引擎 最新活动