原生JS拖拽-知识点和案例

本文目标:将介绍原生JS拖拽事件、属性和方法

本文代码仓库

1. 拖拽示例

在JS红宝书的20.6.5小节 可拖动能力中提到:

  • JS中文字、图片、链接默认允许拖动,但文字需要鼠标选中才可以拖动
  • 所有标签均可以设置draggable属性,值若为true,则可以直接拖拽;若draggable属性为true,则文字可以直接进行拖拽,不需要选中

如下dom结构展示了没有设置draggable属性的文字链接图片设置了draggable为true的的文字四种情况

html 复制代码
<div>
    文字选中拖动
</div>

<div>
    <a href="www.baidu.com">我是a标签链接</a>

</div>

<div class="img-box">
    <img src="./hs.png" alt="">
</div>

<!-- 我是一个普通的div盒子 -->
<div draggable="true">我是draggable属性为true的div盒子</div>

如下图,第一行文字要选中才能拖动;其余情况鼠标都能直接拖动:

2. 拖拽事件

拖拽事件如下:

  • dragstart: 从目标元素身上拖拽起来触发
  • drag: 拖拽移动过程中触发
  • dragend: 拖拽结束时触发,以上三种在拖拽对象上进行绑定
  • dragenter: 拖拽刚刚进入'目标对象'身上触发
  • dragover: 拖拽不断在'目标对象'身上移动触发
  • dragleave:拖拽离开'目标对象'身上
  • drop:拖拽放到'目标对象'身上,以上四种在放置目标对象上实现

如下dom结构,左侧是待拖拽对象,右侧是目标对象(目标对象就是要拖拽到他身上

html 复制代码
<div class="box">
    <ul class="list">
        <li draggable="true">我是拖拽目标1</li>

        <li draggable="true">我是拖拽目标2</li>

        <li draggable="true">我是拖拽目标3</li>

        <li draggable="true">我是拖拽目标4</li>

    </ul>

    <ul class="list2">
        <li draggable="true">我是放置目标对象1</li>

        <li draggable="true">我是放置目标对象2</li>

        <li draggable="true">我是放置目标对象3</li>

        <li draggable="true">我是放置目标对象4</li>

    </ul>

</div>

样式:

css 复制代码
.box {
    width: 400px;
    display: flex;
    justify-content: space-between;
}
.list, 
.list2 {
    border: 1px solid black;
    list-style: none;
    padding-left: 0;
}
li {
    width: 100px;
    border-bottom: 1px solid black;
}

给拖拽对象每个小li绑定dragstartdragdragend事件

js 复制代码
liArr.forEach(li => {
    li.addEventListener('dragstart', (event) => {
        console.log('dragstart');
    })
    li.addEventListener('drag', (event) => {
        console.log('drag');
    })
    li.addEventListener('dragend', (event) => {
        console.log('dragend');
    })
})

执行时机为dragstart -> drag -> dragend,效果如下: 给拖拽目标对象绑定dragenter dragover dragleave/drop

js 复制代码
// 拖拽目标绑定
let droptarget = document.querySelector('.list2')
droptarget.addEventListener('dragenter', (event) => {
    console.log('dragenter');
})
droptarget.addEventListener('dragover', (event) => {
    console.log('dragover');
})
droptarget.addEventListener('dragleave', (event) => {
    console.log('dragleave');
})
droptarget.addEventListener('drop', (event) => {
    console.log('drop');
})

此时执行时机dragenter -> dragover -> drop,效果如下:

我们发现,鼠标在目标对象上松开了,但是并没有触发drop事件(dragleave可以触发),这是为什么?

原因是元素默认都是不允许放置的,必须得覆盖dragenter和dragover的默认事件,才可以触发,

diff 复制代码
droptarget.addEventListener('dragenter', (event) => {
+    event.preventDefault()
    console.log('dragenter');
})
droptarget.addEventListener('dragover', (event) => {
+    event.preventDefault();
    console.log('dragover');
})

如下图,现在可以触发了:

总结:拖拽共有两组类型的事件:

  • 第一组是刚开始拖拽时的dragstart drag dragend,其需要给拖拽对象绑定;
  • 第二组是dragenter dragover dragleave drop事件,需要绑定在拖拽放置目标上;
  • 比如把A拖拽到B上,需要给A绑定dragstart drag dragend事件,给B绑定dragenter dragover dragleave drop事件

3. 拖拽传递数据:dataTransfer对象 --> getData setData

存储数据用dataTransfer对象setData,获取数据用dataTransfer对象getData方法

若把文本框input数据拖拽到其他容器内,浏览器会默认在后台调用setData('text',文本框的内容),我们可以在drop事件中拿到数据:

dom结构如下:

html 复制代码
<div class="box">
    <div class="wenben">
        <input type="text" value="被拖动元素" id="ipt">
    </div>

    <ul class="list2">
        <li>4</li>

        <li>5</li>

        <li>6</li>

    </ul>

</div>
css 复制代码
.box {
    width: 200px;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
#ipt {
    margin-right: 10px;
}
.list2 {
    border: 1px solid black;
    list-style: none;
    padding-left: 0;
}
.list2 li {
    width: 50px;
    border-bottom: 1px solid black;
}

此时并未设置setData, 在drop事件里面手动调用getData方法拿到文本框的内容,key默认是text。注意,dragleave拿不到数据,但是drop能够拿到

js 复制代码
droptarget.addEventListener('drop', (event) => {
    // dragleave拿不到数据,但是drop能够拿到
    console.log(event.dataTransfer.getData('text'), 'drop event.dataTransfer.getData');
})

效果如下,控制台打印的是文本框里面的值:

若要手动设置值,则需要在dragstart事件中手动存储数据,通过setData,代码如下:

js 复制代码
// 拿到ipt元素
let ipt = document.querySelector('#ipt')
ipt.addEventListener('dragstart', (event) => {
    event.dataTransfer.setData('name', ipt.value)
})


droptarget.addEventListener('drop', (event) => {
    // dragleave拿不到数据,但是drop能够拿到
    console.log(event.dataTransfer.getData('text'), 'drop event.dataTransfer.getData');
})

效果如下,注意,getData的数据在dragstart和drop事件中才能拿到,其他事件我测试了均未能拿到

4. effectAllowed和dropEffect属性

  • effectAllowed:拖拽对象身上调用,把A拖拽到B,要在A身上设置effectAllowed属性,表示这个元素可以进行哪些操作,要把他移动走还是复制走
  • dropEffect:拖拽的目标对象身上调用,假设把A拖拽到B身上,要在B身上使用dropEffect属性,告诉别人,你可以往我这里做什么操作,比如可以复制过来或者移动过来
  • 这两个属性要同时设置

如果希望别人禁止拖拽元素过来:

diff 复制代码
linkTarget.addEventListener('dragover', (event) => {
+ event.dataTransfer.dropEffect = 'none'
    event.preventDefault()
})

效果如下,此时drop事件也不会触发:

把元素复制出去,复制光标:

diff 复制代码
// 把list2改变成允许拖动的目标
let droptarget = document.querySelector('.list2')
droptarget.addEventListener('dragenter', (event) => {
    event.preventDefault()
})
droptarget.addEventListener('dragover', (event) => {
+    event.dataTransfer.dropEffect = 'copy'
    event.preventDefault()
})
droptarget.addEventListener('drop', (event) => {
    console.log(event.dataTransfer, 'event.dataTransfer');
})


// 拿到ipt元素
let ipt = document.querySelector('#ipt')
ipt.addEventListener('dragstart', (event) => {
+    event.dataTransfer.effectAllowed = 'copy'
    // console.log(event.dataTransfer, 'event.dataTransfer dragstart');
})

把元素移动出去,移动光标:

diff 复制代码
// 把list2改变成允许拖动的目标
let droptarget = document.querySelector('.list2')
droptarget.addEventListener('dragenter', (event) => {
    event.preventDefault()
})
droptarget.addEventListener('dragover', (event) => {
+    event.dataTransfer.dropEffect = 'move'
    event.preventDefault()
})
droptarget.addEventListener('drop', (event) => {
    console.log(event.dataTransfer, 'event.dataTransfer');
})


// 拿到ipt元素
let ipt = document.querySelector('#ipt')
ipt.addEventListener('dragstart', (event) => {
+    event.dataTransfer.effectAllowed = 'move'
    // console.log(event.dataTransfer, 'event.dataTransfer dragstart');
})
相关推荐
TimelessHaze24 分钟前
拆解字节面试题:async/await 到底是什么?底层实现 + 最佳实践全解析
前端·javascript·trae
执键行天涯1 小时前
从双重检查锁定的设计意图、锁的作用、第一次检查提升性能的原理三个角度,详细拆解单例模式的逻辑
java·前端·github
青青子衿越1 小时前
微信小程序web-view嵌套H5,小程序与H5通信
前端·微信小程序·小程序
OpenTiny社区1 小时前
TinyEngine 2.8版本正式发布:AI能力、区块管理、Docker部署一键强化,迈向智能时代!
前端·vue.js·低代码
qfZYG1 小时前
Trae 编辑器在 Python 环境缺少 Pylance,怎么解决
前端·vue.js·编辑器
bug爱好者1 小时前
Vue3 基于Element Plus 的el-input,封装一个数字输入框组件
前端·javascript
Silence_xl2 小时前
RACSignal实现原理
前端
柯南二号2 小时前
【大前端】实现一个前端埋点SDK,并封装成NPM包
前端·arcgis·npm
dangkei2 小时前
【Wrangler(Cloudflare 的官方 CLI)和 npm/npx 的区别一次讲清】
前端·jvm·npm
乔公子搬砖2 小时前
小程序开发提效:npm支持、Vant Weapp组件库与API Promise化(八)
前端·javascript·微信小程序·js·promise·vagrant·事件绑定