在现代Web开发中,实现拖拽功能是一项常见而强大的需求。HTML5引入了拖放API(Drag and Drop API),为我们提供了一种简单而高效的方式来实现拖拽操作。其中,e.dataTransfer是该API中的一个重要属性,用于在拖拽操作中传递数据,并控制拖放的效果和行为。本篇博客将深入探索e.dataTransfer的使用,帮助你更好地理解和应用HTML5拖放功能。
1. e.dataTransfer概述
e.dataTransfer是HTML5拖放API中的关键属性,它是一个DataTransfer对象,提供了一组方法和属性,用于在拖拽过程中控制数据传输。通过e.dataTransfer,我们可以设置和获取数据,调整拖放的效果和行为,以及处理文件传输等操作。
2. DataTransfer对象的常用方法和属性
DataTransfer对象提供了几个常用的方法和属性,下面是其中一些重要的:
2.1. setData(format, data)
该方法用于将数据设置到DataTransfer对象中。其中,format参数表示数据的类型,可以是MIME类型或自定义的字符串。data参数是要传输的具体数据值。通过setData方法,我们可以将数据附加到拖拽操作中,以便在目标元素中获取和处理。
2.2. getData(format)
该方法用于从DataTransfer对象中获取指定类型的数据。通过传入format参数,我们可以获取之前设置的特定类型的数据值。在拖放操作的目标元素中,可以使用getData方法来提取源元素设置的数据,进而进行相应的处理。
2.3. clearData(format)
该方法用于从DataTransfer对象中清除指定类型的数据。通过传入format参数,我们可以清除之前设置的特定类型的数据值。这在需要重置或更新数据传输时非常有用。
2.4. dropEffect
dropEffect属性表示拖放操作完成后的效果。可以设置为none、copy、move或link,用于指定拖放操作的行为。通过设置dropEffect,我们可以调整拖放操作在目标位置的表现方式,使用户获得更直观的反馈。
2.5. effectAllowed
effectAllowed属性表示在拖拽过程中允许的操作。可以设置为none、copy、copyLink、copyMove、link、linkMove、move、all或uninitialized。通过设置effectAllowed,我们可以控制拖拽过程中可进行的操作,限制或扩展用户的拖放选项。
2.6. files
files属性是一个FileList对象,表示拖拽操作中传输的文件列表。这在实现文件拖拽上传功能时非常有用。通过files属性,我们可以访问用户拖拽操作中的文件,进行进一步的处理和操作。
3. 实现拖拽功能的步骤
为了更好地理解和应用e.dataTransfer,我们将介绍一般实现拖拽功能的步骤:
3.1. 拖拽源(Drag Source)
首先,我们需要确定要拖拽的元素,即起始位置的元素。以下是拖拽源的基本设置步骤:
- 为拖拽源元素添加
draggable="true"
属性,以表示该元素可拖拽。 - 监听拖拽源元素的
dragstart
事件,在事件处理函数中使用setData方法将数据设置到DataTransfer对象中。通常,我们会设置数据的类型和具体值,以便在拖放过程中传递和使用这些数据。
3.2. 拖放目标(Drop Target)
接下来,我们需要确定要拖拽到的元素,即目标位置的元素。以下是拖放目标的基本设置步骤:
- 监听拖放目标元素的
dragover
事件,使用preventDefault()
方法阻止默认行为。这样可以允许元素接受放置操作,并在拖放过程中显示适当的鼠标指针。 - 监听拖放目标元素的
drop
事件,在事件处理函数中获取拖拽源元素设置的数据,通常使用getData方法。通过获取数据,我们可以在目标元素中进行相应的处理和操作。
4. 拖拽示例代码
下面是一个简单的拖拽示例代码,演示了如何实现基本的拖拽功能:
<!DOCTYPE html>
<html>
<head>
<style>
.drag-source {
width: 100px;
height: 100px;
background-color: red;
cursor: move;
}
.drop-target {
width: 200px;
height: 200px;
background-color: yellow;
}
</style>
</head>
<body>
<div class="drag-source" draggable="true">拖拽我</div>
<div class="drop-target">拖拽到这里</div>
<script>
// 获取拖拽源和拖放目标元素
const dragSource = document.querySelector('.drag-source');
const dropTarget = document.querySelector('.drop-target');
// 拖拽开始时的处理函数
function handleDragStart(e) {
e.dataTransfer.setData('text/plain', '拖拽的数据');
}
// 拖放目标元素的处理函数
function handleDragOver(e) {
e.preventDefault();
}
function handleDrop(e) {
e.preventDefault();
const data = e.dataTransfer.getData('text/plain');
console.log('拖拽的数据:', data);
}
// 监听拖拽源元素的dragstart事件
dragSource.addEventListener('dragstart', handleDragStart);
// 监听拖放目标元素的dragover和drop事件
dropTarget.addEventListener('dragover', handleDragOver);
dropTarget.addEventListener('drop', handleDrop);
</script>
</body>
</html>
在上述示例中,我们创建了一个红色的拖拽源元素和一个黄色的拖放目标元素。通过设置draggable="true"
,我们将拖拽源元素设置为可拖拽。通过监听dragstart
事件,在拖拽开始时,我们使用setData
方法将数据设置为纯文本格式。在拖放目标元素上,我们监听了dragover
事件,并使用preventDefault
阻止默认行为。这样可以在目标元素上显示合适的鼠标指针,表示可以进行放置操作。最后,在drop
事件处理函数中,我们使用getData
方法获取拖拽源元素设置的数据,并在控制台中输出。
5.实例演示
例如在下方这个vue的低代码平台项目中的两个相关组件:
<!-- 组件列表 -->
<template>
<div class="component-list" @dragstart="handleDragStart">
<div
v-for="(item, index) in componentList"
:key="index"
class="list"
draggable
:data-index="index"
>
<span v-if="item.icon.substring(0, 2) === 'el'" :class="item.icon">
</span>
<span v-else class="iconfont" :class="'icon-' + item.icon"></span>
</div>
</div>
</template>
<script>
import componentList from '@/custom-component/component-list'
export default {
data() {
return {
componentList,
}
},
methods: {
// handleDragStart 方法是拖拽开始时的处理函数,通过 e.dataTransfer.setData 将当前拖拽项的索引存储在数据传输对象中。
handleDragStart(e) {
e.dataTransfer.setData('index', e.target.dataset.index)
},
},
}
</script>
<style lang="scss" scoped>
.component-list {
height: 65%;
padding: 10px;
display: grid;
grid-gap: 10px 19px;
grid-template-columns: repeat(auto-fill, 80px);
grid-template-rows: repeat(auto-fill, 40px);
.list {
width: 80px;
height: 40px;
border: 1px solid #ddd;
cursor: grab;
text-align: center;
color: #333;
padding: 2px 5px;
display: flex;
align-items: center;
justify-content: center;
&:active {
cursor: grabbing;
}
.iconfont {
margin-right: 4px;
font-size: 20px;
}
.icon-wenben,
.icon-biaoge {
font-size: 18px;
}
.icon-tupian {
font-size: 16px;
}
}
}
</style>
<template>
<div class="home">
<!-- 上方操作栏 -->
<Toolbar />
<!-- 下方主体部分 -->
<main>
<!-- 左侧组件列表 -->
<section class="left">
<!-- 左侧上方可构建组件列表 -->
<ComponentList />
<!-- 左侧下方已构建组件列表 -->
<RealTimeComponentList />
</section>
<!-- 中间画布 -->
<section class="center">
<div
class="content"
@drop="handleDrop"
@dragover="handleDragOver"
@mousedown="handleMouseDown"
@mouseup="deselectCurComponent"
>
<Editor />
</div>
</section>
<!-- 右侧属性列表 -->
<section class="right">
<el-tabs v-if="curComponent" v-model="activeName">
<el-tab-pane label="属性" name="attr">
<component :is="curComponent.component + 'Attr'" />
</el-tab-pane>
<el-tab-pane label="动画" name="animation" style="padding-top: 20px;">
<AnimationList />
</el-tab-pane>
<el-tab-pane label="事件" name="events" style="padding-top: 20px;">
<EventList />
</el-tab-pane>
</el-tabs>
<CanvasAttr v-else></CanvasAttr>
</section>
</main>
</div>
</template>
<script>
import Editor from '@/components/Editor/index'
import ComponentList from '@/components/ComponentList' // 左侧列表组件
import AnimationList from '@/components/AnimationList' // 右侧动画列表
import EventList from '@/components/EventList' // 右侧事件列表
import componentList from '@/custom-component/component-list' // 左侧列表数据
import Toolbar from '@/components/Toolbar'
import { deepCopy } from '@/utils/utils'
import { mapState } from 'vuex'
import generateID from '@/utils/generateID'
import { listenGlobalKeyDown } from '@/utils/shortcutKey'
import RealTimeComponentList from '@/components/RealTimeComponentList'
import CanvasAttr from '@/components/CanvasAttr'
import { changeComponentSizeWithScale } from '@/utils/changeComponentsSizeWithScale'
import { setDefaultcomponentData } from '@/store/snapshot'
export default {
components: { Editor, ComponentList, AnimationList, EventList, Toolbar, RealTimeComponentList, CanvasAttr },
data() {
return {
activeName: 'attr',
reSelectAnimateIndex: undefined,
}
},
computed: mapState([
'componentData',
'curComponent',
'isClickComponent',
'canvasStyleData',
'editor',
]),
created() {
this.restore()
// 全局监听按键事件
listenGlobalKeyDown()
},
methods: {
restore() {
// 用保存的数据恢复画布
if (localStorage.getItem('canvasData')) {
setDefaultcomponentData(JSON.parse(localStorage.getItem('canvasData')))
this.$store.commit('setComponentData', JSON.parse(localStorage.getItem('canvasData')))
}
if (localStorage.getItem('canvasStyle')) {
this.$store.commit('setCanvasStyle', JSON.parse(localStorage.getItem('canvasStyle')))
}
},
handleDrop(e) {
e.preventDefault()
e.stopPropagation()
/*
e.preventDefault()是一个事件方法,用于阻止事件的默认行为。在上下文中,它被用于拖放事件中的handleDrop方法。
通过调用e.preventDefault(),可以防止浏览器对拖放操作的默认行为进行处理,例如在某些情况下打开链接或显示拖放图标。
e.stopPropagation()也是一个事件方法,用于停止事件的进一步传播。在上下文中,它被用于拖放事件中的handleDrop方法。
通过调用e.stopPropagation(),可以防止事件继续向上级元素进行传播,确保事件只在当前元素上进行处理,而不会传递给父级元素或其他监听相同事件的元素。
*/
// console.log(this.editor)
const index = e.dataTransfer.getData('index') // 获取拖放数据的索引
const rectInfo = this.editor.getBoundingClientRect() // 获取编辑器的位置信息
if (index) {
const component = deepCopy(componentList[index]) // 深拷贝组件列表中的组件
component.style.top = e.clientY - rectInfo.y // 设置组件的垂直位置
component.style.left = e.clientX - rectInfo.x // 设置组件的水平位置
component.id = generateID() // 生成组件的唯一ID
// 根据画面比例修改组件样式比例 https://github.com/woai3c/visual-drag-demo/issues/91
changeComponentSizeWithScale(component) // 根据画布比例调整组件的大小
this.$store.commit('addComponent', { component }) // 将组件添加到组件列表中
this.$store.commit('recordSnapshot') // 记录快照,用于撤销和重做功能
}
},
handleDragOver(e) {
e.preventDefault()
e.dataTransfer.dropEffect = 'copy'
},
handleMouseDown(e) {
e.stopPropagation()
this.$store.commit('setClickComponentStatus', false)
this.$store.commit('setInEditorStatus', true)
},
deselectCurComponent(e) {
if (!this.isClickComponent) {
this.$store.commit('setCurComponent', { component: null, index: null })
}
// 0 左击 1 滚轮 2 右击
if (e.button != 2) {
this.$store.commit('hideContextMenu')
}
},
},
}
</script>
<style lang="scss">
.home {
height: 100vh;
background: #fff;
main {
height: calc(100% - 64px);
position: relative;
.left {
position: absolute;
height: 100%;
width: 200px;
left: 0;
top: 0;
& > div {
overflow: auto;
&:first-child {
border-bottom: 1px solid #ddd;
}
}
}
.right {
position: absolute;
height: 100%;
width: 288px;
right: 0;
top: 0;
.el-select {
width: 100%;
}
}
.center {
margin-left: 200px;
margin-right: 288px;
background: #f5f5f5;
height: 100%;
overflow: auto;
padding: 20px;
.content {
width: 100%;
height: 100%;
overflow: auto;
}
}
}
.placeholder {
text-align: center;
color: #333;
}
.global-attr {
padding: 10px;
}
}
</style>
在模板部分,有一个中间的画布区域(section class=“center”),它包含一个具有拖拽功能的div元素(class=“content”)。这个div元素绑定了一些拖拽相关的事件处理函数:
- @drop=“handleDrop”:当拖拽的元素被放置在这个div元素上时,会触发handleDrop方法。
- @dragover=“handleDragOver”:当有元素拖动到这个div元素上方时,会触发handleDragOver方法。
- @mousedown=“handleMouseDown”:当鼠标按下时,会触发handleMouseDown方法。
- @mouseup=“deselectCurComponent”:当鼠标松开时,会触发deselectCurComponent方法。
在方法部分,定义了这些拖拽相关的事件处理函数:
- handleDrop(e):处理拖放事件的方法。在这个方法中,首先调用了e.preventDefault()和e.stopPropagation(),阻止了默认的拖放行为和事件传播。然后获取拖放数据的索引,根据索引获取相应的组件数据。通过一些计算,设置了组件的位置、样式等信息,并将组件添加到组件列表中。
- handleDragOver(e):处理拖拽元素在目标区域上方拖动时的事件。在这个方法中,调用了e.preventDefault(),阻止了默认的拖放行为。并设置了e.dataTransfer.dropEffect属性为’copy’,表示可以复制拖拽的元素。
- handleMouseDown(e):处理鼠标按下事件的方法。在这个方法中,调用了e.stopPropagation(),阻止了事件的进一步传播,并修改了一些状态值。
- deselectCurComponent(e):处理鼠标松开事件的方法。在这个方法中,根据鼠标点击位置和状态值,进行了一些判断和操作,如隐藏上下文菜单等。
整体来说,这段代码实现了一个拖拽功能的编辑器界面,用户可以从左侧的组件列表中拖拽组件到中间的画布区域,实现组件的添加和位置调整。这些操作通过调用相应的方法,并在方法中处理拖放事件和修改相关数据来实现。
6. 总结
通过e.dataTransfer属性,我们可以在HTML5拖放API中实现数据的传输和拖拽操作的控制。DataTransfer对象提供了setData、getData、clearData等方法,用于设置、获取和清除数据。此外,dropEffect、effectAllowed和files等属性可以帮助我们调整拖放的效果和行为,实现更灵活和功能丰富的拖拽功能。