如何为使用element.insertBefore()重排的元素添加滑动动画
拖拽排序时添加滑动过渡动画
要实现拖拽重排时元素平滑滑动的效果,无需依赖第三方库,可通过原生CSS过渡结合FLIP动画技术改造现有代码,具体实现如下:
修改后的完整代码
HTML
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" /> <ul class='sortable-list list-unstyled'> <li class='draggable' draggable='true'> Lorem ipsum dolor sit amet 1 </li> <li class='draggable' draggable='true'> Lorem ipsum dolor sit amet 2 </li> <li class='draggable' draggable='true'> Lorem ipsum dolor sit amet 3 </li> <li class='draggable' draggable='true'> Lorem ipsum dolor sit amet 4 </li> </ul>
CSS
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Roboto', sans-serif } body { background-color: #2b3035; padding: 20px; } .sortable-list { display: flex; flex-direction: column; gap: 10px; /* 统一元素间距,避免margin导致过渡跳动 */ } .draggable { padding: 10px 12px; border-radius: 5px; border: 1px solid #5c636a; background-color: #212529; cursor: grab; color: #ffffff; touch-action: none; transition: transform 0.3s ease; /* 开启位移过渡动画 */ position: relative; } .dragging { cursor: grabbing; opacity: 0.3; position: absolute; top: var(--drag-top); /* 通过CSS变量控制拖拽元素位置 */ left: 20px; width: calc(100% - 40px); pointer-events: none; /* 避免拖拽元素干扰目标检测 */ }
JavaScript
let draggedElement = null; let initialRects = {}; function listItemDragged(e) { if (!draggedElement) { draggedElement = e.target; draggedElement.classList.add("dragging"); // 记录所有元素初始位置 const siblings = Array.from(draggedElement.parentNode.children); siblings.forEach(el => initialRects[el] = el.getBoundingClientRect()); // 设置拖拽元素初始位置 const rect = draggedElement.getBoundingClientRect(); draggedElement.style.setProperty('--drag-top', `${rect.top - window.scrollY}px`); } // 获取拖拽目标元素 let dropTarget = document.elementFromPoint(e.clientX, e.clientY); if (!dropTarget || !dropTarget.classList.contains('draggable')) { dropTarget = draggedElement; } const parent = draggedElement.parentNode; if (parent === dropTarget.parentNode && draggedElement !== dropTarget) { // 确定插入位置 const shouldInsertBefore = draggedElement.compareDocumentPosition(dropTarget) & Node.DOCUMENT_POSITION_FOLLOWING; // 记录当前元素位置 const currentRects = {}; const siblings = Array.from(parent.children); siblings.forEach(el => currentRects[el] = el.getBoundingClientRect()); // 更新DOM结构 if (shouldInsertBefore) { parent.insertBefore(draggedElement, dropTarget); } else { parent.insertBefore(draggedElement, dropTarget.nextSibling); } // 计算位移并应用过渡 siblings.forEach(el => { if (el !== draggedElement) { const initial = initialRects[el]; const newRect = el.getBoundingClientRect(); const deltaY = initial.top - newRect.top; el.style.transform = `translateY(${deltaY}px)`; } }); // 过渡结束后清除transform,恢复正常布局 setTimeout(() => { const siblings = Array.from(parent.children); siblings.forEach(el => el.style.transform = ''); // 更新位置记录 siblings.forEach(el => initialRects[el] = el.getBoundingClientRect()); }, 300); } // 实时更新拖拽元素位置 draggedElement.style.setProperty('--drag-top', `${e.clientY - draggedElement.offsetHeight/2 - window.scrollY}px`); } function listItemDropped(e) { if (draggedElement) { draggedElement.classList.remove("dragging"); // 清除拖拽元素的定位样式 draggedElement.style.position = ''; draggedElement.style.top = ''; draggedElement.style.left = ''; draggedElement.style.width = ''; draggedElement = null; initialRects = {}; } e.preventDefault(); } function onLoad() { const listItems = document.querySelectorAll(".draggable"); listItems.forEach(option => { option.ondragstart = (e) => e.dataTransfer.effectAllowed = 'move'; option.ondrag = listItemDragged; option.ondragend = listItemDropped; }); document.querySelector('.sortable-list').addEventListener("dragover", (e) => e.preventDefault()); } onLoad();
关键改动说明
- CSS布局优化:用flex布局+gap统一元素间距,避免margin-top导致的过渡跳动;给
.draggable添加transition属性开启位移过渡。 - 拖拽元素定位:拖拽时将元素设为绝对定位,脱离文档流,避免影响其他元素的布局计算。
- FLIP动画实现:记录元素初始位置→更新DOM→计算位移→应用transform过渡→过渡结束后清除样式,实现平滑滑动效果。
内容的提问来源于stack exchange,提问作者nlblack323
相关产品推荐
相关产品推荐

