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

在mousemove事件中使用offsetX/offsetY属性遇拖拽定位问题

解决国际象棋棋子拖拽偏移问题

问题分析

你的拖拽逻辑存在两个核心问题:

  • 使用event.clientX/clientY计算位置,这两个值是相对于浏览器视口的坐标,当页面滚动时,会忽略滚动偏移,导致棋子定位错误。
  • 固定用棋子宽高的一半作为光标偏移,没有记录鼠标点击棋子时的实际相对位置,容易出现瞬移或偏移。

修复后的代码

HTML(保持原结构无需改动)

<html>
    <head>
        <meta charset="UTF-8">
        <link rel="stylesheet" href="style.css">
        <title>Chess</title>
    </head>
    <body>
        <div id ="container" style="position:relative;">
            <div id="gameBox" class="game">
                <canvas id="board" width="800px" height="800px" style = "position:absolute;z-index=-1"></canvas>
                <img id ="brook1" src="assets/piecebrook.svg" style="position:absolute;left:0px;top:0px;" draggable="false"/>
            </div>
        </div>
        <script type="text/javascript" src="js/utility.js"></script>
        <script type="text/javascript" src="js/main.js"></script>
    </body>
</html>

修改后的JS代码

class ChessGame {
    constructor () {
        this.b = document.getElementById("board");
        this.c = document.getElementById("gameBox");
        this.bContext = this.b.getContext("2d");
        this.bContext.fillStyle = "#825e2b";
        this.bContext.lineWidth = "5";
        this.pieces = {}; // 改为对象,适配按ID存储棋子
        this.selectedPiece = null;
        this.mouseOffset = { x: 0, y: 0 }; // 记录鼠标相对于棋子的点击偏移
    }

    addListeners = function() {
        this.c.addEventListener('mousedown', (event) => {
            eventOnMouseDown(event.target, event);
        }, false);
        window.addEventListener('mouseup', eventOnMouseUp, false);
    }

    fillBoard = function() {
        this.bContext.clearRect(0, 0, 800, 800);
        var fill = true;
        for (let i = 0; i < 8; i++) {
            fill = !fill;
            for (let j = 0; j < 8; j++) {
                this.bContext.beginPath();
                this.bContext.fillStyle = fill ? "#76521f" : "#ffffff";
                this.bContext.fillRect(j * 100, i * 100, 100, 100);
                fill = !fill;
            }
        }
    }
        
    startGame = function() {
        this.fillBoard();
        this.addListeners();
        var obj = document.getElementById("brook1");
        this.pieces[obj.id] = obj; // 修正ID存储逻辑
    }
}

eventOnMouseDown = function(elem, event) {
    if (elem.id !== "board") {
        game.selectedPiece = elem;
        game.selectedPiece.style.zIndex = "50";
        // 计算鼠标相对于棋子左上角的偏移量
        const rect = elem.getBoundingClientRect();
        game.mouseOffset.x = event.clientX - rect.left;
        game.mouseOffset.y = event.clientY - rect.top;
        window.addEventListener('mousemove', movePiece, true);
    }
}

eventOnMouseUp = function(event) {
    window.removeEventListener('mousemove', movePiece, true);
    game.selectedPiece = null;
}

movePiece = function(event) {
    if (!game.selectedPiece) return;
    // 获取游戏容器的视口位置
    const gameBoxRect = game.c.getBoundingClientRect();
    // 计算棋子相对于游戏容器的坐标
    const left = event.clientX - gameBoxRect.left - game.mouseOffset.x;
    const top = event.clientY - gameBoxRect.top - game.mouseOffset.y;
    // 限制棋子在棋盘范围内(可选)
    const clampedLeft = Math.max(0, Math.min(left, 800 - game.selectedPiece.width));
    const clampedTop = Math.max(0, Math.min(top, 800 - game.selectedPiece.height));
    game.selectedPiece.style.left = clampedLeft + 'px';
    game.selectedPiece.style.top = clampedTop + 'px';
}

var game = new ChessGame();
game.startGame();

关键改动说明

  1. 记录鼠标点击偏移:在mousedown时通过getBoundingClientRect()获取棋子的视口位置,计算鼠标点击点相对于棋子左上角的偏移,确保拖拽时光标始终对应点击位置,而非固定中心。
  2. 基于父容器计算坐标:拖拽时先获取gameBox的视口位置,用鼠标视口坐标减去容器视口坐标,得到相对于父容器的坐标,再减去鼠标偏移,解决滚动和窗口缩放导致的定位错误。
  3. 优化数据结构:将pieces从数组改为对象,适配按ID快速查找棋子的需求。
  4. 可选边界限制:添加棋子在棋盘范围内的位移限制,防止拖出800*800的棋盘区域。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 00:16:10