在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();
关键改动说明
- 记录鼠标点击偏移:在
mousedown时通过getBoundingClientRect()获取棋子的视口位置,计算鼠标点击点相对于棋子左上角的偏移,确保拖拽时光标始终对应点击位置,而非固定中心。 - 基于父容器计算坐标:拖拽时先获取
gameBox的视口位置,用鼠标视口坐标减去容器视口坐标,得到相对于父容器的坐标,再减去鼠标偏移,解决滚动和窗口缩放导致的定位错误。 - 优化数据结构:将
pieces从数组改为对象,适配按ID快速查找棋子的需求。 - 可选边界限制:添加棋子在棋盘范围内的位移限制,防止拖出800*800的棋盘区域。
内容的提问来源于stack exchange,提问作者Courby
相关产品推荐
相关产品推荐

