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

Canvas屏幕中心基准缩放问题求解(附代码)

Canvas屏幕中心缩放逻辑修正

核心原理

要实现以屏幕中心为基准的缩放,关键在于缩放过程中保持屏幕中心对应的世界坐标不变,具体步骤:

  1. 获取当前画布的缩放比例与偏移量
  2. 计算屏幕中心在世界坐标系中的对应位置
  3. 计算新的缩放比例
  4. 根据新缩放比例重新调整偏移量,确保屏幕中心的世界坐标位置不变

修正后的zoom函数

替换原代码中的zoom函数即可实现需求:

function zoom(p){
    const currentTransform = ctx.getTransform();
    const currentScale = currentTransform.a;
    const currentX = currentTransform.e;
    const currentY = currentTransform.f;

    // 屏幕中心的像素坐标
    const screenCenterX = canvas.width / 2;
    const screenCenterY = canvas.height / 2;

    // 计算屏幕中心对应的世界坐标
    const worldCenterX = (screenCenterX - currentX) / currentScale;
    const worldCenterY = (screenCenterY - currentY) / currentScale;

    let newScale;
    if (p === 1) {
        newScale = currentScale * 2;
    } else {
        // 限制最小缩放比例,避免缩放过小导致异常
        newScale = Math.max(currentScale / 2, 0.125);
    }

    // 计算新偏移量,保证屏幕中心对应原世界坐标
    const newX = screenCenterX - worldCenterX * newScale;
    const newY = screenCenterY - worldCenterY * newScale;

    ctx.setTransform(newScale, 0, 0, newScale, newX, newY);
    graphPaperDrawer();
}

完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>2D Circuit Simulator</title>
    <style>
        canvas {
            border: 1px solid white;
            background-color: black;
        }
        body {
            color: white;
            margin: 0px;
            background-color: black;
        }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <button onclick="zoom(1)">Zoom+</button>
    <button onclick="zoom(-1)">Zoom-</button>
    <script>
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = 960;
        canvas.height = 600;
        var eleMatrix = [];
        var bodyTrans = new DOMMatrix([1, 0, 0, 1, 0, 0]);
        ctx.setTransform(bodyTrans);
        graphPaperDrawer();

        function zoom(p){
            const currentTransform = ctx.getTransform();
            const currentScale = currentTransform.a;
            const currentX = currentTransform.e;
            const currentY = currentTransform.f;

            const screenCenterX = canvas.width / 2;
            const screenCenterY = canvas.height / 2;

            const worldCenterX = (screenCenterX - currentX) / currentScale;
            const worldCenterY = (screenCenterY - currentY) / currentScale;

            let newScale;
            if (p === 1) {
                newScale = currentScale * 2;
            } else {
                newScale = Math.max(currentScale / 2, 0.125);
            }

            const newX = screenCenterX - worldCenterX * newScale;
            const newY = screenCenterY - worldCenterY * newScale;

            ctx.setTransform(newScale, 0, 0, newScale, newX, newY);
            graphPaperDrawer();
        }

        dragElement(canvas);
        function dragElement(elmnt) {
            var elpos = [], oldx = 0, oldy = 0, disx = 0, disy = 0, graphxy = [],cusdr, target;
            elmnt.onmousedown = dragMouseDown;
            elmnt.ontouchstart = dragMouseDown;
            function dragMouseDown() {
                event.preventDefault();
                oldx = getXY(event).x;
                oldy = getXY(event).y;
                graphxy = [bodyTrans.e, bodyTrans.f,bodyTrans.a];

                document.onmouseup = closeDragElement;
                document.ontouchend = closeDragElement;
                document.onmousemove = elementDrag;
                document.ontouchmove = elementDrag;
            }
            function elementDrag() {
                event.preventDefault();
                disx = getXY(event).x - oldx;
                disy = getXY(event).y - oldy;
                var subtar = null;
                cusdr = [false];

                    ctx.setTransform(ctx.getTransform().a, 0, 0, ctx.getTransform().a, (graphxy[0] + disx), (graphxy[1] + disy));
                    graphPaperDrawer();
                    elpos = [ctx.getTransform().e,ctx.getTransform().f,ctx.getTransform().a];
            }
            function closeDragElement() {
                 bodyTrans = new DOMMatrix([elpos[2], 0, 0, elpos[2], elpos[0], elpos[1]]);
                document.onmouseup = null;
                document.ontouchend = null;
                document.onmousemove = null;
                document.ontouchmove = null;
                console.log(bodyTrans.e+","+bodyTrans.f)
            }
        }
        function graphPaperDrawer() {   
               var ft = ctx.getTransform().a;
               ctx.clearRect(-ctx.getTransform().e/ft, -ctx.getTransform().f/ft, canvas.width/ft, canvas.height/ft);    
              ctx.fillStyle= "red";
              ctx.fillRect(0, 0, 100, 100);
              ctx.fillStyle = "green";
              ctx.fillRect(860, 0, 100, 100);
              ctx.fillStyle = "blue";
              ctx.fillRect(0, 500, 100, 100);
              ctx.fillStyle = "white";
              ctx.fillRect(480, 300, 100, 100);
            }
                function getXY(event) {
                    const deviceType = window.matchMedia("(pointer: coarse)").matches;
                    var x, y;
                    if (deviceType) {
                        var x = event.touches[0].clientX;
                        var y = event.touches[0].clientY;
                    } else {
                        var x = event.clientX;
                        var y = event.clientY;
                    }
                    return { "x": x, "y": y };
                }
    </script>
</body>
</html>

效果说明

修正后,点击缩放按钮时,画布会以屏幕中心(480,300像素位置)为基准放大/缩小,缩放过程中屏幕中心对应的世界元素位置保持不变,符合预期的中心缩放效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 21:05:24