原生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');
})
相关推荐
桂月二二4 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062065 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb5 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角5 小时前
CSS 颜色
前端·css
浪浪山小白兔6 小时前
HTML5 新表单属性详解
前端·html·html5
lee5767 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579657 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
limit for me7 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者7 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架
qq_392794488 小时前
前端缓存策略:强缓存与协商缓存深度剖析
前端·缓存