HTML 拖放(Drag and Drop)接口让应用程序能够在浏览器中使用拖放功能。例如,用户可使用鼠标选择可拖动(draggable)元素,将元素拖动到可放置(droppable)元素,并释放鼠标按钮以放置这些元素。拖动操作期间,可拖动元素会有一个半透明表示跟随着鼠标指针。
<div class="dragdemo" @dragstart="onDragStart" draggable="true">drag me</div>
<div class="target" @drop="onDrop">drop here</div>
在想要拖拽的元素上加`draggable="true"`,元素就变得可拖拽了。拖放后的元素变化,需要自己完成
function onDragStart(ev) {
// 记录id
ev.dataTransfer.setData("text", ev.target.id);
}
function onDrop(ev) {
ev.preventDefault();
var data = ev.dataTransfer.getData("text");
// 通过 id 获取 dragSource
const item = document.getElementById(data)
ev.target.appendChild(item.cloneNode(true));
}
事件名称 | 触发时机 |
---|---|
drag | 当拖动元素或选中的文本时触发 |
dragend | 当拖拽操作结束时触发 (比如松开鼠标按键或敲“Esc”键) |
dragenter | 当拖动元素或选中的文本到一个可释放目标时触发 |
dragleave | 当拖动元素或选中的文本离开一个可释放目标时触发 |
dragover | 当元素或选中的文本被拖到一个可释放目标上时触发(每100毫秒触发一次) |
dragstart | 当用户开始拖动一个元素或选中的文本时触发 |
drop | 当元素或选中的文本在可释放目标上被释放时触发 |
DataTransfer 对象用于保存拖拽过程中的数据。它可以保存一项或多项数据,这些数据项可以是一种或者多种数据类型,值会被隐式转换。
function dragstart_handler(ev) {
ev.dataTransfer.setData("obj", {foo: 'bar'});
ev.dataTransfer.setData("num", 1234);
}
function drop_handler(ev) {
ev.preventDefault();
ev.dataTransfer.getData('obj') // "[object Object]"
ev.dataTransfer.getData('num') // "123"
// 清除数据
ev.dataTransfer.clearData();
}
dataTransfer 中还有一个 setDragImage 方法,可以用来设置拖拽起来的预览图:
setDragImage 接收三个参数:
1. 用于预览的 Image 元素或 Element 元素
2. 相对于图片的横向偏移量
3. 相对于图片的纵向偏移量
<div id="drag-with-image" class="dragdemo" draggable="true">drag me</div>
<script>
document.getElementById("drag-with-image").addEventListener("dragstart", function(e) {
var img = document.createElement("img");
img.src = "http://kryogenix.org/images/hackergotchi-simpler.png";
e.dataTransfer.setDragImage(img, 0, 0);
}, false);
</script>
用于预览的元素必须在文档流中,否则图片无法展示。但是通常情况下,我们构造的预览元素只需要用于拖拽预览,不需要让用户在其他地方看到,这时可以用一些css技巧让元素在文档流中而不会被看到:
1. display: none:不在文档流中 ❌
2. visibility:hidden:会展示一个看不见的元素 ❌
3. transform: translateX(-200px):✅
4. position: absolute; top: -200px:✅
浏览器API没有提供拖拽开始后中途修改拖拽预览图的能力,所以如果想实现类似功能,只能自己模拟拖拽的效果。
原生拖拽 vs 自定义拖拽:原生拖拽在体验上比较流畅,取消拖拽有默认的还原动画,自定义拖拽在预览反馈方面有更强的灵活性。
// 最简单的方法可以用全局变量保存拖拽元素
let dragSourceItem;
const useDrag = () => {
const dragSource = useRef(null);
const dragPreview = useRef(null);
useEffect(() => {
if(dragSource.current){
const node = dragSource.current;
node.setAttribute('draggable', true);
node.addEventListener('dragstart', e => {
if(dragPreview.current){
e.dataTransfer.setDragImage(dragPreview.current, 0, 0);
dragSourceItem = dragPreview.current;
}
});
}
}, [dragSource]);
return {
dragSource,
dragPreview
}
};
const useDrop = () => {
const drop = useRef(null);
useEffect(() => {
if(drop.current){
const node = drop.current;
// 需要在 dragover 中 prevendDefault, 否则 drop 无法触发
node.addEventListener('dragover', e => {
e.preventDefault();
});
node.addEventListener('drop', e => {
node.appendChild(dragSourceItem);
});
}
}, [drop]);
return {
drop
};
};