言
HTML5 中提供了直接拖放的 API,极大的方便我们实现拖放效果,不需要去写一大堆的 js,只需要通过监听元素的拖放事件就能实现各种拖放功能。
想要拖放某个元素,必须设置该元素的 draggable 属性为 true,当该属性为 false 时,将不允许拖放。而 img 元素和 a 元素都默认设置了 draggable 属性为 true,可直接拖放,如果不想拖放这两个元素,把属性设为 false 即可。
拖放事件
拖放事件由不同的元素产生。一个元素被拖放,他可能会经过很多个元素上,最终到达想要放置的元素内。这里,我暂时把被拖放的元素称为源对象,被经过的元素称为过程对象,到达的元素我称为目标对象。不同的对象产生不同的拖放事件。
源对象:
过程对象:
目标对象:
<div id="source" draggable="true">a元素</div> <div id="process">b元素</div> <div id="target">c元素</div> <script> var source = document.getElementById('source'), // a元素 process = document.getElementById('process'), // b元素 target = document.getElementById('target'); // c元素 source.addEventListener('dragstart',function(ev){ // dragstart事件由a元素产生 console.log('a元素开始被拖动'); },false) process.addEventListener('dragenter',function(ev){ // dragenter事件由b元素产生 console.log('a元素开始进入b元素'); },false) process.addEventListener('dragleave',function(ev){ // dragleave事件由b元素产生 console.log('a元素离开b元素'); },false) target.addEventListener('drop',function(ev){ // drop事件由c元素产生 console.log('a元素拖放到c元素了'); ev.preventDefault(); },false) document.ondragover = function(e){e.preventDefault();} </script>
dataTransfer 对象
在所有拖放事件中提供了一个数据传递对象 dataTransfer,用于在源对象和目标对象间传递数据。接下来认识一下这个对象的方法和属性,来了解它是如何传递数据的。
setData()
该方法向 dataTransfer 对象中存入数据。接收两个参数,第一个表示要存入数据种类的字符串,现在支持有以下几种:
第二个参数为要存入的数据。例如:
event.dataTransfer.setData('text/plain','Hello World');
getData()
该方法从 dataTransfer 对象中读取数据。参数为在 setData 中指定的数据种类。例如:
event.dataTransfer.getData('text/plain');
clearData()
该方法清除 dataTransfer 对象中存放的数据。参数可选,为数据种类。若参数为空,则清空所有种类的数据。例如:
event.dataTransfer.clearData();
setDragImage()
该方法通过用img元素来设置拖放图标。接收三个参数,第一个为图标元素,第二个为图标元素离鼠标指针的X轴位移量,第三个为图标元素离鼠标指针的Y轴位移量。例如:
var source = document.getElementById('source'), icon = document.createElement('img'); icon.src = 'img.png'; source.addEventListener('dragstart',function(ev){ ev.dataTransfer.setDragImage(icon,-10,-10) },false)
effectAllowed 和 dropEffect 属性
这两个属性结合起来设置拖放的视觉效果。
值得注意的是:dataTransfer 对象不支持IE。对,不支持IE。
实现拖放排序
上面已经熟悉了拖放 API 的使用,我们来实现个简单的拖放排序,这也是在项目中比较常见的。先来理一下思路:
主要代码如下:
var source = document.querySelectorAll('.list'), dragElement = null; for(var i = 0; i < source.length; i++){ source[i].addEventListener('dragstart',function(ev){ dragElement = this; },false); source[i].addEventListener('dragenter', function(ev){ if(dragElement != this){ this.parentNode.insertBefore(dragElement,this); } }, false) source[i].addEventListener('dragleave', function(ev){ if(dragElement != this){ if(this == this.parentNode.lastElementChild || this == this.parentNode.lastChild){ this.parentNode.appendChild(dragElement); } } }, false) }; document.ondragover = function(e){e.preventDefault();} document.ondrop = function(e){e.preventDefault();}
兼容
主要是在IE中的兼容不太好,不过至少在IE10中能兼容上面的拖动排序。
而且在我简单的试验中发现,就是在 IE 中元素不设置 height 的时候,不会触发 dragleave 事件。
更重要的一点是在 ios 和安卓上,完全不兼容。不过还好,有一个插件能让它在移动端完美兼容。
插件地址:ios-html5-drag-drop-shim
只需要在原有的代码中引入该插件,即可在移动端上实现拖动了。
<script> var iosDragDropShim = { enableEnterLeave: true } </script> <script src="vendor/ios-drag-drop.js"></script>
https://zhuanlan.zhihu.com/p/26666141
到拖拽,我们都很熟悉,那么拖放呢?一字之差,代表的意义是不一样的,拖拽就是拉着走,拖放就是有拖,有放,我们都知道原生 JS 拖拽效果的缺点:1. 代码相对复杂与冗余2. 仅限于在浏览器内的元素间拖放3、不能实现跨页面的拖放
所以H5就出现了拖放技术,与 JS 原生相比 HTML5 拖放的优势:
H5拖放技术,drag&drop,对于浏览器的支持性:
Internet Explorer 9、Firefox、Opera 12、Chrome 以及 Safari 5 支持拖放。注:在 Safari 5.1.2 中不支持拖放。
那么它有哪些api语法,我们来看一下:
1、 draggable 属性
通过 draggable 告诉浏览器哪些元素需要实现拖拽功能。有三个可选值:
true: 元素可以被拖拽
false:元素不能被拖拽
auto:浏览器自己判断元素是否能被拖拽 ( 默认 )
2、对象拖放事件
dragstart:按下鼠标键并开始移动时触发
drag:在元素拖拽过程中持续触发----相似与mousemove
dragend:元素拖拽停止时触发
3、投放区事件流程
元素被拖动到有效的放置目标时,下列事件会依次发生:
1. dragenter:当拖拽对象进入投放区时触发
2. dragover :拖拽对象在投放区内移动时持续触发
3. dragleave:元素被拖出了投放区时触发
4. drop :拖拽对象投放在投放区时触发
虽然所有元素都支持放置目标事件,但这些元素默认是不允许放置的,需要重写事件的默认行为,例如:
在ondragover中一定要执行 preventDefault()否则ondrop事件不会被触发
4、 dataTransfer 对象--常用方法
setDragImage (图标,图标距指针X轴偏移值,Y轴偏移值 )
指定一个图标,当拖动发生时,显示在光标下方
5、dataTransfer 对象--常用属性:
dropEffect 表示被拖动的元素能够执行哪种放置行为
可能的值:
"none" : 不能把拖动的元素放在这里
"move": 把拖动的元素移动到放置目标
"copy": 把拖动的元素复制到放置目标
"link": 放置目标会打开拖动的元素(有URL)
effectAllowed 允许拖动元素的哪种dropEffect
允许值:
"copyLink" : 允许值为 copy 和 link 的 dropEffect
"copyMove": 允许值为 copy 和 move 的 dropEffect
"linkMove" : 允许值为 link和 move 的 dropEffect
"all" : 允许任意的 dropEffect
注意:dropEffect属性搭配effectAllowed属性使用
在dragstart事件处理程序中设置effectAllowed属性
在dragover事件处理程序中设置dropEffect属性
dropEffect 的每个可能值都会导致光标显示为不同的符号
6、files 文件
dataTransfer.files:如果是拖放文件,则返回正在拖放的文件列表FileList
FileReader:专门用于读取文件,FileReader 接口提供一些读取文件的方法与一个包含读取结果的事件模型
FileReader.readAsDataURL方法:参数为要读取的文件对象,将文件读取为DataUrl
FileReader.onload事件:当读取文件成功完成的时候触发此事件,在事件触发后,你可以通过this.result来获取读取的文件数据,如果是图片,将返回base64格式的图片数据。
以上就是拖放提供的api,非常多,可以任意设置拖放的效果,拖拽事件以及投放事件,我们比较常见的垃圾桶效果,拖动排序,使用h5拖放技术实现就会非常简单。
最近接了一个需求,需要实现不同登录人员可以自定义首页模块卡片。简单来说,就是实现首页看板模块的增添与拖拉拽,效果如下:
原生js是支持拖拉拽的,只需要将拖拽的元素的 draggable 属性设置成 "true"即可,然后就是调用相应的函数即可。
拖拽操作 - Web API 接口参考 | MDN
但是,原生js功能不够完善,使用起来需要改造的地方很多,因此,选用成熟的第三方插件比较好。
我们的主项目采用的是vue3,,经过一系列对比,最终选择了 vue-draggable-next这个插件。
vue-draggable-next
vue-draggable-next的周下载量月3万左右,可以看出是一个比较靠谱的插件。
它的使用方式npmj上也介绍的很详细:
vue-draggable-next
如果英文的使用Api看起来比较难受,网上还有中文的使用文档:
vue.draggable.next 中文文档 - itxst.com
这个插件也有vue2版本和纯js版本,其他框架也是也是可以完美使用的。
根据我们的需求,我们应该实现的是分组拖拽,假设我们有三列,那我们要实现的就是这A、B、C三列数据相互拖拽。
我们看看中文官网给的示例:
vue.draggable.next group 例子
看起来很容易,我们只需要写多个draggable标签,每个draggable标签写入相同的组名即可。
回到代码中,要想实现一个三列可拖拉拽的模块列表,我们首先需要引入组件
<script lang="ts" setup>
import { VueDraggableNext } from 'vue-draggable-next'
// ....
</script>
然后定义一个数组储存数据:
<script lang="ts" setup>
import { VueDraggableNext } from 'vue-draggable-next'
const moduleList = ref([
{
"columnIndex": 1,
"moduleDetail": [
{ "moduleCode": "deviation", "moduleName": "控制失调空间",},
{ "moduleCode": "meeting_pending", "moduleName": "会议待办",},
{ "moduleCode": "abnormal_events", "moduleName": "异常事件", },
{ "moduleCode": "audit_matters", "moduleName": "事项审批",}
],
},
{
"columnIndex": 2,
"moduleDetail": [
{ "moduleCode": "air_conditioning_terminal", "moduleName": "空调末端", }
],
},
{
"columnIndex": 3,
"moduleDetail": [
{ "moduleCode": "run_broadcast", "moduleName": "运行播报",},
{"moduleCode": "my_schedule", "moduleName": "我的日程", },
{ "moduleCode": "cold_station", "moduleName": "冷站",}
],
}
])
</script>
最后,在代码中我们使用v-for循环渲染即可
<div v-for="moduleColumn in moduleList " :key="moduleColumn.columnIndex" class="box">
<VueDraggableNext :list="moduleColumn.moduleDetail" group="column" >
<div v-for="(item, index) in moduleColumn.moduleDetail " :key="item.moduleCode" class="drag-item">
<!-- 模块内容 -->
</div>
</VueDraggableNext>
</div>
注意上面的html结构,我们循环渲染了三列VueDraggableNext标签,每个VueDraggableNext标签内部又通过v-for="(item, index) in moduleColumn.moduleDetail渲染了这个拖拽列内部的所有模块。我们通过group="column" 让每个VueDraggableNext组件的组名相同,实现了三个拖拽标签之间的模块互相拖拉拽。
正常情况小,我们肯定是希望在某个组件的固定位置才能拖动组件,因此我们需要使用到拖拽组件的handle属性。
vue.draggable.next属性说明:
handle | :handle=".mover" 只有当鼠标在class为mover类的元素上才能触发拖到事件 |
根据属性说明,我们的代码实现起来也非常容易了。
<div v-for="moduleColumn in moduleList " :key="moduleColumn.columnIndex" class="box">
<VueDraggableNext :list="moduleColumn.moduleDetail" handle=".move" group="column">
<div v-for="(item, index) in moduleColumn.moduleDetail " :key="item.moduleCode" class="drag-item">
<div class="move">
拖拽区域
</div>
<!-- 模块内容 -->
</div>
</VueDraggableNext>
</div>
实际开发中,我么一定会根据接口或者操作动态的更改列表,代码层也就是更改moduleList的值。非常幸运的是,如果你按照上面的方式写代码,当你拖拉拽完毕后,上面的moduleList值会自动更改,我们不用做任何处理!!!这么看,数据的增删改根本不是问题。
实际开发中,我们可能会遇到一个问题,就是如何动态的去渲染组件,如果你熟悉vue,使用动态组件component就可以实现。
首先,我们需要定义一个模块列表
import MeetingPending from '../components/meetingPending.vue'
import AbnormalEvents from '../components/abnormalEvents/index.vue'
import MySchedule from '../components/mySchedule.vue'
import TransactionApproval from '../components/transactionApproval.vue'
import RunningBroadcast from '../components/runningBroadcast.vue'
import CodeSite from '../components/codeSite/index.vue'
import MismatchSpace from '../components/mismatchSpace/index.vue'
import AirDevice from '../components/airDevice/index.vue'
// !全量模块选择列表
export const allModuleList = [
{ moduleCode: 'meeting_pending', label: '会议待办', component: MeetingPending },
{ moduleCode: 'my_schedule', label: '我的日程', component: MySchedule },
{ moduleCode: 'audit_matters', label: '事项审批', component: TransactionApproval },
{ moduleCode: 'abnormal_events', label: '异常事件', component: AbnormalEvents },
{ moduleCode: 'deviation', label: '控制失调空间', component: MismatchSpace },
{ moduleCode: 'run_broadcast', label: '运行播报', component: RunningBroadcast },
{ moduleCode: 'cold_station', label: '冷站', component: CodeSite },
{ moduleCode: 'air_conditioning_terminal', label: '空调末端', component: AirDevice }
]
然后根据moduleCode做匹配,动态渲染即可
<div v-for="moduleColumn in moduleList " :key="moduleColumn.columnIndex" class="box">
<VueDraggableNext :list="moduleColumn.moduleDetail" handle=".move" group="column">
<div v-for="(item, index) in moduleColumn.moduleDetail " :key="item.moduleCode" class="drag-item">
<div class="move">
拖拽区域
</div>
<component :is="getComponentsByCode(item.moduleCode)" ></component>
</div>
</VueDraggableNext>
</div>
如果上面的功能不满足你的需求,我们可以使用这个组件的其他属性,完成更多意想不到的效果
如果下面的属性说明未能完全看明,可以看左边的对应的菜单查看详细说明和例子。
属性名称 | 说明 |
group | 如果一个页面有多个拖拽区域,通过设置group名称可以实现多个区域之间相互拖拽 或者 { name: "...", pull: [true, false, 'clone', array , function], put: [true, false, array , function] } |
sort | 是否开启排序,如果设置为false,它所在组无法排序 |
delay | 鼠标按下多少秒之后可以拖拽元素 |
touchStartThreshold | 鼠标按下移动多少px才能拖动元素 |
disabled | :disabled= "true",是否启用拖拽组件 |
animation | 拖动时的动画效果,如设置animation=1000表示1秒过渡动画效果 |
handle | :handle=".mover" 只有当鼠标在class为mover类的元素上才能触发拖到事件 |
filter | :filter=".unmover" 设置了unmover样式的元素不允许拖动 |
draggable | :draggable=".item" 样式类为item的元素才能被拖动 |
ghost-class | :ghost-class="ghostClass" 设置拖动元素的占位符类名,你的自定义样式可能需要加!important才能生效,并把forceFallback属性设置成true |
chosen-class | :ghost-class="hostClass" 被选中目标的样式,你的自定义样式可能需要加!important才能生效,并把forceFallback属性设置成true |
drag-class | :drag-class="dragClass"拖动元素的样式,你的自定义样式可能需要加!important才能生效,并把forceFallback属性设置成true |
force-fallback | 默认false,忽略HTML5的拖拽行为,因为h5里有个属性也是可以拖动,你要自定义ghostClass chosenClass dragClass样式时,建议forceFallback设置为true |
fallback-class | 默认false,克隆选中元素的样式到跟随鼠标的样式 |
fallback-on-body | 默认false,克隆的元素添加到文档的body中 |
fallback-tolerance | 按下鼠标移动多少个像素才能拖动元素,:fallback-tolerance="8" |
scroll | 默认true,有滚动区域是否允许拖拽 |
scroll-fn | 滚动回调函数 |
scroll-fensitivity | 距离滚动区域多远时,滚动滚动条 |
scroll-speed | 滚动速度 |
传送门:vue.draggable.next 中文文档 - itxst.com
关联文章:如何实现模块的锚点定位及闪烁提示:juejin.cn/post/734622…
作者:石小石Orz
链接:https://juejin.cn/post/7346121373112811583
*请认真填写需求信息,我们会在24小时内与您取得联系。