前言
拖拽(Draggle)是一种用户交互方式,更加直观地操作网页元素,提高用户体验。
本文将介绍拖拽、应用场景以及实现一个课程表拖拽案例。
效果如下:
1. 拖拽介绍
HTML 拖拽(Drag and Drop)接口使应用程序能够在浏览器中使用拖放功能。
例如,用户可使用鼠标选择可拖拽(draggable)元素,将元素拖拽到可放置(droppable)元素,并释放鼠标按钮以放置这些元素。拖拽操作期间,会有一个可拖拽元素的半透明快照跟随着鼠标指针。
1.1 DataTransfer接口
介绍两个属性:
-
DataTransfer.dropEffect:获取当前选定的拖放操作类型或者设置的为一个新的类型。值必须为none,copy,link或move。 -
DataTransfer.effectAllowed:提供所有可用的操作类型。必须是none,copy,copyLink,copyMove,link,linkMove,move,alloruninitialized之一。
通过这个接口,我们可以控制拖拽元素的显示,和操作类型。
例如:当effectAllowed设置为move时,拖拽元素是不会出现+号。
1.2 常用事件介绍
主要介绍四个事件,一个拖拽元素操作过程会分别触发
dragstart:当用户开始拖动元素或选择文本时触发此事件dragover:当将元素或文本选择拖动到有效放置目标(每几百毫秒)上时,会触发此事件dragenter:当拖动的元素或选择文本输入有效的放置目标时,会触发此事件drop:拖动元素或选择文本时放开时触发此事件,注意:需要阻止浏览器默认行为
2. 拖拽技术的应用场景
拖拽技术常用场景有:
- 列表排序:通过拖拽列表项,用户可以轻松地调整列表的顺序。
- 图片编辑:在图片编辑工具中,用户可以通过拖拽来调整图片的位置、大小或角度。
- 窗口管理:在桌面或移动应用中,用户可以通过拖拽来移动、调整或关闭窗口。
- 数据可视化:在数据可视化应用中,用户可以通过拖拽来筛选、排列或操作数据等
3. 实现课程表案例
3.1 准备静态页面
左侧课程,右侧表格
3.2 添加拖拽事件
要想完成拖拽就需要给左侧的元素添加draggable为true的属性,即可完成拖拽。开始定义拖拽事件:
// 拖拽的元素
container.ondragstart = function (e) {
}
// 经过哪个元素
container.ondragover = function (e) {
// 需要阻止默认行为,否则drop事件无法触发
e.preventDefault()
}
// 放在哪个元素上方
container.ondragenter = function (e) {
}
// 松手 浏览器有默认行为,阻止拖放
container.ondrop = function (e) {
}
3.3 实现具体逻辑
- 第一个要考虑的问题从左边拖拽的元素是需要显示添加效果,而在课程表中的放置的元素是不需要显示添加效果
通过dataTransfer.effectAllowed进行控制。如何进行控制,就需要在拖拽元素上添加自定义data-effect属性为copy,在开始的事件中进行定义,后面拖拽完成只需要修改为move即可。
container.ondragstart = function (e) {
// 通过dataset设置默认效果
e.dataTransfer.effectAllowed = e.target.dataset.effect
}
- 拖拽到目标元素添加背景颜色,如何确定目标元素是否允许添加
可以通过自定义属性data-drop定义为copy,说明该元素允许接受copy,即可添加背景色,例如添加类drop-over,添加之前需要先清除。例如左侧只接受data-drop为move,不需要每个元素添加自定义属性,只需要在父元素添加一个即可,所以查找元素时,只要自身或者父元素满足条件即可。
// 清除元素的背景颜色
function clearDropStyle() {
document.querySelectorAll('.drop-over').forEach(node => {
node.classList.remove('drop-over')
})
}
// 只要自身或者父元素满足条件即可
function getDropNode(node) {
while (node) {
if (node.dataset && node.dataset.drop) {
return node
}
// 否则向上查找
node = node.parentNode
}
}
// 放在哪个元素上方
container.ondragenter = function (e) {
// 需要清除之前的drop-over
clearDropStyle()
// 添加背景颜色,需要判断dataset属性为copy并且是不是等于拖拽元素的e.dataTransfer.effectAllowed
const dropNode = getDropNode(e.target)
if (dropNode && dropNode.dataset.drop === e.dataTransfer.effectAllowed) {
dropNode.classList.add('drop-over')
}
}
- 拖动到指定单元格
到达目标元素,需要清除样式,获取拖拽元素,
- 判断是否为
copy状态,如果是,先清空目标节点内容,修改data-effect为move,避免再次拖拽,添加到目标节点即可 - 如果原节点不是可
copy状态,直接删除即可
// 拖拽的哪个元素
let sourceNode
container.ondragstart = function (e) {
// 默认是copy,通过dataset设置默认效果
e.dataTransfer.effectAllowed = e.target.dataset.effect
sourceNode = e.target
}
// 松手 浏览器有默认行为,阻止拖放
container.ondrop = function (e) {
console.log('打印***e.target', e.target)
// 需要清除之前的drop-over
clearDropStyle()
const dropNode = getDropNode(e.target)
// 目标节点是copy
if (dropNode && dropNode.dataset.drop === e.dataTransfer.effectAllowed) {
if (dropNode.dataset.drop === 'copy') {
// 避免多次添加
dropNode.innerHTML = ''
const cloneNode = sourceNode.cloneNode(true)
cloneNode.dataset.effect = 'move'
dropNode.appendChild(cloneNode)
} else {
// 删除拖拽节点
sourceNode.remove()
}
}
}
至此,所有的逻辑就完成了。
完整代码:
```html
<style>
.container {
margin: 0;
display: flex;
align-items: center;
width: 100vw;
height: 100vh;
}
.left {
display: flex;
justify-content: space-around;
align-items: center;
flex-direction: column;
width: 100px;
height: 400px;
flex-shrink: 0;
background-color: whitesmoke;
}
.item {
width: 80px;
height: 50px;
text-align: center;
line-height: 50px;
}
.color1 {
background-color: pink;
}
.color2 {
background-color: red;
}
.color3 {
background-color: orange;
}
.color4 {
background-color: yellow;
}
.color5 {
background-color: green;
}
.color6 {
background-color: blue;
}
.right {
background-color: whitesmoke;
margin: 0 20px 0 12px;
}
.right table td {
width: 80px;
height: 50px;
}
.drop-over {
background-color: #999;
}
</style>
<div class="container">
<div class="left" data-drop="move">
<div data-effect="copy" draggable="true" class="color1 item">语文</div>
<div data-effect="copy" draggable="true" class="color2 item">数学</div>
<div data-effect="copy" draggable="true" class="color3 item">英语</div>
<div data-effect="copy" draggable="true" class="color4 item">历史</div>
<div data-effect="copy" draggable="true" class="color5 item">地理</div>
<div data-effect="copy" draggable="true" class="color6 item">政治</div>
</div>
<div class="right">
<table border>
<caption>
<h1>课程表</h1>
</caption>
<colgroup>
<col />
<col />
<col />
<col />
<col />
<col />
<col />
<col />
</colgroup>
<thead>
<tr>
<td></td>
<th>星期一</th>
<th>星期二</th>
<th>星期三</th>
<th>星期四</th>
<th>星期五</th>
<th>星期六</th>
<th>星期天</th>
</tr>
</thead>
<tbody>
<tr>
<th rowspan="4" class="span">上午</th>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
</tr>
<tr>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
</tr>
<tr>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
</tr>
<tr>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
</tr>
<tr>
<td colspan="8"></td>
</tr>
<tr>
<th rowspan="4" class="span">上午</th>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
</tr>
<tr>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
</tr>
<tr>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
<td data-drop="copy"></td>
</tr>
</tbody>
</table>
</div>
</div>
<script>
const container = document.querySelector('.container')
// 拖拽的哪个元素
let sourceNode
container.ondragstart = function (e) {
// 默认是copy,通过dataset设置默认效果
e.dataTransfer.effectAllowed = e.target.dataset.effect
sourceNode = e.target
}
// 经过哪个元素
container.ondragover = function (e) {
e.preventDefault()
// console.log('打印***e.target', e.target)
}
// 清除元素的背景颜色
function clearDropStyle() {
document.querySelectorAll('.drop-over').forEach(node => {
node.classList.remove('drop-over')
})
}
function getDropNode(node) {
while (node) {
if (node.dataset && node.dataset.drop) {
return node
}
// 否则向上查找
node = node.parentNode
}
}
// 放在哪个元素上方
container.ondragenter = function (e) {
// 需要清除之前的drop-over
clearDropStyle()
// 添加背景颜色,需要判断dataset属性为copy并且是不是等于拖拽元素的e.dataTransfer.effectAllowed
const dropNode = getDropNode(e.target)
if (dropNode && dropNode.dataset.drop === e.dataTransfer.effectAllowed) {
dropNode.classList.add('drop-over')
}
}
// 松手 浏览器有默认行为,阻止拖放
container.ondrop = function (e) {
console.log('打印***e.target', e.target)
// 需要清除之前的drop-over
clearDropStyle()
const dropNode = getDropNode(e.target)
// 目标节点是copy
if (dropNode && dropNode.dataset.drop === e.dataTransfer.effectAllowed) {
if (dropNode.dataset.drop === 'copy') {
// 避免多次添加
dropNode.innerHTML = ''
const cloneNode = sourceNode.cloneNode(true)
cloneNode.dataset.effect = 'move'
dropNode.appendChild(cloneNode)
} else {
// 删除拖拽节点
sourceNode.remove()
}
}
}
</script>
4. 总结
最后总结一下,主要介绍了 HTML 拖拽接口(Drag and Drop API)的基本概念,包括 DataTransfer 接口和常用的拖拽事件。
详细介绍了如何在静态页面中实现一个课程表的拖拽功能。首先,通过设置元素的 draggable 属性为 true,使元素可拖拽。然后,通过监听拖拽事件,在事件处理函数中实现拖拽过程中的逻辑,包括拖拽元素的显示效果、拖拽元素的移动和放置。
记录一下,如有错误,请指正O^O!