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

如何实现可拖拽放置的行列表,解决CSS定位导致的功能冲突

修复方案

核心问题

  • 设position: absolute时元素脱离文档流,父容器flex布局无法作用于拖拽项,导致初始状态重叠
  • 设position: relative时,top/left属性是相对于元素本身在文档流中的初始位置偏移,和拖拽逻辑用的全局视窗坐标不匹配,导致拖拽异常

具体修改步骤

  1. 给拖拽项的父容器#lang1Container添加position: relative,建立定位上下文,让子元素的absolute定位基于父容器计算
  2. 页面初始化时临时将拖拽项设为relative获取flex布局下的默认位置,再改回absolute设置初始坐标,保证初始状态均匀分布
  3. 修正拖拽和吸附逻辑的坐标计算,统一转换为相对于父容器的坐标
  4. 修复CSS中font-size缺失数值的语法错误

完整可运行代码

<!DOCTYPE html>
<html>
  <head>
    <style>
      html,
      body {
        width: 100%;
        height: 100%;
        overflow: hidden;
      }

      .lang1Container {
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: column;
        justify-content: flex-start;
      }

      .meaningContainer {
        border: 1px solid rgba(15, 15, 15, 0.2);
        box-shadow: 0px 0px 5px 10px rgba(138, 57, 57, 0.2);
      }

      .lang1Item {
        width: 300px;
        height: 20px;
        position: absolute;
        background-color: #f44336;
        color: #ffffff;
        font-family: Oxygen, sans-serif;
        font-size: 14px;
        padding: 7px;
        box-shadow: inset 1px 1px 10px 0 rgba(138, 57, 57, 0.2);
        border-radius: 3px;
        cursor: pointer;
      }

      .snap {
        width: 301px;
        height: 30px;
        margin-top: 25px;
        margin-bottom: 25px;
      }

      .over {
        background-color: rgb(158, 89, 87, 0.2);
        border: 1px solid #7c5b59;
        border-radius: 3px;
        border-style: dashed;
      }
    </style>
  </head>
  <body>
    <div style="display: flex; flex-direction: column">
      <div
        id="lang1MeaningContainer"
        style="
          background-color: bisque;
          min-width: 300px;
          min-height: 400px;
          border-color: red;
          border-style: dashed;
          margin: 10px;
        "
      >
        <div class="header">lang1MeaningContainer - Meaning Container</div>
        <div class="snap"></div>
        <div class="snap"></div>
        <div class="snap"></div>
        <div class="snap"></div>
        <div class="snap"></div>
        <div class="snap"></div>
      </div>

      <div
        id="lang1Container"
        style="
          background-color: bisque;
          min-width: 300px;
          min-height: 400px;
          border-color: red;
          border-style: dashed;
          margin: 10px;
          display: flex;
          flex-direction: column;
          position: relative;
        "
      >
        <div class="header">lang1Container - English Words</div>
        <div id="item1" class="lang1Item">Lang Item 1</div>
        <div id="item2" class="lang1Item">Lang Item 2</div>
        <span id="item3" class="lang1Item">Lang Item 3</span>
        <span id="item4" class="lang1Item">Lang Item 4</span>
      </div>
    </div>
    <script>
      let lang1Container = document.getElementById("lang1Container");
      let meaningContainer = document.getElementById("lang1MeaningContainer");
      let lang1Items = document.getElementsByClassName("lang1Item");
      let containerRect = lang1Container.getBoundingClientRect();

      // 初始化拖拽项初始位置
      for (let i = 0; i < lang1Items.length; i++) {
        let item = lang1Items[i];
        // 临时切换为relative获取flex布局下的默认位置
        item.style.position = 'relative';
        const offsetTop = item.offsetTop;
        // 切回absolute设置初始坐标
        item.style.position = 'absolute';
        item.style.top = offsetTop + 'px';
        item.style.left = '0px';
        
        item.addEventListener("mousedown", (e) => {
          onMouseDown(e, item);
        });
        document.body.addEventListener("mousemove", (e) => {
          onMouseMove(e, item);
        });
        item.addEventListener("mouseup", (e) => {
          onMouseUp(e, item);
        });
      }

      let mouseOffset = { x: 0, y: 0 };
      let isMouseDown = false;

      let doElsCollide = function (el1, el2) {
        el1.offsetBottom = el1.offsetTop + el1.offsetHeight;
        el1.offsetRight = el1.offsetLeft + el1.offsetWidth;
        el2.offsetBottom = el2.offsetTop + el2.offsetHeight;
        el2.offsetRight = el2.offsetLeft + el2.offsetWidth;

        return !(
          el1.offsetBottom < el2.offsetTop ||
          el1.offsetTop > el2.offsetBottom ||
          el1.offsetRight < el2.offsetLeft ||
          el1.offsetLeft > el2.offsetRight
        );
      };
      function snapMeaning(item, container) {
        let box = container.getBoundingClientRect();
        // 转换为相对于父容器的坐标
        item.style.left = (box.x - containerRect.x) + "px";
        item.style.top = (box.y - containerRect.y) + "px";
      }
      function onMouseDown(e, item) {
        isMouseDown = true;

        mouseOffset = {
          x: item.offsetLeft - e.clientX + containerRect.x,
          y: item.offsetTop - e.clientY + containerRect.y,
        };

        item.style.backgroundColor = "red";
      }
      function onMouseMove(e, item) {
        e.preventDefault();
        if (isMouseDown) {
          // 转换为相对于父容器的坐标
          item.style.left = (e.clientX + mouseOffset.x - containerRect.x) + "px";
          let tmpStyle = getComputedStyle(item);
          let strMarginTop = tmpStyle.marginTop;
          let tmpMarginTop = strMarginTop.replace("px", "");
          item.style.top = (e.clientY - tmpMarginTop + mouseOffset.y - containerRect.y) + "px";

          // 检查碰撞
          let snaps = document.getElementsByClassName("snap");
          for (let i = 0; i < snaps.length; i++) {
            if (doElsCollide(item, snaps[i])) {
              snaps[i].className = "snap over";
            } else {
              snaps[i].className = "snap";
            }
          }
        }
      }
      function onMouseUp(e, item) {
        isMouseDown = false;

        let snaps = document.getElementsByClassName("snap");
        for (let i = 0; i < snaps.length; i++) {
          if (doElsCollide(item, snaps[i])) {
            snapMeaning(item, snaps[i]);
          }
        }
        item.className = "lang1Item";
      }
    </script>
  </body>
</html>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 15:36:02