前端拖拽排序实现详解:从原理到实践 - 附完整代码

🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志

🎐 个人CSND主页------Micro麦可乐的博客

🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战

🌺《RabbitMQ》专栏19年编写主要介绍使用JAVA开发RabbitMQ的系列教程,从基础知识到项目实战

🌸《设计模式》专栏以实际的生活场景为案例进行讲解,让大家对设计模式有一个更清晰的理解

🌛《开源项目》本专栏主要介绍目前热门的开源项目,带大家快速了解并轻松上手使用

🍎 《前端技术》专栏以实战为主介绍日常开发中前端应用的一些功能以及技巧,均附有完整的代码示例

✨《开发技巧》本专栏包含了各种系统的设计原理以及注意事项,并分享一些日常开发的功能小技巧

💕《Jenkins实战》专栏主要介绍Jenkins+Docker的实战教程,让你快速掌握项目CI/CD,是2024年最新的实战教程

🌞《Spring Boot》专栏主要介绍我们日常工作项目中经常应用到的功能以及技巧,代码样例完整

👍《Spring Security》专栏中我们将逐步深入Spring Security的各个技术细节,带你从入门到精通,全面掌握这一安全技术
如果文章能够给大家带来一定的帮助!欢迎关注、评论互动~

前端拖拽排序实现详解:从原理到实践 - 附完整代码

  • [1. 前言](#1. 前言)
  • [2. 拖拽排序的应用](#2. 拖拽排序的应用)
  • [3. 核心实现原理](#3. 核心实现原理)
      • [3.1 HTML5拖放API基础](#3.1 HTML5拖放API基础)
      • [3.2 关键事件解析](#3.2 关键事件解析)
  • [4. 完整示例代码](#4. 完整示例代码)
  • [5. 结语](#5. 结语)

1. 前言

在我们前端开发过程中经常会用到排序的功能,其中列表项的拖拽排序(Drag-and-Drop Sortable)不仅能让用户直观、高效地调整顺序,还能大幅提升交互体验。无论是管理后台的菜单排序、看板(Kanban)中任务卡片的调整,还是移动端的图片/视频重排,拖拽排序都是不可或缺的交互模式。

本文博主将带着小伙伴从零开始,用原生 HTML5 Drag & Drop API 实现一个简洁的可拖拽排序列表,并模拟向后端提交新顺序的完整流程。


2. 拖拽排序的应用

任务管理工具

用户可以拖拽卡片改变优先级或在不同分组间移动(如Trello

内容管理系统

页面元素排序(如WordPress

图片/视频排序

相册管理、商品轮播图顺序调整

问卷表单选项

管理问答列表时,调整题目或选项顺序更直观

自定义导航菜单

后台可视化拖拽菜单层级与顺序

这些场景下,通过拖拽排序,用户无需点上下箭头、输入序号,就能快速完成调整,显著提升效率与体验。


3. 核心实现原理

3.1 HTML5拖放API基础


HTML5 Drag & Drop 原生 API 核心流程如下:

  • 设定可拖拽元素:draggable="true"
  • 监听拖拽开始:dragstart 事件,记录当前拖拽项索引或标识
  • 允许拖拽进入目标:在目标元素或容器上 dragover 事件中调用 event.preventDefault()
  • 处理放置:drop 事件中获取拖拽项和目标项索引,完成 DOM 位置交换。
  • 结束拖拽:可选的 dragend 事件,用于清理样式或状态。

3.2 关键事件解析

默认拖放API具备以下几个事件类型:

事件类型 触发时机 常用操作
dragstart 开始拖拽元素时 设置被拖拽元素的ID
dragenter 进入目标元素时 添加视觉反馈
dragover 在目标元素上悬停时 阻止默认行为(允许放置)
dragleave 离开目标元素时 移除视觉反馈
drop 在目标元素上释放时 处理元素位置交换
dragend 拖拽操作结束时 清理状态

4. 完整示例代码

下面是一个最小可运行的示例,小伙伴们可以复制至本地测试运行,注意后端排序保存已经将当前顺序的 ID 数组传递,后端接口小伙伴们自行实现即可

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>拖拽排序示例</title>
  <style>
    body {
      font-family: sans-serif;
      padding: 20px;
    }
    #sortable-list {
      list-style: none;
      padding: 0;
      width: 300px;
      margin: 0 auto;
    }
    #sortable-list li {
      padding: 10px 15px;
      margin-bottom: 8px;
      background: #f0f0f0;
      border: 1px solid #ddd;
      cursor: move;
      user-select: none;
    }
    /* 拖拽时样式 */
    .dragging {
      opacity: 0.5;
    }
    .over {
      border-top: 2px solid #007bff;
    }
  </style>
</head>
<body>

  <h2>拖拽排序示例</h2>
  <ul id="sortable-list">
    <li data-id="1" draggable="true">项目 1</li>
    <li data-id="2" draggable="true">项目 2</li>
    <li data-id="3" draggable="true">项目 3</li>
    <li data-id="4" draggable="true">项目 4</li>
    <li data-id="5" draggable="true">项目 5</li>
  </ul>
  <button id="saveOrderBtn">保存顺序</button>

  <script>
    const list = document.getElementById('sortable-list');
    let dragSrcEl = null;

    function handleDragStart(e) {
      dragSrcEl = this;
      this.classList.add('dragging');
      e.dataTransfer.effectAllowed = 'move';
      e.dataTransfer.setData('text/plain', this.dataset.id);
    }

    function handleDragOver(e) {
      e.preventDefault(); // 必须阻止默认,才有 drop 事件
      e.dataTransfer.dropEffect = 'move';
      return false;
    }

    function handleDragEnter(e) {
      if (this !== dragSrcEl) {
        this.classList.add('over');
      }
    }

    function handleDragLeave(e) {
      this.classList.remove('over');
    }

    function handleDrop(e) {
      e.stopPropagation(); // 阻止事件冒泡
      if (dragSrcEl !== this) {
        // 在 DOM 中交换位置
        const nodes = Array.from(list.children);
        const srcIndex = nodes.indexOf(dragSrcEl);
        const targetIndex = nodes.indexOf(this);

        if (srcIndex < targetIndex) {
          list.insertBefore(dragSrcEl, this.nextSibling);
        } else {
          list.insertBefore(dragSrcEl, this);
        }
      }
      return false;
    }

    function handleDragEnd(e) {
      // 清理样式
      this.classList.remove('dragging');
      Array.from(list.children).forEach(item => {
        item.classList.remove('over');
      });
    }

    // 绑定事件
    Array.from(list.children).forEach(item => {
      item.addEventListener('dragstart', handleDragStart);
      item.addEventListener('dragenter', handleDragEnter);
      item.addEventListener('dragover', handleDragOver);
      item.addEventListener('dragleave', handleDragLeave);
      item.addEventListener('drop', handleDrop);
      item.addEventListener('dragend', handleDragEnd);
    });

    // 模拟后端提交新顺序
    document.getElementById('saveOrderBtn').addEventListener('click', () => {
      const order = Array.from(list.children).map(li => li.dataset.id);
      console.log('新的顺序:', order);

      // 示例:POST 到 /api/update-order
      fetch('/api/update-order', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ order })
      })
      .then(res => {
        if (!res.ok) throw new Error('保存失败');
        return res.json();
      })
      .then(data => {
        alert('顺序保存成功!');
      })
      .catch(err => {
        console.error(err);
        alert('保存失败,请重试');
      });
    });
  </script>

</body>
</html>

要点说明:

1、每个 <li> 元素设置了 draggable="true"data-id 作为唯一标识;

2、dragstart 时记录源元素并设置拖拽数据;

3、dragover 必须 preventDefault(),否则无法触发 drop

4、drop 事件中根据源元素和目标元素在父容器中的索引,动态交换位置;

5、点击"保存顺序"按钮后,将当前顺序的 ID 数组发送给后端接口 /api/update-order,完成持久化。


5. 结语

通过上述示例,相信小伙伴们已经掌握了使用原生 HTML5 Drag & Drop API 实现前端拖拽排序的全流程:从可拖拽元素配置、事件监听,到 DOM 顺序交换,再到模拟后端提交。

该方案无需引入第三方库,也足够轻量易懂。可以根据项目需求进一步优化:

  • 性能优化:对大型列表使用虚拟化或节流拖拽事件。
  • 视觉增强 :使用动画(CSS transition)平滑排序过程。
  • 多容器拖拽:扩展至跨列表、跨分组的拖拽。
  • 第三方库 :在复杂场景下可结合 SortableJSDragula 等成熟库。

希望本文能帮助小伙伴们快速在项目中落地拖拽排序功能,如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家 一键三连 给博主一点点鼓励!


相关推荐
爷_3 小时前
字节跳动震撼开源Coze平台!手把手教你本地搭建AI智能体开发环境
前端·人工智能·后端
charlee444 小时前
行业思考:不是前端不行,是只会前端不行
前端·ai
Amodoro5 小时前
nuxt更改页面渲染的html,去除自定义属性、
前端·html·nuxt3·nuxt2·nuxtjs
Wcowin6 小时前
Mkdocs相关插件推荐(原创+合作)
前端·mkdocs
伍哥的传说6 小时前
CSS+JavaScript 禁用浏览器复制功能的几种方法
前端·javascript·css·vue.js·vue·css3·禁用浏览器复制
lichenyang4536 小时前
Axios封装以及添加拦截器
前端·javascript·react.js·typescript
Trust yourself2437 小时前
想把一个easyui的表格<th>改成下拉怎么做
前端·深度学习·easyui
三口吃掉你7 小时前
Web服务器(Tomcat、项目部署)
服务器·前端·tomcat
Trust yourself2437 小时前
在easyui中如何设置自带的弹窗,有输入框
前端·javascript·easyui
烛阴7 小时前
Tile Pattern
前端·webgl