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

如何为使用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();

关键改动说明

  1. CSS布局优化:用flex布局+gap统一元素间距,避免margin-top导致的过渡跳动;给.draggable添加transition属性开启位移过渡。
  2. 拖拽元素定位:拖拽时将元素设为绝对定位,脱离文档流,避免影响其他元素的布局计算。
  3. FLIP动画实现:记录元素初始位置→更新DOM→计算位移→应用transform过渡→过渡结束后清除样式,实现平滑滑动效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 03:15:06