前端里的拖拖拽拽
myzbx 2025-01-02 17:55 34 浏览
最近在项目中使用了 react-dnd,一个基于 HTML5 的拖拽库,“拖拽能力”丰富了前端的交互方式,基于拖拽能力,会扩展各种各样的拖拽反馈效果,因此有必要学习了解,最好的学习方式就是实操!
拖拽交互常见于各种前端编辑器里,而“编辑器”是一个集成前端技术能力的综合性工程,其中就会涉及到各种形式的拖拽交互,因为“拖拽”是提升用户体验的重要交互方式,所以需要对拖拽的交互效果做各种定制化,作为开发者理应熟练掌握“拖拽”的应用!
最近在开发一款低代码平台,所以借此机会分享一下关于“拖拽”这一交互的基础知识和实践经验,希望可以给有需要的同学提供一点参考。
一、HTML5 中的拖放
拖(Drag)和放(Drop)是 HTML5 标准的组成部分,了解掌握之后,举一反三,有助于提升我们在拖拽场景下技术方案的设计能力。
1.1 draggable 属性
现代浏览器中,不难发现,图片标签(<img />)是可以被长按拖拽,但如果需要自定义的 DOM 节点可以被拖拽需要配置以告诉浏览器提供对元素(Element / Tag)支持拖拽的能力。
而元素是否允许被拖放且可响应 API 操作依赖于 draggable 全局标签属性
draggable 是一个布尔值类型的标签属性:
- true:元素可被拖拽
- false:元素不可拖拽
当元素设置了 draggable 属性,此时长按就可以自由拖拽了:
1.2 Drag & Drop 事件
HTML 的 drag & drop 使用了“DOM Event”和从“Mouse Event”继承而来的“drag event” 。
一个典型的拖拽操作: 用户选中一个可拖拽的(draggable)元素,并将其拖拽(鼠标按住不放)至一个可放置的(droppable)元素上,然后松开鼠标。
在拖动元素期间,一些与拖放相关的事件会被触发,像 drag 和 dragover 类型的事件会被频繁触发。
除了定义拖拽事件类型,每个事件类型还赋予了对应的事件处理器
事件类型 | 事件处理器 | 触发时机 | 绑定元素 |
dragstart | ondragstart | 当开始拖动一个元素时 | 拖拽 |
drag | ondrag | 当元素被拖动期间按一定频率触发 | 拖拽 |
dragend | ondragend | 当拖动的元素被释放(?松开、按键盘 ESC)时 | 拖拽 |
dragenter | ondragenter | 当拖动元素到一个可释放目标元素时 | 放置 |
dragexit | ondragexit | 当元素变得不再是拖动操作的选中目标时 | 放置 |
dragleave | ondragleave | 当拖动元素离开一个可释放目标元素 | 放置 |
dragover | ondragover | 当元素被拖到一个可释放目标元素上时(100 ms/次) | 放置 |
drop | ondrop | 当拖动元素在可释放目标元素上释放时 | 放置 |
各个事件的时机可以用下面这个图简单表示:
??注意: dragOver 事件的默认行为是:“Reset the current drag operation to "none"”。也就是说,如果不阻止放置元素的 dragOver 事件,则放置元素不会响应“拖动元素”的“放置行为”
// 让绑定该事件的元素支持放置
function handleDragOver(e) {
// 阻止默认的重置行为
// 即可成为拖拽元素的放置区
e.preventDefault();
}
从设计事件标准来看,如果我们需要自行实现拖拽的效果,就需要从这关键的几个事件去思考设计。
1.3 DataTransfer
在上述的事件类型中,不难发现,放置元素和拖动元素分别绑定了自己的事件,可如何将拖拽元素和放置元素建立联系以及传递数据?
这就涉及到 DataTransfer 对象:
DataTransfer 对象用于保存拖动并放下(drag and drop)过程中的数据。它可以保存一项或多项数据,这些数据项可以是一种或者多种数据类型。 —— DataTransfer - MDN
DataTransfer 对象在不同浏览器上因为标准可能不一样使得 API 有差异,但有几个“标准(常用)”属性和方法需要熟悉
在 Chrome 浏览器上的 DataTransfer 实例如下:
(1) 属性
属性 | 说明 |
dropEffect | 获取当前选定的拖放操作类型或者设置的为一个新的类型。值为:none、copy、link、move |
effectAllowed | 提供所有可用的操作类型。值是:none、copy、copyLink、copyMove、link、linkMove、move、all、uninitialized |
files | 包含数据传输中可用的所有本地文件的列表。如果拖动操作不涉及拖动文件,则此属性为空列表 |
items (只读) | 提供一个包含所有拖动数据列表的 DataTransferItemList 对象 |
types (只读) | 提供一个 dragstart 事件中设置的格式的 strings 数组。 |
(2) 方法
属性 | 说明 |
setData(format, value) | 设置给定类型的数据。如果该类型的数据不存在,则将其添加到末尾,以便类型列表中的最后一项将是新的格式。如果该类型的数据已经存在,则在相同位置替换现有数据。 |
getData(format) | 检索给定类型的数据,如果该类型的数据不存在或 data transfer 不包含数据,则返回空字符串 |
clearData([format]) | 删除与给定类型关联的数据。类型参数是可选的。如果类型为空或未指定,则删除与所有类型关联的数据。如果指定类型的数据不存在,或者 data transfer 中不包含任何数据,则该方法不会产生任何效果。 |
`setDragImage(img | element, xOffset, yOffset)` |
在简单的拖拽场景中,其实可以类比 window.localStorage 对象的 setItem() 和 getItem() 方法来理解记忆.
但 getData() 在测试中发现只能在 ondrop 事件中获取到值:
1.4 一个案例掌握拖放 API
<div>
<div class="drag" draggable="true" id="dragger" ondragstart="handleDragStart(event)">拖动元素</div>
<div class="drop" ondrop="handleDrop(event)" ondragover="allowDrop(event)">放置区域</div>
</div>
<script>
function handleDragStart(e) {
e.dataTransfer.setData('DRAG_NODE_ID', e.target.id)
}
function handleDragOver(e) {
e.preventDefault();
}
function handleDrop(e) {
e.preventDefault();
var data = e.dataTransfer.getData('DRAG_NODE_ID');
e.target.appendChild(document.getElementById(data));
}
</script>
演示案例: codepen.io/DYBOY/pen/e…
效果:
1.6 兼容性
是 HTML5 标准提出的能力,因此各大浏览器厂商对于标准的支持有差异,其兼容性参考如下:
相较于传统的通过鼠标事件:mousedown、mousemove、mouseup 组合实现的拖拽要简单很多,少了放入目标边界的判断,也少了对位置的实时获取操作。
另外目前的 API 不算多,例如我们想要定制化拖拽的图片大小、鼠标样式等,目前暂时没发现比较方便的解决方式,但是从另一个角度来说,让我们对于拖拽能力的设计和标准有了一个更深切的认识,对于设计实现拖拽交互有了一个“理论”基础!
二、手搓一个
有了上面的基础知识,那么实现一个列表拖拽排序并不是什么难事。
2.1 设计实现
结合上述的 Drag & Drop 的事件类型,那么拖拽排序主要是针对“拖动对象”之间相互作用关系的逻辑梳理,此处我们暂且区分为:
- 源对象: 拖拽列表中被拖动的单个列表项
- 目标对象: 拖拽列表中和“源对象”产生“相互作用”的列表项
整体的交互事件的设计思路如下:
(1) ondragstart
此时开始拖拽“源对象”的时机,在此事件回调函数中改变“源对象”的样式,设置拖拽的一些传递参数等初始值。
// 源对象开始拖拽
const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
e.dataTransfer.effectAllowed = "move";
setDragId(e.currentTarget.dataset.index); // 从 dataset 获取拖拽项的 id
};
(2) ondragover
正与拖拽中的“源对象”产生相互影响的目标对象,此时“源对象”处于“目标对象”的正上方,目标对象 100ms/次的频率调用“目标对象”的 ondragover 中声明的回调事件。
此时,我们会计算改变“源对象”和“目标对象”的位置。
// 源对象在目标对象上方时
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault(); // 允许放置,阻止默认事件
const dropId = e.currentTarget.dataset.index;
move(dragId, dropId); // 改变原列表数据
};
(3) ondrag
该事件作用于“源对象”,此时正处于拖拽过程中,此时可以改变源对象的 opacity、display(none)、visiblity 样式属性,如果在 dragstart 事件改变,则会导致拖拽拷贝对象丢失。
// 源对象被拖拽过程中
const handleDrag = (e: React.DragEvent<HTMLDivElement>) => {
e.currentTarget.style.opacity = "0";
};
(4) ondragend
在松手完成“源对象”的放置时,主动调用绑定在“源对象”身上的事件,此时恢复更改的样式。
// 源对象被放置完成时
const handleDragEnd = (e: React.DragEvent<HTMLDivElement>) => {
e.currentTarget.style.opacity = "1";
};
2.2 实现效果
2.3 加点动画
上面的实现中效果还算可以,但是少了拖拽项的切换过程动画,直接在 dragover 事件中通过 move(dragId, dropId) 方法直接修改了原列表数据的排序,导致切换突变。
借助 animation 新增 CSS 帧动画:
@keyframes dropUp {
100% {
transform: translateY(5px);
}
}
@keyframes dropDown {
100% {
transform: translateY(-5px);
}
}
.drop-up{
animation: dropUp 0.3s ease-in-out forwards;
}
.drop-down{
animation: dropDown 0.3s ease-in-out forwards;
}
同样的在 dragOver 事件中处理,新增逻辑代码:
// 源对象在目标对象上方时
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
...
// 设置动画
const dropId = e.currentTarget.dataset.index;
const dragIndex = findIndex(listData, (i) => i.id === dragId);
const dropIndex = findIndex(listData, (i) => i.id === dropId);
// 通过增加对应的 CSS class,实现视觉上的动画过渡
e.currentTarget.classList.remove("drop-up", "drop-down");
if (dragIndex < dropIndex) {
e.currentTarget.classList.add("drop-down");
} else if (dragIndex > dropIndex) {
e.currentTarget.classList.add("drop-up");
}
...
};
增加了动画的效果:
看起来似乎好一点了,当然大家可以去扩充动画的效果,亦或者借助三方动画库。
三、已有拖拽库
目前主流的拖拽库有:
- react-dnd: github.com/react-dnd/r…
- react-beautiful-dnd: github.com/atlassian/r…
- sortablejs: sortablejs.github.io/Sortable/
- react-sortable-hoc: github.com/clauderic/r…
关于几者的差异,可以参阅:《关于react中使用拖拽插件的评测》
四、总结
由于低代码平台其实会有丰富的拖拽场景,从可扩展和兼容性上考虑,最终选择了 react-dnd 作为基础拖拽库,当然,在复杂的拖拽场景下,是需要自行扩展该拖拽库,上手难度相对会高一点,不过有了这些“拖拽知识”作为前置基础,那么扩展功能也就不是什么难事了。
- 上一篇:看完就懂的前端拖拽那些事
- 下一篇:使用vue自定义指令构建拖放插件
相关推荐
- 如何设计一个优秀的电子商务产品详情页
-
加入人人都是产品经理【起点学院】产品经理实战训练营,BAT产品总监手把手带你学产品电子商务网站的产品详情页面无疑是设计师和开发人员关注的最重要的网页之一。产品详情页面是客户作出“加入购物车”决定的页面...
- 怎么在JS中使用Ajax进行异步请求?
-
大家好,今天我来分享一项JavaScript的实战技巧,即如何在JS中使用Ajax进行异步请求,让你的网页速度瞬间提升。Ajax是一种在不刷新整个网页的情况下与服务器进行数据交互的技术,可以实现异步加...
- 中小企业如何组建,管理团队_中小企业应当如何开展组织结构设计变革
-
前言写了太多关于产品的东西觉得应该换换口味.从码农到架构师,从前端到平面再到UI、UE,最后走向了产品这条不归路,其实以前一直再给你们讲.产品经理跟项目经理区别没有特别大,两个岗位之间有很...
- 前端监控 SDK 开发分享_前端监控系统 开源
-
一、前言随着前端的发展和被重视,慢慢的行业内对于前端监控系统的重视程度也在增加。这里不对为什么需要监控再做解释。那我们先直接说说需求。对于中小型公司来说,可以直接使用三方的监控,比如自己搭建一套免费的...
- Ajax 会被 fetch 取代吗?Axios 怎么办?
-
大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!今天给大家带来的主题是ajax、fetch...
- 前端面试题《AJAX》_前端面试ajax考点汇总
-
1.什么是ajax?ajax作用是什么?AJAX=异步JavaScript和XML。AJAX是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,AJAX可以使网页实...
- Ajax 详细介绍_ajax
-
1、ajax是什么?asynchronousjavascriptandxml:异步的javascript和xml。ajax是用来改善用户体验的一种技术,其本质是利用浏览器内置的一个特殊的...
- 6款可替代dreamweaver的工具_替代powerdesigner的工具
-
dreamweaver对一个web前端工作者来说,再熟悉不过了,像我07年接触web前端开发就是用的dreamweaver,一直用到现在,身边的朋友有跟我推荐过各种更好用的可替代dreamweaver...
- 我敢保证,全网没有再比这更详细的Java知识点总结了,送你啊
-
接下来你看到的将是全网最详细的Java知识点总结,全文分为三大部分:Java基础、Java框架、Java+云数据小编将为大家仔细讲解每大部分里面的详细知识点,别眨眼,从小白到大佬、零基础到精通,你绝...
- 福斯《死侍》发布新剧照 "小贱贱"韦德被改造前造型曝光
-
时光网讯福斯出品的科幻片《死侍》今天发布新剧照,其中一张是较为罕见的死侍在被改造之前的剧照,其余两张剧照都是死侍在执行任务中的状态。据外媒推测,片方此时发布剧照,预计是为了给不久之后影片发布首款正式预...
- 2021年超详细的java学习路线总结—纯干货分享
-
本文整理了java开发的学习路线和相关的学习资源,非常适合零基础入门java的同学,希望大家在学习的时候,能够节省时间。纯干货,良心推荐!第一阶段:Java基础重点知识点:数据类型、核心语法、面向对象...
- 不用海淘,真黑五来到你身边:亚马逊15件热卖爆款推荐!
-
Fujifilm富士instaxMini8小黄人拍立得相机(黄色/蓝色)扫二维码进入购物页面黑五是入手一个轻巧可爱的拍立得相机的好时机,此款是mini8的小黄人特别版,除了颜色涂装成小黄人...
- 2025 年 Python 爬虫四大前沿技术:从异步到 AI
-
作为互联网大厂的后端Python爬虫开发,你是否也曾遇到过这些痛点:面对海量目标URL,单线程爬虫爬取一周还没完成任务;动态渲染的SPA页面,requests库返回的全是空白代码;好不容易...
- 最贱超级英雄《死侍》来了!_死侍超燃
-
死侍Deadpool(2016)导演:蒂姆·米勒编剧:略特·里斯/保罗·沃尼克主演:瑞恩·雷诺兹/莫蕾娜·巴卡林/吉娜·卡拉诺/艾德·斯克林/T·J·米勒类型:动作/...
- 停止javascript的ajax请求,取消axios请求,取消reactfetch请求
-
一、Ajax原生里可以通过XMLHttpRequest对象上的abort方法来中断ajax。注意abort方法不能阻止向服务器发送请求,只能停止当前ajax请求。停止javascript的ajax请求...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 简介 (30)
- HTML 响应式设计 (31)
- HTML URL 编码 (32)
- HTML Web 服务器 (31)
- HTML 表单属性 (32)
- HTML 音频 (31)
- HTML5 支持 (33)
- HTML API (36)
- HTML 总结 (32)
- HTML 全局属性 (32)
- HTML 事件 (31)
- HTML 画布 (32)
- HTTP 方法 (30)
- 键盘快捷键 (30)
- CSS 语法 (35)
- CSS 轮廓宽度 (31)
- CSS 谷歌字体 (33)
- CSS 链接 (31)
- CSS 定位 (31)
- CSS 图片库 (32)
- CSS 图像精灵 (31)
- SVG 文本 (32)
- 时钟启动 (33)
- HTML 游戏 (34)
- JS Loop For (32)
