原生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');
})
相关推荐
Mr_Mao1 小时前
Naive Ultra:中后台 Naive UI 增强组件库
前端
前端小趴菜052 小时前
React-React.memo-props比较机制
前端·javascript·react.js
摸鱼仙人~3 小时前
styled-components:现代React样式解决方案
前端·react.js·前端框架
sasaraku.4 小时前
serviceWorker缓存资源
前端
RadiumAg5 小时前
记一道有趣的面试题
前端·javascript
yangzhi_emo5 小时前
ES6笔记2
开发语言·前端·javascript
yanlele5 小时前
我用爬虫抓取了 25 年 5 月掘金热门面试文章
前端·javascript·面试
中微子6 小时前
React状态管理最佳实践
前端
烛阴7 小时前
void 0 的奥秘:解锁 JavaScript 中 undefined 的正确打开方式
前端·javascript
中微子7 小时前
JavaScript 事件与 React 合成事件完全指南:从入门到精通
前端