Drag && Drop

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 当拖动元素或选中的文本到一个可释放目标时触发
dragexit 当元素变得不再是拖动操作的选中目标时触发
dragleave 当拖动元素或选中的文本离开一个可释放目标时触发
dragover 当元素或选中的文本被拖到一个可释放目标上时触发(每100毫秒触发一次)
dragstart 当用户开始拖动一个元素或选中的文本时触发
drop 当元素或选中的文本在可释放目标上被释放时触发

拖拽事件总览

dataTransfer

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>

dataTransfer

用于预览的元素必须在文档流中,否则图片无法展示。但是通常情况下,我们构造的预览元素只需要用于拖拽预览,不需要让用户在其他地方看到,这时可以用一些css技巧让元素在文档流中而不会被看到:

1. display: none:不在文档流中 ❌

2. visibility:hidden:会展示一个看不见的元素 ❌

3. transform: translateX(-200px):✅

4. position: absolute; top: -200px:✅

局限性

局限性

浏览器API没有提供拖拽开始后中途修改拖拽预览图的能力,所以如果想实现类似功能,只能自己模拟拖拽的效果。

原生拖拽 vs 自定义拖拽:原生拖拽在体验上比较流畅,取消拖拽有默认的还原动画,自定义拖拽在预览反馈方面有更强的灵活性。

在 React 中简单封装-useDrag

// 最简单的方法可以用全局变量保存拖拽元素
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
  }
};

在 React 中简单封装-useDrop

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
  };
};

Thanks

Made with Slides.com