的网格布局(如 Flexbox 或 CSS Grid)可自由设置 position: relative/absolute、启用 draggable="true",并结合 getBoundingClientRect() 精确计算落点。
以下是一个最小可行实现(含完整 HTML + 内联 CSS/JS),聚焦「拖拽 → 悬停高亮 → 松开后吸附至最近棋盘格」核心流程:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>可拖拽国际象棋棋盘</title>
<style>
.chessboard {
display: grid;
grid-template-columns: repeat(8, 1fr);
grid-template-rows: repeat(8, 1fr);
width: 480px; height: 480px;
margin: 20px auto;
border: 2px solid #333;
box-shadow: 0 0 12px rgba(0,0,0,0.2);
}
.square {
display: flex;
align-items: center;
justify-content: center;
font-size: 36px;
user-select: none;
transition: background-color 0.15s;
}
.square.light { background-color: #f0d9b5; }
.square.dark { background-color: #b58863; }
.square.drag-over { background-color: #a0d8f1 !important; }
.piece {
cursor: grab;
user-select: none;
z-index: 10;
/* 启用拖拽 */
-webkit-user-drag: element;
-khtml-user-drag: element;
-moz-user-drag: element;
-o-user-drag: element;
user-drag: element;
}
/* 隐藏默认拖拽虚线框 */
::-webkit-dnd-feedback { opacity: 0; }
</style>
</head>
<body>
<div class="chessboard" id="board"></div>
<script>
const board = document.getElementById('board');
const pieceSymbols = {
'r': '♜', 'n': '♞', 'b': '♝', 'q': '♛', 'k': '♚', 'p': '♟',
'R': '♖', 'N': '♘', 'B': '♗', 'Q': '♕', 'K': '♔', 'P': '♙'
};
// 初始化棋盘(8×8)
for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) {
const square = document.createElement('div');
square.className = `square ${(row + col) % 2 === 0 ? 'light' : 'dark'}`;
square.dataset.row = row;
square.dataset.col = col;
// 初始摆放(简化:仅黑白两排兵 + 底排)
if (row === 1) square.innerHTML = pieceSymbols['p']; // 黑兵
if (row === 6) square.innerHTML = pieceSymbols['P']; // 白兵
if (row === 0) {
const backRank = ['r','n','b','q','k','b','n','r'];
square.innerHTML = pieceSymbols[backRank[col]];
}
if (row === 7) {
const backRank = ['R','N','B','Q','K','B','N','R'];
square.innerHTML = pieceSymbols[backRank[col]];
}
board.appendChild(square);
}
}
// 拖拽逻辑
let draggedPiece = null;
let dragStartX = 0;
let dragStartY = 0;
document.addEventListener('dragstart', e => {
if (e.target.classList.contains('piece')) {
draggedPiece = e.target;
e.dataTransfer.setData('text/plain', '');
// 记录初始偏移(用于吸附计算)
const rect = e.target.getBoundingClientRect();
dragStartX = e.clientX - rect.left;
dragStartY = e.clientY - rect.top;
e.target.style.opacity = '0.7';
e.target.style.zIndex = '20';
}
});
document.addEventListener('dragend', () => {
if (draggedPiece) {
draggedPiece.style.opacity = '';
draggedPiece.style.zIndex = '';
draggedPiece = null;
}
});
document.addEventListener('dragover', e => {
e.preventDefault(); // 必须阻止默认行为才能触发 drop
});
document.addEventListener('drop', e => {
e.preventDefault();
if (!draggedPiece) return;
const boardRect = board.getBoundingClientRect();
const x = e.clientX - boardRect.left;
const y = e.clientY - boardRect.top;
const cellSize = boardRect.width / 8;
// 计算最近格子索引(四舍五入取整)
const col = Math.round(x / cellSize) - 1;
const row = Math.round(y / cellSize) - 1;
// 边界校验
if (row < 0 || row > 7 || col < 0 || col > 7) return;
const targetSquare = board.children[row * 8 + col];
// 清空目标格(简易逻辑,暂不校验吃子)
targetSquare.innerHTML = '';
// 将棋子追加到目标格(自动居中)
targetSquare.appendChild(draggedPiece);
});
// 可选:悬停高亮(增强 UX)
document.addEventListener('dragenter', e => {
if (e.target.classList.contains('square')) {
e.target.classList.add('drag-over');
}
});
document.addEventListener('dragleave', e => {
if (e.target.classList.contains('square')) {
e.target.classList.remove('drag-over');
}
});
</script>
</body>
</html>✅ 关键要点说明:
-
结构优势:
构成响应式网格,每格独立可交互,避免
的 DOM 层级嵌套与样式限制; -
拖拽启用:通过 draggable="true"(或 CSS user-drag: element)激活原生拖拽,无需第三方库;
-
吸附逻辑:利用 clientX/clientY 与棋盘 getBoundingClientRect() 计算相对坐标,除以单格尺寸后 Math.round() 实现像素级吸附;
-
无障碍友好:保留 role="grid" 和 aria-label 可进一步增强可访问性(生产环境建议补充);
-
扩展提示:后续加入合法走法校验时,可在 drop 事件中调用 isValidMove(draggedPiece, fromRow, fromCol, toRow, toCol) 进行拦截。
⚠️ 注意事项:
立即学习“前端免费学习笔记(深入)”;
- 原生 drag-and-drop 在移动端支持有限,如需全平台兼容,建议搭配 interact.js 或自定义 touchstart/move/end 手势;
- 当前示例未持久化棋子状态,真实项目应维护一个二维数组 boardState[8][8] 并同步 UI;
- 若使用图片棋子(如提问者 GitHub 仓库中的 PNG),请将
替换为

,并确保 img 元素同样绑定 dragstart 事件。
掌握此模式后,你已具备构建专业级交互棋盘的核心能力——下一步可无缝集成 FEN 解析、走法高亮、悔棋栈与 UCI 引擎通信。