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

Canvas坐标系与鼠标位置缩放不一致问题求助

解决Canvas绘图位置与鼠标坐标不匹配的问题

按下Q键绘制正方形时,图形位置与鼠标位置偏差明显,离屏幕原点越远错位越严重,甚至超出视口。

问题根源

Canvas元素存在两个独立尺寸:

  • CSS样式尺寸:控制canvas在页面上的显示大小(当前通过100vw/100vh设为全屏)
  • 绘图缓冲区尺寸:canvas内部的像素画布尺寸,默认是300px×150px

当CSS显示尺寸和绘图缓冲区尺寸不一致时,浏览器会拉伸/压缩绘图内容,导致绘图坐标(基于缓冲区)和屏幕鼠标坐标(基于显示尺寸)不对应,距离原点越远,缩放比例带来的偏差越大。

修复步骤及代码修改

1. 初始化Canvas的实际绘图尺寸

在main.js开头,将canvas的width和height属性设置为窗口的实际像素值,替代仅依赖CSS的方式:

const canvas = document.getElementById("main-canvas");
// 设置绘图缓冲区尺寸为窗口实际像素
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.overflow = "hidden";
const ctx = canvas.getContext("2d");
// ... 其他代码

2. 监听窗口resize事件,动态更新Canvas尺寸

窗口大小变化时,同步更新绘图缓冲区尺寸,避免再次错位:

// 在main.js末尾添加
window.addEventListener('resize', () => {
    // 保存当前画布内容(可选,需保留绘制内容时启用)
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    // 更新尺寸
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    // 恢复画布内容
    ctx.putImageData(imageData, 0, 0);
    // 重新绘制背景色
    ctx.fillStyle = canvasColor;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
});

3. 修正初始化时的背景绘制

原来使用screen.width/screen.height是屏幕整体尺寸,改为canvas.width/canvas.height匹配绘图缓冲区:

// 替换原背景绘制代码
ctx.fillStyle = canvasColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);

4. (可选)让正方形以鼠标为中心绘制

当前fillRect的x/y是左上角坐标,若想让鼠标位于正方形中心,调整坐标:

// 在Q键按下的处理函数中修改fillRect
ctx.fillRect(mousePos.x - shapeSize/4, mousePos.y - shapeSize/4, shapeSize/2, shapeSize/2);

完整修改后的main.js代码

const canvas = document.getElementById("main-canvas");
// 设置绘图缓冲区尺寸为窗口实际像素
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.overflow = "hidden";
const ctx = canvas.getContext("2d");
const settingsButton = document.getElementById("settings-button");
const settingsMenu = document.getElementById("settings-menu");
let mousePos = { x: undefined, y: undefined };
let settingsState = false
let shapeSize = 50
let squareColor = "#0000ff";
let canvasColor = "#000000";

// 初始化背景
ctx.fillStyle = canvasColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);

document.getElementById("settings-button").onclick = function() {
    settingsState =  !settingsState;
    if (settingsState === true) {
        settingsButton.style.backgroundColor = "gray";
        settingsMenu.style.visibility = "visible";
        
    }
    else {
        settingsButton.style.backgroundColor = "white";
        settingsMenu.style.visibility = "hidden";
    }
}

document.getElementById("shape-size").onchange = function() {
    let shapeSizeValue = document.getElementById("shape-size");
    let shapeSizeLabel = document.getElementById("shape-size-label");
    shapeSizeLabel.innerHTML = "Shape Size: " + shapeSizeValue.value;
    shapeSize = parseInt(shapeSizeValue.value) // 转为数字,避免除法时字符串拼接
}
document.getElementById("square-color").onchange = function() {
    let squareColorValue = document.getElementById("square-color");
    squareColor = squareColorValue.value
}
document.getElementById("circle-color").onchange = function() {
    let circleColorValue = document.getElementById("circle-color");
    // 补充缺失的赋值逻辑
    // circleColor = circleColorValue.value;
}
document.getElementById("rectangle-color").onchange = function() {
    let rectangleColorValue = document.getElementById("rectangle-color");
    // 补充缺失的赋值逻辑
    // rectangleColor = rectangleColorValue.value;
}
document.getElementById("triangle-color").onchange = function() {
    let triangleColorValue = document.getElementById("triangle-color");
    // 补充缺失的赋值逻辑
    // triangleColor = triangleColorValue.value;
}
document.getElementById("canvas-color").addEventListener("input", (event) => {
    let canvasColorValue = document.getElementById("canvas-color");
    canvasColor = canvasColorValue.value; // 保存当前背景色
    ctx.fillStyle = canvasColor;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
});

window.addEventListener('mousemove', (event) => {
    mousePos = { x: event.clientX, y: event.clientY };
    document.getElementById("settings-button").innerHTML = mousePos.x.toString() + " " + mousePos.y.toString()
});

window.onkeydown = function(event) {
    if (event.key === "q") {
        ctx.fillStyle = squareColor;
        // 可选:让正方形以鼠标为中心
        ctx.fillRect(mousePos.x - shapeSize/4, mousePos.y - shapeSize/4, shapeSize/2, shapeSize/2);
        // 原代码(左上角对齐鼠标):
        // ctx.fillRect(mousePos.x, mousePos.y, shapeSize/2, shapeSize/2);
    }
    if (event.key === "w") {

    }
    if (event.key === "e") {

    }
    if (event.key === "r") {

    }
}

// 监听窗口resize,更新canvas尺寸
window.addEventListener('resize', () => {
    // 保存当前画布内容(可选,需保留绘制内容时启用)
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    // 更新尺寸
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    // 恢复内容
    ctx.putImageData(imageData, 0, 0);
    // 重新绘制背景
    ctx.fillStyle = canvasColor;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
});

额外说明

  • 需将shapeSize转为数字类型(parseInt(shapeSizeValue.value)),避免字符串拼接导致计算错误。
  • 原代码中circle-color等颜色选择器的赋值逻辑缺失,可根据功能需求补充。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 12:07:13