drag事件详解:html5鼠标拖动排序及resize实现方案分析及实践
Author:[email protected] Date:
对列表进行拖动排序,尺寸改变。之前一般会使用jQuery-UI(interactjs更加纯粹)。其通过mousedown、mousemove、mouseup这三个事件来实现页面元素被鼠标拖拽的效果。vue-drag-resize vuedraggable等包也大抵如此:https://codepen.io/lujun-zhou/pen/LYybXNx。
而HTML5中直接提供了拖放的API,只要通过监听元素的拖放事件就能实现各种拖放功能。
拖放API阐释
拖放(Drag和 drop)是 HTML5 标准的组成部分。相比之前用jquery-UI等库实现,更加方便(省去计坐标计算等)。
为了使元素可拖动,必须把 draggable 属性设置为 true :
<div id=test draggable=true>test</div>
draggable属性:设置元素是否可拖动。
true: 可以拖动
false: 禁止拖动
auto: 跟随浏览器定义是否可以拖动
在HTML5标准中 文本、图片和链接是默认可以拖放的,它们的draggable属性自动被设置成了true。
图片和链接按住鼠标左键选中,就可以拖放。
文本只有在被选中的情况下才能拖放。如果显示设置文本的draggable属性为true,按住鼠标左键也可以直接拖放。
拖放事件事件详情
一个元素被拖放,他可能会经过很多个元素上,最终到达想要放置的元素内。这里,我暂时把被拖放的元素称为源对象,被经过的元素称为过程对象,到达的元素称为目标对象。不同的对象产生不同的拖放事件。
源对象事件:
dragstart:源对象开始拖放,开始移动时事件触发
drag:源对象拖放过程中,移动被拖拽对象时触发
dragend:源对象拖放结束,整个拖放操作结束时触发。
过程对象事件:
dragenter:源对象进入过程对象范围内,被拖拽对象进入过程对象时被触发
dragover:源对象在过程对象范围内移动,被拖拽对象在过程对象内移动时触发
dragleave:源对象离开过程对象的范围,被拖拽对象离开目标对象时触发
拖动事件列表
每一个可拖动的元素,在拖动过程中,都会经历三个过程,拖动开始-->拖动过程中--> 拖动结束
在拖动目标上触发事件 (源元素-被拖动的元素):
ondragstart - 在元素开始被拖动时候触发——拖动什么
ondrag - 在元素被拖动时反复触发
ondragend - 在拖动操作完成时触发
释放目标时触发的事件(目的地对象):
ondragenter - 当被鼠标拖动的对象进入其容器范围内时触发此事件
ondragover - 当被拖动元素在目的地元素内时触发——放到何处
ondragleave - 当被拖动元素没有放下就离开目的地元素时触发
ondrop - 在一个拖动过程中,释放鼠标键时触发此事件——进行放置
针对对象 | 事件名称 | 说明 |
---|---|---|
被拖动的元素 | dragstart | 在元素开始被拖动时候触发 |
drag | 在元素被拖动时反复触发 | |
dragend | 在拖动操作完成时触发 | |
目的地对象 | dragenter | 当被拖动元素进入目的地元素所占据的屏幕空间时触发 |
dragover | 当被拖动元素在目的地元素内时触发 | |
dragleave | 当被拖动元素没有放下就离开目的地元素时触发 |
整个拖拽事件触发的顺序如下:dragstart-> drag -> dragenter -> dragover -> dragleave -> drop ->dragend
目标对象事件:
drop:源对象拖放到目标对象中,目标对象完全接受被拖拽对象时触发,可理解为在目标对象内松手时触发。
dragenter和dragover事件的默认行为是拒绝接受任何被拖放的元素。因此,我们必须阻止浏览器这种默认行为。e.preventDefault();
如果drop接收盒子要想接收到元素,那么接收的拖动元素 dragenter和dragover必须阻止默认行为。
el.ondragover = function(e){ e.preventDefault(); } el.ondrop = function(e){ e.preventDefault(); }
在vue项目中:
@dragstart.native="dragStart(index,$event)" @dragenter.native.prevent="dragenter(index,$event)" @dragleave.native.prevent @dragover.native.prevent @dragend.native="dragend(index,$event)" @drop.native.prevent="drop(index,$event)"
这样才会触发drop
dataTransfer对象
在所有的拖放事件中都提供了一个数据传输对象dataTransfer,主要是用于在源对象和目标对象之间传递数据。
与拖放操作所触发的事件同时派发的对象是DragEvent,它派生于MouseEvent,具有Event与MouseEvent对象的所有功能,并增加了dataTransfer属性。该属性用于保存拖放的数据和交互信息,返回DataTransfer对象。
DataTransfer对象定义的属性和方法有很多种,我们看下列入标准的几个。
属性 | 说明 |
---|---|
types | 只读属性。它返回一个我们在dragstart事件中设置的拖动数据格式的数组。 格式顺序与拖动操作中包含的数据顺序相同。 |
files | 返回拖动操作中的文件列表。包含一个在数据传输上所有可用的本地文件列表。 如果拖动操作不涉及拖动文件,此属性是一个空列表。 |
dropEffect | 获取当前选定的拖放操作的类型或将操作设置为新类型。它应该始终设置成effectAllowed的可能值之一【none、move、copy、link】。dragover事件处理程序中针对放置目标来设置dropEffect。 |
effectAllowed | 指定拖放操作所允许的效果。必须是其中之一【 none, copy, copyLink, copyMove, link, linkMove, move, all, uninitialized】默认为uninitialized 表示允许所有的效果。ondragstart处理程序中设置effectAllowed属性 |
方法 | 说明 |
---|---|
void setData(format, data) | 将拖动操作的拖动数据设置为指定的数据和类型。format可以是MIME类型 |
String getData(format) | 返回指定格式的数据,format与setData()中一致 |
void clearData([format]) | 删除给定类型的拖动操作的数据。如果给定类型的数据不存在,此方法不执行任何操作。如果不给定参数,则删除所有类型的数据。 |
void setDragImage(img, xOffset, yOffset) | 指定一副图像,当拖动发生时,显示在光标下方。大多数情况下不用设置,因为被拖动的节点被创建成默认图片。x,y参数分别指示图像的水平、垂直偏移量 |
dataTransfer方法
setData(format, data)
设置拖拽事件中要传递的数据,format的参数为数据类型,data要存入的数据。例如:event.dataTransfer.setData('text/plain', 'hello world')
存入的数据类型,共有4种:
text/plain
text/html
text/xml
text/uri-list
注:如果给定类型的数据不存在,则将其添加到拖动数据存储的末尾,使得dataTransfer.types列表中的最后一个项目将是新类型。
getData(format)
该方法从dataTransfer对象中读取数据,参数为在setData方法中指定的数据类型,例如:event.dataTransfer.getData('text/plain')
clearData()
该方法清空dataTransfer对象中存储的数据,参数可选,为数据类型。若为空,则清空所有数据。
setDragImage(element,x,y)
该方法通过img元素来设置拖放图标
element表示拖拽时鼠标下面的图片(通常是image元素,也可以说canvas元素)
x、y分别指示相对于图片的横向和纵向偏移量,相对应鼠标指针。
从拖动目标(dragstart事件触发的元素)生成半透明图像,并在拖动过程中跟随鼠标指针。这个图片是自动创建的,你不需要自己去创建它。然而,如果想要设置为自定义图像,那么 DataTransfer.setDragImage() 方法就能派上用场。
图像通常是一个 <image> 元素,但也可以是<canvas> 或任何其他图像元素。
这里面需要特别注意,element元素要在页面内是被渲染的。
具体可以查看:https://www.kryogenix.org/code/browser/custom-drag-image.html
这个文章已经讲的非常清楚了。
setData注意事项:
dataTransfer.getData()在dragover,dragenter,dragleave中无法获取数据的问题
dataTransfer.setData()中所设置的数据是存储在drag data store中,而根据W3C标准,drag data store有三种模式,Read/write mode, Read-only mode跟Protected mode。
W3C Working Draft中5.7.2.关于三种drag data store mode的定义
A drag data store mode, which is one of the following:
Read/write mode(读/写模式)
For the dragstart event. New data can be added to the drag data store.
读/写模式,在dragstart事件中使用,可以添加新数据到drag data store中。
Read-only mode(只读模式)
For the drop event. The list of items representing dragged data can be read, including the data. No new data can be added.
在drop事件中使用,可以读取被拖拽数据,不可添加新数据。
Protected mode(保护模式)
For all other events. The formats and kinds in the drag data store list of items representing dragged data can be enumerated, but the data itself is unavailable and no new data can be added.
在所有其他的事件中使用,数据的列表可以被枚举,但是数据本身不可用且不能添加新数据。
具体查看官方文档:https://html.spec.whatwg.org/multipage/dnd.html#drag-data-store
这样就可以解释为什么dragover中dataTransfer.getData()返回的数据为空,以及在dragover时dataTransfer中的types不为0了,因为在除了dragstart,drop以外的事件,包括dragover,dragenter,dragleave中,drag data store出于安全原因处于保护模式,因此不可访问。
如果要实现dragover中访问dragstart中设置的数据,可以采用定义一个全局变量的方法,在dragstart中赋值,之后在dragend中清空。
另外,我在ondragover时,尝试给被拖拽元素添加class以改变其样式发现,虽然拖拽时class已经改变,但在拖拽过程中样式并没有改变,而是等到拖拽动作完成后,也就是drop之后样式才被应用上去,所以在dragover,dragenter,dragleave中做得更多的应该是对数据的处理,而不是应用效果。
dataTransfer属性
dropEffect 和 effectAllowed属性
给指定拖放操作所允许的一个效果,例如:dataTransfer.effectAllowed = "move"。
effectAllowed 用来指定当元素被拖放式所允许的视觉效果(作用于被拖放元素)。如果effectAllowed属性是定为none,则不允许拖放元素
dropEffect 表示拖放操作的视觉效果(作用于目标元素),如果dropEffect 属性设定为none,则不允许被拖放到目标元素中。
应该在dragstart事件中设置此属性,以便为拖动源设置所需的拖动效果。在 dragenter 和dragover 事件处理程序中,该属性将设置为在dragstart 事件期间分配的任何值,因此,可以使用effectAllowed来确定允许哪个效果。其值如下:
effectAllowed值:
uninitialized:没有该被拖动元素放置行为。
none:被拖动的元素不能有任何行为。
copy:只允许值为“copy”的dropEffect。
link:只允许值为“link”的dropEffect。
move:只允许值为“move”的dropEffect。
copyLink:允许值为“copy”和“link”的dropEffect。
copyMove:允许值为“copy”和”link”的dropEffect。
linkMove:允许职位“link”和”move”的dropEffect。
all:允许任意dropEffect。
dropEffect值:
none:不能把拖动的元素放在这里。这是除文本框之外所有元素的默认值。
move:应该把拖动的元素移动到放置目标
copy:应该把拖动的元素复制到放置目标
link:表示放置目标会打开拖动的元素(但拖动的元素必须是一个链接,有URL)
chrome 默认是显示一个绿色的加号,设置:$event.dataTransfer.effectAllowed = 'move'。则恢复成默认鼠标样式。
files属性
返回被拖拽的文件列表,是一个FileList对象,有length属性,可通过下标访问。此功能可用于将文件从用户桌面拖动到浏览器。一般配合FileReader来处理文件。如FileReader.readAsDataURL与FileReader.onload
拖动元素排序实现
之前写了两篇文章,有读者留言希望看代码,这次大致写了下
https://codepen.io/lujun-zhou/pen/KKmNgxv
vue实现
https://codepen.io/lujun-zhou/pen/zYwozMZ
magicBox tab 排序也是,就是采用这个方案。
画布元素位置与尺寸调整
可以先回顾下《再谈BOM和DOM(6):dom对象及event对象位值计算—如offsetX/Top,clentX》
如果使用mouse事件控制,就显得非常复杂。这种一般都是使用第三方库实现,如 interact.js 、vue-drag-resize等。
如果直接使用 css resize,然后通过 resizeObserve 回调尺寸改变,实现起来就变得简单的多。
这种思路之前也介绍过,如《懒加载优化:JavaScript IntersectionObserver API监听元素是否可见》。
这里的大致实现代码:
https://codepen.io/lujun-zhou/pen/jOmVLGy
之前做过一版类似DataV的需求,就是直接用html5 原生属性写的。
canvas 画布实现:https://codepen.io/atindo23/pen/OJLbdrJ
鼠标缩放元素操作
缩放事件,实现不肖多说,MDN案列写的够好了
https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onwheel
参考文章:
HTML5--拖放事件与dataTransfer对象 https://blog.csdn.net/hjc256/article/details/89021483
说说 HTML 中的dropEffect 和 effectAllowed https://blog.csdn.net/gggg5643/article/details/52135824
HTML5 进阶系列:拖放 API 实现拖放排序 - 林鑫的文章 - 知乎 https://zhuanlan.zhihu.com/p/26666141
HTML5前端技术教程:H5拖放 - 方伟景的文章 - 知乎 https://zhuanlan.zhihu.com/p/47458212
HTML5原生拖拽/拖放(drag & drop)详解 https://www.cnblogs.com/weiqinl/p/7886049.html
HTML5 拖放(Drag 和 Drop)详解与实例 https://www.cnblogs.com/moqiutao/p/6365113.html
dataTransfer.getData()在dragover,dragenter,dragleave中无法获取数据的问题 https://blog.csdn.net/azureternite/article/details/51415359
转载本站文章《drag事件详解:html5鼠标拖动排序及resize实现方案分析及实践》,
请注明出处:https://www.zhoulujun.cn/html/webfront/SGML/html5/2016_0124_434.html