在日常开发中,拖拽功能无疑是一个常见的需求场景。为了更好地满足这一需求,HTML5提供了一套便捷的拖放API。这些API不仅能够帮助开发者轻松实现拖拽效果,还可以提高排查拖拽问题的效率,甚至可以让我们更加灵活地自定义拖拽场景和设计能力。
要想完全掌握拖放API的使用及常见问题,本文将从三个步骤出发,带你深入了解并掌握这一技术。
首先,我们将通过案例演示来直观地展示拖拽功能在实际应用中的表现,帮助你快速理解拖拽的基本概念和操作流程。其次,我们将详细解析拖放流程并介绍相关的API,包括拖放事件的类型、事件处理函数以及关键属性的使用方法等。最后,我们将通过案例实现和讲解来巩固所学知识,让你能够亲自动手实践,并将所学知识应用到实际项目中。
通过本文的学习,你将能够轻松掌握HTML5拖放API的使用技巧,提高拖拽功能的开发效率,为你的项目增色添彩。
一、案例演示
实现一个可拖放自定义课程表,将左侧的科目拖拽到右侧的课程表中,支持增加、修改、删除、移动功能。

二、API介绍
1. 拖放过程
整个拖放过程中,存在两个关键元素:拖拽元素、放置元素 拖拽元素:被拖拽的元素
- ondrag:元素被拖拽时触发,从开始拖拽到拖拽结束前整个过程会一直持续的触发 ◦ondragstart:元素被拖拽开始时触发
- ondrop:拖拽元素被放置到放置元素内时触发,如果没有在放置元素内松手,则不会触发
放置元素:
- ondragenter:有拖拽元素进入时触发
- ondragover:有拖拽元素在该元素上时触发,在离开前会持续触发
- ondragleave:拖拽元素离开时触发
- ondragend:拖拽元素放置时触发

2. 可拖拽元素
在HTML中,文本、图片和链接是默认可以拖放的元素。
其他元素都是默认不可拖动的,如果需要让其他非默认可拖动的HTML元素变得可拖动,比如<div>、<span>等,你需要明确地为这些元素设置 draggable="true" 属性。这样,这些元素就能够接受拖放操作了。
1<div draggable="true">语文</div>
3. 放置元素
所有HTML元素在默认情况下都不接受拖拽元素的放置,除非通过特定的事件处理来允许。 要使一个HTML元素能够接受被拖动的元素,需要对这个元素进行一些特定的设置和事件绑定:
- dragover事件:当被拖动的元素在另一个元素上方移动时,会触发dragover事件。为了接受拖放,必须在- -
- dragover事件处理器中调用event.preventDefault()方法,这会阻止浏览器的默认行为,也就是不接受任何被拖放的元素。
1container.ondragover = (e) => { 2 e.preventDefault(); 3}
4. DataTransfer
DataTransfer对象用于保存拖动并放下(drag and drop)过程中的数据。它可以保存一项或多项数据,这些数据项可以是一种或者多种数据类型
- dropEffect:获取当前选定的拖放操作类型,或设置为一个新的类型。值必须为 none、copy、link、move
- effectAllowed:提供所有可用的操作类型。值必须为none、copy、copyLink、copyMove、link、copyMove、move、all、uninitialized
- files:包含数据传输中的所有本地文件列表
- items(只读):提供一个包含所有拖动数据列表的DataTransferItemList对象
- types(只读):一个提供 dragstart 事件中设置的格式的 strings 数组
三、案例实现
1.把左侧科目设置为可拖拽
1<div class="content"> 2 <div class="left" data-drop="move"> 3 <div draggable="true" style="background:rgb(26, 231, 156)">语文</div> 4 <div draggable="true" style="background:rgb(107, 219, 15)">数学</div> 5 <div draggable="true" style="background:rgb(208, 133, 13)">英语</div> 6 <div draggable="true" style="background:rgb(30, 98, 246)">物理</div> 7 <div draggable="true" style="background:rgb(210, 40, 113)">化学</div> 8 <div draggable="true" style="background:rgb(210, 224, 26)">生物</div> 9 </div> 10 <div class="right"> 11 <table> 12 <thead> 13 <tr> 14 <td>星期一</td> 15 <td>星期二</td> 16 <td>星期三</td> 17 <td>星期四</td> 18 <td>星期五</td> 19 </tr> 20 </thead> 21 <body> 22 <tr> 23 <td></td> 24 <td></td> 25 <td></td> 26 <td></td> 27 <td></td> 28 </tr> 29 <tr> 30 <td></td> 31 <td></td> 32 <td></td> 33 <td></td> 34 <td></td> 35 </tr> 36 <tr> 37 <td></td> 38 <td></td> 39 <td></td> 40 <td></td> 41 <td></td> 42 </tr> 43 <tr> 44 <td></td> 45 <td></td> 46 <td></td> 47 <td></td> 48 <td></td> 49 </tr> 50 </body> 51 </table> 52 </div> 53</div>
2. 绑定事件
这里采用事件委托的方式,对整个父元素进行事件绑定
1const container = document.querySelector('.content'); 2ondragover:让整个容器内的所有元素都可以接受拖拽元素,这里只需要阻止默认行为就可以 3container.ondragover = (e) => { 4 e.preventDefault(); 5}
ondragstart:拖拽开始时,需要做以下处理
- 拖拽元素原来的位置样式有些变化,这里就是整体颜色变得更透明
- 从左侧拖到右侧,为新增,鼠标显示新增的手势。右侧拖到左侧为移除,显示普通手势。(拖拽过程中默认为新增手势)
- 保存当前拖拽元素,后续操作需要用到

通过data属性给拖拽元素添加标识,标识此元素是新增还是移除,新增设置为 data-effect="copy"
1<div class="left" data-drop="move"> 2 <div data-effect="copy" draggable="true" style="background:rgb(26, 231, 156)">语文</div> 3 <div data-effect="copy" draggable="true" style="background:rgb(107, 219, 15)">数学</div> 4 <div data-effect="copy" draggable="true" style="background:rgb(208, 133, 13)">英语</div> 5 <div data-effect="copy" draggable="true" style="background:rgb(30, 98, 246)">物理</div> 6 <div data-effect="copy" draggable="true" style="background:rgb(210, 40, 113)">化学</div> 7 <div data-effect="copy" draggable="true" style="background:rgb(210, 224, 26)">生物</div> 8</div>
1let source = ''; 2container.ondragstart = (e) => { 3 // 如果是移除操作,则设置手势为移除 4 if (e.target.dataset.effect === "move") { 5 e.dataTransfer.effectAllowed = "move"; 6 } 7 source = e.target; 8 // 设置拖拽元素的样式 9 e.target.style.opacity = '0.2' 10}
ondragenter:进入放置元素,需要做以下处理
- 判断该元素以及该元素的所有上级元素是否可以接收拖拽元素。哪些元素可以接收,也通过data属性进行标识 data-drop="copy"
- 如果可以接收拖拽元素,需要设置其样式用于标识
1<tr> 2 <td data-drop="copy"></td> 3 <td data-drop="copy"></td> 4 <td data-drop="copy"></td> 5 <td data-drop="copy"></td> 6 <td data-drop="copy"></td> 7</tr>
1container.ondragenter = (e) => { 2 const dropNode = getDropNode(e.target); 3 // 判断放置元素是否可以接收拖拽元素,当 data-effect 和 data-drop 的值相等时,说明可以 4 if (dropNode && dropNode.dataset.drop === source.dataset.effect) { 5 // 给放置元素添加样式 6 dropNode.classList.add('hover-background'); 7 } 8} 9 10// 获取最近的可接受拖拽元素的父节点 11function getDropNode(node) { 12 while (node) { 13 // 判断元素是否设置了data-drop属性,如果设置了,说明此元素是一个放置元素 14 if (node.dataset && node.dataset.drop) { 15 return node; 16 } 17 node = node.parentNode; 18 } 19 return node; 20}
此时会发现,所有经过的放置元素背景色都发生了变化

每次在进入元素时,清除所有的背景色。
1container.ondragenter = (e) => { 2 const dropNode = getDropNode(e.target); 3 // 判断放置元素是否可以接收拖拽元素,当 data-effect 和 data-drop 的值相等时,说明可以 4 if (dropNode && dropNode.dataset.drop === source.dataset.effect) { 5 // 给放置元素添加样式 6 dropNode.classList.add('hover-background'); 7 } 8} 9 10function clearHoverClass() { 11 document.querySelectorAll('.hover-background').forEach(ele => ele.classList.remove("hover-background")); 12}
ondrop:松手放置时 对放置元素的处理:
- 清除hover时的背景样式
- 【新增课程】清除放置元素内的内容;克隆拖拽元素;设置克隆后的元素data-effect属性只为move;恢复拖拽元素本身的样式;添加到放置元素中
- 【移除课程】删除掉拖拽元素
对放置元素的处理:
- 恢复元素样式
1// 对放置元素触发的事件 2container.ondrop = (e) => { 3 // 清除hover时的样式 4 clearHoverClass(); 5 // 获取最近的放置节点 6 const dropNode = getDropNode(e.target); 7 if (dropNode && dropNode.dataset.drop === source.dataset.effect) { 8 // 如果是新增课程 9 if (dropNode.dataset.drop === "copy") { 10 dropNode.innerHTML = ""; 11 const cloned = source.cloneNode(true); 12 cloned.dataset.effect = "move"; 13 cloned.style.opacity = "1"; 14 dropNode.appendChild(cloned); 15 // 移除课程 16 } else { 17 source.remove(); 18 } 19 } 20} 21 22// 对拖拽元素触发的事件 23container.ondragend = (e) => { 24 e.target.style.opacity = "1"; 25}
最后,可以到我的个人网站(www.dengzhanyong.com)的资源中心中下载本地案例的完整源码。
欢迎关注我的公众号,不错过每一篇推送

