Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HTML 元素拖拽 #69

Open
jtwang7 opened this issue Aug 14, 2022 · 0 comments
Open

HTML 元素拖拽 #69

jtwang7 opened this issue Aug 14, 2022 · 0 comments

Comments

@jtwang7
Copy link
Owner

jtwang7 commented Aug 14, 2022

HTML 拖拽

引用:HTML Drag & Drop API 指南

🌈 实现拖拽

实现拖拽需要定义拖拽元素和放置区域:

  • 元素要支持可拖拽,需要做三件事:
    • 设置 draggable = true
    • 添加 dragstart 事件监听
    • 在监听事件中设置拖拽数据
  • 另一个元素要支持可放置:
    • 监听 dragover 事件,事件中调用 preventDefault() 阻止默认事件,即可将该元素设置为可放置区域
    • 监听 drop 事件,处理对应的逻辑

可拖拽元素定义好自己的拖拽数据

可放置元素定义要接受的数据和对应的处理逻辑

这样两个模块就是独立的模块,不会相互耦合。

🌈 API

❇️ draggable 属性

当我们想让元素变成可拖拽时,我们就需要设置 draggable 属性。

  • auto: 拖拽行为为浏览器默认行为,只有选中的文字,链接,图片可以拖动。【默认】
  • true: 设置元素为可拖动。
  • false: 设置元素为不可拖动

❇️ 拖拽事件

拖放过程的各个事件和触发时刻如下:

事件 触发时刻
dragstart 当用户开始拖拽一个元素或选中的文本时触发
drag 当拖拽元素或选中的文本时触发
dragend 当拖拽操作结束时触发 (比如松开鼠标按键或敲“Esc”键)
dragenter 当拖拽元素或选中的文本到一个可放置的目标时触发
dragover 当元素或选中的文本被拖到一个可放置的目标上时触发(每100毫秒触发一次)
drop 当元素或选中的文本在可放置的目标上被释放时触发
dragleave 当拖拽元素或选中的文本离开一个可放置的目标时触发。
dragexit 和dragleave类似,但是兼容性不好,建议不要使用。说明

事件分类:

  • 可拖拽元素:dragstartdragdragend
  • 可放置的元素:dragenterdragoverdropdragleave

拖拽事件的 event 对象 dragEvent 继承 mouseEventdragEvent 有个属性 dataTransferdataTransfer 属性是一个 DataTransfer 对象。

接下来介绍这几个事件常用的方式:

  • dragstart: 设置拖拽数据,调整拖拽元素样式等
  • drag:拖拽的移动跟踪
  • dragend:判断拖拽操作是否成功(即是否在放置区释放,通过 dropEffect 判断),恢复拖拽元素样式等
  • dragenter:设置样式,设置dropEffect等等
  • dragover:设置为可放置区,设置dropEffect等等
  • drop:处理放置事件
  • dragexit:恢复样式
<div id="drop-area"></div>
<div id="drag-el" draggable="true">...</div>
<div>放置结果:<span id="result"></span></div>
复制代码
window.onload = () => {
  const carEl = document.querySelector('#drag-el')
  const dropArea = document.querySelector('#drop-area')
  const result = document.querySelector('#result')

  carEl.addEventListener('dragstart', (event) => {
    event.dataTransfer.setData('text/plain', event.target.id)
    event.dataTransfer.effectAllowed = 'move'
    event.target.style.opacity = 0.5
  })
  carEl.addEventListener('drag', (event) => {
    console.log(event.clientX, event.clientY)
  })
  carEl.addEventListener('dragend', (event) => {
    event.target.style.opacity = 1
    const isDrop = event.dataTransfer.dropEffect !== 'none'
    result.innerText = isDrop ? '成功' : '未放置'
  })
  dropArea.addEventListener('dragenter', (event) => {
    event.dataTransfer.dropEffect = 'move'
    event.target.style.borderStyle = 'dashed'
  })
  dropArea.addEventListener('dragover', (event) => {
    event.dataTransfer.dropEffect = 'move'
    event.preventDefault()
  })
  dropArea.addEventListener('drop', (event) => {
    const id = event.dataTransfer.getData('text/plain')
    dropArea.appendChild(document.getElementById(id))
    event.target.style.borderStyle = 'solid'
  })
  dropArea.addEventListener('dragleave', (event) => {
    event.target.style.borderStyle = 'solid'
  })
}
复制代码

上面这个例子中就使用到了各个事件,每个事件都有对应的用法。

❇️ 拖拽数据对象

拖拽数据对象涉及到三个类:DataTransfer, DataTransferItemList, DataTransferItem

DataTransfer

DataTransfer 对象用于保存在拖放操作期间拖动的数据,同时还可以设置拖拽样式,读取拖拽文件等等。它可以包含一个或多个数据项,每个数据项包含一个或多个数据类型。

所有拖拽事件中我们都可以通过 event.dataTransfer访问到它。

属性:

  • dropEffect :当前选定的拖放操作类型,一般在可放置元素的相关事件中设置
  • effectAllowed :提供可用的操作类型,一般在拖拽元素的相关事件中设置
  • files :包含数据传输中可用的所有本地文件的列表。如果拖动操作不涉及拖动文件,则此属性为空列表
  • items :提供一个包含所有拖动数据列表的 DataTransferItemList 对象 ,只读属性。
  • types :所有数据项类型的数组

方法:

  • clearData() :删除与给定类型关联的数据。类型参数是可选的。如果类型为空或未指定,则删除与所有类型关联的数据
  • setData() :设置给定类型的数据。如果该类型的数据不存在,则将其添加到末尾,类型列表中的最后一项将是新的类型。如果该类型的数据已经存在,则在相同位置替换现有数据
  • getData() :获取给定类型的数据,如果该类型的数据不存在或 dataTransfer不包含数据,则返回空字符串
  • setDragImage() :用于设置自定义的拖动图像。

接下来我们将详细的介绍各个属性和方法。

dropEffecteffectAllowed

dropEffect 用于表示放置区接受什么行为的拖放,一般在 dragenter 和 dragover 中设置;对应的 effectAllowed 表示这次拖拽的行为是什么行为,要在 dragstart 中设置。

effectAllowed 支持的值:

  • none: 所有拖拽行为都允许
  • copy: 支持复制行为
  • move: 支持移动行为
  • link: 支持链接关联行为
  • copyMove: 支持 copy 和 move
  • copyLink: 支持 copy 和 link
  • linkMove: 支持 link 和 move
  • all: 支持 copy, move 和 link
  • uninitialized: 未设置值,默认和 all 效果一样

dropEffect 支持的值:

  • none: 不允许放置
  • copy: 支持复制行为
  • move: 支持移动行为
  • link: 支持链接关联行为

这两个属性的作用有俩个:

  1. 鼠标样式会根据设置值展示,例如设置的是 move,在元素放置到拖拽区时,鼠标光标旁有个方形的样式
  2. dropEffect 的值必须在 effectAllowed 范围中才可以支持放置,不然拖拽元素是无法在放置区中触发 drop 事件。
files

这个属性就如定义一样,如果拖拽的文件,这个数组就对应的就有 File 对象的数组项

items

这个属性保存了所有拖拽数据,我们在下面 DataTransferItemList 中介绍它。

types

这个数组保存了当前拖拽数据所有的数据类型值,我们可以看下默认图片拖拽时,这个数组的值情况:

可以看到图片拖拽的时候拖拽数据项有三个,它们的类型分别是 text/uri-list, text/html, Files。

这边我们顺便介绍下拖拽数据项的常见的类型值:text/plain, text/uri-list, text/html, Files,除了这些值,你自定义格类型值,如 application/x.bookmark。

clearData
DataTransfer.clearData([format]);

这个方法就是清除拖拽数据项的方法,不传参数清除所有数据,有传就清除对应类型的数据。

setData
void dataTransfer.setData(format, data);

设置拖拽数据的方法,format 可以使用一些通用的值,如 text/plain,或者自定义值。data 为字符串,如果你数据是对象的话,需要序列化处理下。

同时设置数据默认会加到 dataTransfer.items 数组最后位置,但是如果数据的类型已存在,就会更新之前那条数据项的值。

getData
DOMString dataTransfer.getData(format);

获取拖拽项数据的方法,传入数据类型获取。

setDragImage
void dataTransfer.setDragImage(img, xOffset, yOffset);

拖拽元素,浏览器默认会有一个图像。通过此方法可以自定义拖拽图像。

DataTransferItemList

一个 DataTransferItem 数组,DataTransferItem 代表的是一个拖拽数据项。event.dataTransfer.items 即是此类型

属性:

  • length: 数组长度

方法:

  • add(data, type) 添加一个拖拽数据项,这个方法和 dataTransfer 的 getData 类似,不过在添加相同的类型数据的时候,这个方法会返回一个错误
  • remove(index) 移除一个数据项
  • clear() 清除数据项

DataTransferItem

拖拽时是数据项,event.dataTransfer.items 中的每一项即是此类型

属性:

  • kind: 指明数据是文件还是字符串,string | file
  • type: 数据的类型

方法:

  • getAsFile(callback) 返回 File,拖拽不是文件就返回 null
  • getAsFileSystemHandle(callback) 返回文件或者文件夹 handle,拖拽不是文件就返回 null
  • getAsString(callback) 返回字符串

注意 DataTransferItem 获取数据方法是异步的,dataTransfer 的 getData 的获取数据是同步的。通常我们直接是使用 dataTransfer 的方法即可。

❇️ 兼容性

拖拽 API 主要还是在 PC 上使用,大部分移动设备都不支持。PC 端除了 ie 兼容性有些问题,其他浏览器兼容性良好。

🌈 总结

HTML 拖拽知识点中比较关键的有以下几点:

  • 如何让元素支持拖拽
  • 如何让元素变成可放置区
  • 拖拽事件
  • 拖拽中的数据:dataTransfer 对象

掌握以上几个知识点,就能很好完成拖拽相关功能的编写。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant