原生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');
})
相关推荐
江号软件分享24 分钟前
有效保障隐私,如何安全地擦除电脑上的敏感数据
前端
web守墓人1 小时前
【前端】ikun-markdown: 纯js实现markdown到富文本html的转换库
前端·javascript·html
Savior`L1 小时前
CSS知识复习5
前端·css
许白掰1 小时前
Linux入门篇学习——Linux 工具之 make 工具和 makefile 文件
linux·运维·服务器·前端·学习·编辑器
中微子6 小时前
🔥 React Context 面试必考!从源码到实战的完整攻略 | 99%的人都不知道的性能陷阱
前端·react.js
中微子7 小时前
React 状态管理 源码深度解析
前端·react.js
加减法原则8 小时前
Vue3 组合式函数:让你的代码复用如丝般顺滑
前端·vue.js
yanlele9 小时前
我用爬虫抓取了 25 年 6 月掘金热门面试文章
前端·javascript·面试
lichenyang4539 小时前
React移动端开发项目优化
前端·react.js·前端框架
你的人类朋友9 小时前
🍃Kubernetes(k8s)核心概念一览
前端·后端·自动化运维