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

Canvas自适应缩放后画笔定位偏移问题的解决方案咨询

解决Canvas缩放后绘画位置偏移的问题

这个问题的核心是你把Canvas的内部绘图分辨率和视觉显示尺寸混在一起了。当你直接修改canvas.width和canvas.height来适配窗口时,虽然图片能完整显示,但绘图上下文的坐标系统也被改变了,而鼠标事件获取的是基于Canvas元素显示尺寸的坐标,两者比例不一致就会出现偏移。下面是完整的解决方案,满足你的三个需求:

解决方案思路

  1. 分离绘图分辨率与显示尺寸:
    • 保持Canvas的内部绘图分辨率(width/height属性)为固定比例(比如初始的800x400,或匹配图片的原始比例),确保绘图坐标的准确性。
    • 通过CSS控制Canvas的显示尺寸,在窗口resize时动态调整,让图片完整适配屏幕。
  2. 坐标转换:每次鼠标事件触发时,计算显示尺寸与绘图分辨率的缩放比例,把鼠标的屏幕坐标转换成Canvas绘图上下文的坐标。
  3. 监听窗口resize:确保窗口尺寸变化时,Canvas的显示尺寸能实时更新。

修改后的完整代码

<canvas data-fieldid="1" id="can-1" class="image-canvas"></canvas>
<!-- 假设这里有对应的图片元素 -->
<img id="image_1" src="你的图片地址" style="display:none;">

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script>
var flag = false, prevX = 0, currX = 0, prevY = 0, currY = 0, dot_flag = false;
var x = "red", y = 3;

function init(fieldId) {
    let canvas = document.getElementById('can-' + fieldId);
    let ctx = canvas.getContext("2d");
    // 设置Canvas的绘图分辨率(固定比例,初始800x400)
    const baseWidth = 800;
    const baseHeight = 400;
    canvas.width = baseWidth;
    canvas.height = baseHeight;

    // 加载图片并绘制到Canvas(使用绘图分辨率尺寸)
    let base_image = new Image();
    base_image.crossOrigin = "anonymous"
    base_image.src = document.getElementById('image_' + fieldId).src
    base_image.onload = function() {
        // 按Canvas绘图分辨率绘制图片,保持比例
        ctx.drawImage(base_image, 0, 0, canvas.width, canvas.height);
        // 初始化显示尺寸
        resizeCanvas(canvas);
    };

    // 绑定鼠标事件
    canvas.addEventListener("mousemove", function(e) { findxy('move', e, this) }, false);
    canvas.addEventListener("mousedown", function(e) { findxy('down', e, this) }, false);
    canvas.addEventListener("mouseup", function(e) { findxy('up', e, this) }, false);
    canvas.addEventListener("mouseout", function(e) { findxy('out', e, this) }, false);
}

function draw(ctx) {
    ctx.beginPath();
    ctx.moveTo(prevX, prevY);
    ctx.lineTo(currX, currY);
    ctx.strokeStyle = x;
    ctx.lineWidth = y;
    ctx.stroke();
    ctx.closePath();
}

function findxy(res, e, c) {
    let ctx = c.getContext('2d');
    // 计算缩放比例:绘图分辨率 / 显示尺寸
    const scaleX = c.width / c.offsetWidth;
    const scaleY = c.height / c.offsetHeight;

    // 获取鼠标在Canvas显示元素上的相对坐标,再转换为绘图坐标
    const rect = c.getBoundingClientRect();
    const mouseX = (e.clientX - rect.left) * scaleX;
    const mouseY = (e.clientY - rect.top) * scaleY;

    if (res == 'down') {
        prevX = currX;
        prevY = currY;
        currX = mouseX;
        currY = mouseY;
        flag = true;
        dot_flag = true;
        if (dot_flag) {
            ctx.beginPath();
            ctx.fillStyle = x;
            ctx.fillRect(currX, currY, 2 * scaleX, 2 * scaleY); // 点的大小也要按比例缩放
            ctx.closePath();
            dot_flag = false;
        }
    }
    if (res == 'up' || res == "out") {
        flag = false;
    }
    if (res == 'move') {
        if (flag) {
            prevX = currX;
            prevY = currY;
            currX = mouseX;
            currY = mouseY;
            draw(ctx);
        }
    }
}

// 调整Canvas的显示尺寸,确保完整显示且不小于最小尺寸
function resizeCanvas(canvas) {
    const minWidth = 800;
    const minHeight = 400;
    const ratio = canvas.height / canvas.width;

    // 计算可用宽度:窗口宽度,不小于最小宽度
    let displayWidth = Math.max(minWidth, document.body.clientWidth);
    // 如果窗口宽度小于最小宽度,用窗口宽度作为显示宽度
    if (document.body.clientWidth < minWidth) {
        displayWidth = document.body.clientWidth;
    }
    let displayHeight = displayWidth * ratio;

    // 确保高度不小于最小高度
    if (displayHeight < minHeight) {
        displayHeight = minHeight;
        displayWidth = displayHeight / ratio;
    }

    // 设置CSS显示尺寸
    canvas.style.width = `${displayWidth}px`;
    canvas.style.height = `${displayHeight}px`;
}

$(document).ready(function() {
    $(".image-canvas").each(function() {
        init($(this).data("fieldid"));
    });

    // 监听窗口resize事件,实时调整Canvas显示尺寸
    $(window).on('resize', function() {
        $(".image-canvas").each(function() {
            resizeCanvas(this);
        });
    });
});
</script>

关键修改点说明

  • 分离分辨率与显示尺寸:Canvas的width和height属性固定为800x400(或你需要的基础分辨率),显示尺寸通过style.width和style.height控制,这样绘图上下文的坐标系统不会被缩放影响。
  • 坐标转换逻辑:通过scaleX和scaleY计算出显示尺寸到绘图分辨率的比例,把鼠标的屏幕坐标转换成绘图上下文的真实坐标,解决偏移问题。
  • resize事件监听:窗口尺寸变化时,重新计算Canvas的显示尺寸,确保图片始终完整显示,同时保持绘图精度。
  • 点的大小适配:绘制单点时,点的大小也需要乘以缩放比例,保证在不同显示尺寸下点的视觉大小一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 09:32:47