摘要:如其他属性及方法,详细可以查看跨终端能力跨终端能力是最大的特点。在指定区域的事件中,通过对象的属性,即可获得文件列表信息,如打印文件名在中实践在项目中使用,依然遵循数据驱动的原则,即事件数据更新。同时,在事件中执行判断。
最近有个需求,需要产品导航栏支持拖放。
虽然开源社区已有不少成熟的拖放库,但考虑到代码可控性和可定制性,还是自己写吧。
关于选型,前端实现拖放功能,无外乎几种:
1、通过样式布局+鼠标事件,采用此方案的插件如:@shopify/draggable
2、Canvas绘制,插件如:konva
3、Drag&Drop接口,插件如:dragula
经过一番研究,最终选择了原生Drag&Drop的方案,原因如下:
1、原生拖放事件,顺应JS语言发展趋势;
2、兼容性符合项目要求;
3、在Can I use...中有如下描述:
事件
一个拖放行为,自然牵涉到两部分元素,即拖动元素和释放区域元素。
与之相关的事件总共有8个,其中绑定在拖动元素的事件有三个:drag、dragstart、dragend;
剩下5个事件绑定在释放区域元素上:dragenter、dragover、dragleave、dragexit、drop。
具体定义可以参考mdn
浏览器中,有三种元素,默认是可以被拖动的,它们是:
1、被选中后的文本;
2、图片;
3、链接
其他元素要转成可拖动元素,必须添加draggable="true",如:
"true">div>
注意:这里不能略写,如写成:
div>
是无效的。
定义可被释放区域
要使一块元素可被释放,首先需要绑定dragenter或dragover事件,然后阻止事件,如下:
"return false">
<div ondragover="event.preventDefault()">
因为,这两个事件的默认行为就是“不触发”drop事件,所以要定义成可被释放区域,就反其道而行之即可。
DataTransfer对象
一个完整的拖放操作,除了拖动一个元素,在指定区域释放之外,还有最重要的一步,就是将元素携带的信息在被释放区域中展示。
比如,拖放一张图片,本质上就是获取到被拖动的图片src属性值,并在释放时,在释放区域展示一张相同src的图片。
而这个信息,就存储在DataTransfer对象中。
对于非默认可拖放元素来说,其包含的信息需要在dragstart事件中设置,使用DataTransfer.setData(),如:
dragItem.ondragstart = e => {
e.dataTransfer.setData("text/plain", "drag info");
}
如果希望拖动时,展示自定义的图片,还可以调用dataTransfer.setDragImage,如:
dragItem1.ondragstart = e => {
const img = new Image();
img.src = "img_url.jpg";
e.dataTransfer.setDragImage(img, 0, 0);
}
在drop事件中,可以取得拖放元素的信息,并将指定信息通过dom操作,展示在特定区域,如:
dropArea.ondrop = e => {
e.preventDefault();
const data = event.dataTransfer.getData("text/plain");
const div = document.createElement("div");
div.textContent = data;
e.target.appendChild(div);
}
在DataTransfer对象还有一对属性,用来确保释放区域只能释放特定类型的拖拽元素,即dropEffect和effectAllowed。
effectAllowed只能在dragstart事件中设置,在dragenter或dragover事件中,需要设置dropEffect的值与effectAllowed一致,才能触发drop事件。如:
dragItem.ondragstart = e => {
e.dataTransfer.effectAllowed = "move";
}
dropArea.ondragover = e => {
e.preventDefault();
e.dataTransfer.dropEffect = "move";
}
其他属性及方法,详细可以查看mdn
跨终端能力
跨终端能力是drag&drop最大的特点。
最常见的跨终端需求,就是从用户的本地拖放文件到浏览器中指定区域实现上传功能。
在指定区域的drop事件中,通过DataTransfer对象的files属性,即可获得文件列表信息,如:
dropArea.ondrop = e => {
e.preventDefault();
const files = e.dataTransfer.files;
if (files.length) {
Array.prototype.forEach.call(files, f => {
console.log(f.name); //打印文件名
});
}
}
在React中实践
在React项目中使用drag&drop,依然遵循React数据驱动的原则,即事件->数据->DOM更新。
所以,像之前提到的,通过DataTransfer对象传递数据的方式,在React项目中,可以改为操作组件对象属性,保证数据流的清晰。
但除此之外,在实际实践中,还是遇到了一些问题,需要特殊处理。具体如下:
1、必须保留dataTransfer.setData
起初,为保证数据流清晰,在React组件中,绑定onDragStart,仅负责监听事件,数据的变动和传递全部修改组件属性,但是会遇到Firefox浏览器无法拖放的兼容问题。经查发现,在Firefox中,可拖放元素必须满足:
1、添加draggable="true";
2、绑定事件dragstart;
3、在dragstart中,dataTransfer.setData设置数据
所以,即使e.dataTransfer.setData("text", "");设置空字符串,也必须添加上这一条。
2、防止跨终端拖拽或不合法拖拽
drop&drag跨终端能力有时也会成为干扰。在项目中,会发现,如果没有做判断,同一个页面同时打开两个浏览器tab,其拖放元素可以跨tab拖动,可能会造成意外BUG。为此,需要增加判断。
一种方式,在组件实例构建时,生成一个随机字符,借助dataTransfer.setData,为拖放元素打上标记。同时,在drop事件中执行判断。
当然,如果拖放元素和释放区域分属不同组件,则需要在他们的父组件中,生成随机字符,以props形式,传递到两个子组件。
3、防止Firefox自动打开新页面
在上述提到的为拖放元素打标签中,起初采用的是这样的写法:
e.dataTransfer.setData("text", uniqDataTransferTag);
结果在Firefox中,每次drop事件触发时,浏览器会自动打开新tab并搜索uniqDataTransferTag(随机字符)。
根据官方解释,需要在drop事件中调用e.preventDefault(),同时阻止冒泡e.stopPropagation(),但经过尝试,依然不生效。初步判断,可能与React的SyntheticEvent机制有关。于是只好曲线救国,改为设置自定义的MIME type,如:
e.dataTransfer.setData("ucloud_drag_tag", uniqDataTransferTag);
4、节流与避免event被回收
在项目中,需要在onDragOver中,判断被拖放元素当前位置,并执行DOM操作。
根据定义,dragover事件会在被拖放元素拖到释放区域上时,每几百毫秒触发一次,显然不做任何处理会非常影响性能。这里,自然想到采用节流throttle方式优化。
由于节流是异步操作,而根据React的SyntheticEvent,event对象会在当前事件循环结束后移除,除非调用e.persist(),才能在异步操作中访问到。
5、HACK拖放元素拖动过程中,实现“被拖走”的视觉效果
根据设计师要求,项目中希望实现元素拖动开始后要被拖走,如下图:
但默认的拖放效果,其实是这样:
很可惜,官方并没有提供对被拖放元素拖动开始后设置效果的接口。经过尝试,找到一个通过样式HACK方法,如下:
1、新增一个css class,包含样式:
transform: translateX(-9999px);
2、对被拖放元素添加样式:
transition: transform 0.1s;
3。在拖动开始后,添加上述第一步的css class。
6、实现长按元素激活拖放效果
根据交互设计,需要实现长按元素一定时长后才可以触发拖拽。
起初,采用的方案是,绑定鼠标事件mousedown,触发setTimeout,达到固定时长后触发state更新,改变拖放元素的draggable值。但实际测试中发现,这种方法存在一定的失败率,即明明已经达到了长按的时长,依然不能拖放。而且,在Firefox中这个问题更加明显。
推测,可能是draggable的更新偶尔会晚于dragstart事件,导致拖放失败。
于是转变思路,增设组件的属性作为判断标志,在mousedown事件中更新判断标志,而draggable始终设为true。如下:
// mousedown事件处理函数
handleLongPress = e => {
this.resetDragTimer(); // 清除定时器
return (this.triggerDragTimer = setTimeout(() => {
this.isMenuDraggable = true; // 判断标志
}, this.triggerDragInterval));
};
// dragstart事件处理函数
handleDragStart = e => {
if (!this.isMenuDraggable) {
e.preventDefault();
} else {
...
}
};
总结
Drag&Drop作为原生拖放API,可以用最少代码实现拖放,看似“简单”,实际并非如此。在实践中,还是需要对官方接口定义,以及各浏览器差异有足够了解,才能避免各种未知错误。而在React这类数据驱动的框架中运用时,如何处理事件监听,同时又不打乱组件的数据流,还是需要好好设计一番。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/7280.html
相关文章
-
使用 Drag and Drop 给Web应用提升交互体验
摘要:注意点在鼠标操作拖放期间,有一些事件可能触发多次,比如和。可拖拽元素,建议使用,设定可拖拽元素的鼠标游标,提升交互。在中使用拖拽中使用可以直接绑定到组件上。
什么是 Drag and Drop (拖放)?
简单来说,HTML5 提供了 Drag and Drop API,允许用户用鼠标选中一个可拖动元素,移动鼠标拖放到一个可放置到元素的过程。
我相信每个人都或多或少接触过拖放,比如浏览...
-
React-sortable-hoc 结合 hook 实现 Draggin 和 Droppin
摘要:启动项目教程最终的目的是构建一个带有趣的应用程序来自,可以在视口周围拖动。创建组件,添加样式和数据为简单起见,我们将在文件中编写所有样式。可以看出,就是在当前的外层包裹我们所需要实现的功能。现在已经知道如何在项目中实现拖放
翻译:https://css-tricks.com/draggi...
React 社区提供了许多的库来实现拖放的功能,例如 react-dnd, react-b...
-
HTML5拖放API Drag and Drop
摘要:此文研究中的拖放接口,提供各个属性和方法的说明,解决拖放过程中的拖拽数据对象存储和获取问题。方法增加一个拖拽数据对象到属性中,并返回增加的拖拽数据对象。若拖拽数据对象是文本字符串类型,通过回调函数获取拖拽数据中的字符串数据。
此文研究Web API中的拖放接口,提供各个属性和方法的说明,解决拖放过程中的拖拽数据对象存储和获取问题。
拖放API作用到两个目标对象,分别是拖拽目标对象和放置...
发表评论
0条评论
lcodecorex
男|高级讲师
TA的文章
阅读更多
盘点前端开发中那些用得少却很实用的功能
阅读 480·2019-08-30 15:44
重学前端学习笔记(十九)--JavaScript中的函数
阅读 894·2019-08-30 10:55
html+js(swiper.js)+css左右滑动切换页面效果,适配移动端
阅读 2726·2019-08-29 15:16
PostCSS自学笔记(二)【插件篇】
阅读 922·2019-08-29 13:17
Javascript基础之-this
阅读 2799·2019-08-26 13:27
[译] 关于 Angular 动态组件你需要知道的
阅读 567·2019-08-26 11:53
【全栈之路】JAVA基础课程十一_JDK8十大新特性(20190706v1.2)
阅读 2116·2019-08-23 18:31
jQuery之模拟实现$().animate()(上)
阅读 1881·2019-08-23 18:23