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

拖拽交互问题:拖拽舰船时仅触发单个棋盘列dragover事件

拖拽舰船时棋盘多列高亮问题解决方案

问题描述

我实现了一个10×10的棋盘,以及3个1行3列的舰船。需求为:拖拽舰船悬停在棋盘上时,舰船覆盖的所有棋盘列均显示特定背景色。当前已为所有棋盘列绑定dragover事件,为舰船设置draggable="true",但存在两个问题:

  • 拖拽舰船时仅单个棋盘列变色;
  • 仅当舰船的某一列悬停在棋盘列上时才触发dragover事件,例如点击舰船第三列拖拽时,只有该列覆盖的棋盘列才会变色。

现有代码

script.js

import dragandrop from "./dragAndDrop.js";

const board = document.querySelector(".board");
const shipsContainer = document.querySelector(".ships");

// 创建10行10列的棋盘
function renderBoard() {
  for (let i = 1; i <= 10; i++) {
    const row = document.createElement("div");
    row.classList.add(`row-${i}`, "row");
    board.appendChild(row);

    for (let j = 1; j <= 10; j++) {
      const column = document.createElement("div");
      column.classList.add(`row-${i}-column-${j}`, "column");
      row.appendChild(column);
    }
  }
}

// 创建3个3格长的舰船
function renderShips() {
  for (let i = 1; i <= 3; i++) {
    const ship = document.createElement("div");
    ship.classList.add(`ship-${i}`, "ship");
    ship.setAttribute("draggable", "true");

    shipsContainer.appendChild(ship);
    for (let j = 1; j <= 3; j++) {
      const shipColumn = document.createElement("div");
      shipColumn.classList.add(`ship-column-${j}`, "ship-column");
      ship.appendChild(shipColumn);
    }
  }
}

renderBoard();
renderShips();
dragandrop();

dragAndDrop.js

export default function dragandrop() {
  const columns = document.querySelectorAll(".column");
  const ships = document.querySelectorAll(".ship");

  ships.forEach((ship) => {
    ship.addEventListener("dragstart", (e) => {
      e.stopPropagation();
      console.log("舰船开始拖拽");
    });
  });

  columns.forEach((column) => {
    column.addEventListener("dragover", (e) => {
      column.style.backgroundColor = "red";
    });
  });
}

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>战舰拖拽</title>
    <!-- Google Fonts -->
    <link
      href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap"
      rel="stylesheet"
    />
    <!-- Stylesheet -->
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <div class="container">
      <div class="board"></div>

      <div class="ships-container">
        <div class="ships"></div>
      </div>
    </div>

    <script src="script.js" type="module"></script>
  </body>
</html>

style.css

* {
  margin: 0;
  box-sizing: border-box;
}

body {
  height: 100vh;
}

.container {
  height: 100%;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.board {
  width: 400px;
  height: 400px;
  border: 1px solid black;
  overflow: hidden;
}

.row {
  width: 100%;
  height: calc(400px / 10);
  border-bottom: 1px solid black;
  display: flex;
  align-items: center;
  justify-content: center;
}

.row:last-of-type {
  border-bottom: none;
}

.column {
  height: 100%;
  width: calc(100% / 10);
  border-right: 1px solid black;
}

.column:last-of-type {
  border-right: none;
}

.ships-container {
  height: 400px;
  width: 200px;
  border: 1px solid black;
  margin-left: 2rem;
  display: flex;
  justify-content: center;
  align-items: center;
}

.ships {
  width: 100%;
  height: 76.5%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 1rem;
}

.ship {
  width: min-content;
  height: auto;
  border: 1px solid black;
  display: flex;
  cursor: pointer;
  overflow: hidden;
}

.ship-column {
  width: 40px;
  height: 40px;
  border-right: 1px solid black;
}
.ship-column:last-of-type {
  border-right: none;
}

解决方案

核心思路

  1. 将dragover事件绑定到整个棋盘容器,而非单个列,确保拖拽过程中持续捕获位置信息
  2. 通过鼠标相对坐标计算舰船覆盖的棋盘列范围,批量设置背景色
  3. 处理拖拽结束和离开棋盘的事件,及时清除所有列的高亮状态

修改后的dragAndDrop.js代码

export default function dragandrop() {
  const board = document.querySelector(".board");
  const columns = document.querySelectorAll(".column");
  const ships = document.querySelectorAll(".ship");
  const GRID_SIZE = 40; // 单个棋盘格的宽高,与CSS保持一致
  const SHIP_LENGTH = 3; // 舰船长度

  // 清除所有列的背景色
  function clearHighlights() {
    columns.forEach(col => col.style.backgroundColor = "");
  }

  ships.forEach((ship) => {
    ship.addEventListener("dragstart", (e) => {
      e.stopPropagation();
      // 存储舰船长度,供后续计算使用
      e.dataTransfer.setData("shipLength", SHIP_LENGTH);
    });

    // 拖拽结束时清除高亮
    ship.addEventListener("dragend", clearHighlights);
  });

  // 监听棋盘的dragover事件
  board.addEventListener("dragover", (e) => {
    e.preventDefault(); // 必须阻止默认行为才能触发后续拖拽逻辑
    clearHighlights();

    // 获取鼠标在棋盘内的相对坐标
    const rect = board.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;

    // 计算当前所在的行和起始列索引(从0开始)
    const rowIndex = Math.floor(y / GRID_SIZE);
    const startColIndex = Math.floor(x / GRID_SIZE);
    const shipLength = parseInt(e.dataTransfer.getData("shipLength"));

    // 计算结束列索引,避免超出棋盘范围
    const endColIndex = Math.min(startColIndex + shipLength - 1, 9);

    // 高亮覆盖的所有列
    for (let col = startColIndex; col <= endColIndex; col++) {
      const targetColumn = document.querySelector(`.row-${rowIndex + 1}-column-${col + 1}`);
      if (targetColumn) {
        targetColumn.style.backgroundColor = "red";
      }
    }
  });

  // 鼠标离开棋盘时清除高亮
  board.addEventListener("dragleave", clearHighlights);
}

关键调整说明

  • 统一了棋盘格和舰船列的尺寸为40px,避免位置计算错位
  • 将dragover事件绑定到棋盘容器,确保拖拽过程中持续触发位置计算
  • 通过鼠标相对坐标计算覆盖的列范围,批量设置背景色解决单格高亮问题
  • 添加dragend和dragleave事件处理,确保拖拽结束后清除所有高亮状态

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 17:14:55