如何实现可拖拽放置的行列表,解决CSS定位导致的功能冲突
修复方案
核心问题
- 设
position: absolute时元素脱离文档流,父容器flex布局无法作用于拖拽项,导致初始状态重叠 - 设
position: relative时,top/left属性是相对于元素本身在文档流中的初始位置偏移,和拖拽逻辑用的全局视窗坐标不匹配,导致拖拽异常
具体修改步骤
- 给拖拽项的父容器
#lang1Container添加position: relative,建立定位上下文,让子元素的absolute定位基于父容器计算 - 页面初始化时临时将拖拽项设为
relative获取flex布局下的默认位置,再改回absolute设置初始坐标,保证初始状态均匀分布 - 修正拖拽和吸附逻辑的坐标计算,统一转换为相对于父容器的坐标
- 修复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
相关产品推荐
相关产品推荐

