你会知道的有关拖拽的那些事

181 阅读3分钟

拖拽

在元素的拖拽上,市面上常用的:有H5提供下的drag事件,或者灵活度更大的mouse自定义形式去实现。

drag

触发时机: drag 事件在元素已经被拖动并且其数据(例如拖拽的元素)正在拖动时触发。这通常用于拖动的数据传输(例如拖拽文件到浏览器、拖拽元素到容器等)。

使用场景: drag 事件通常与 dragstartdragoverdragend 等一系列拖拽事件一起使用,用于实现 HTML5 的拖放 API。它适用于需要拖动数据的场景,且不需要开发者手动控制拖动位置。

element.addEventListener('dragstart', (e) => {
  e.dataTransfer.setData('text/plain', '拖动数据');
});

dropArea.addEventListener('dragover', (e) => {
  e.preventDefault(); // 允许拖拽进入
});

dropArea.addEventListener('drop', (e) => {
  e.preventDefault();
  const data = e.dataTransfer.getData('text/plain');
  console.log('Dropped data:', data);
});

简单示例

<div class="dropzone">
    <div id="draggable" draggable="true">这个 div 可以拖动</div>
    </div>
<div class="dropzone" id="droptarget"></div>

<script>
let dragged;

/* 在可拖动的目标上触发的事件 */
const source = document.getElementById("draggable");

source.addEventListener("dragstart", (event) => {
  // 保存被拖动元素的引用
  dragged = event.target;
  // 设置为半透明
  event.target.classList.add("dragging");
});

source.addEventListener("dragend", (event) => {
  // 拖动结束,重置透明度
  event.target.classList.remove("dragging");
});

/* 在放置目标上触发的事件 */
const target = document.getElementById("droptarget");
target.addEventListener(
  "dragover",
  (event) => {
    // 阻止默认行为以允许放置
    event.preventDefault();
  },
  false,
);

target.addEventListener("dragenter", (event) => {
  // 在可拖动元素进入潜在的放置目标时高亮显示该目标
  if (event.target.classList.contains("dropzone")) {
    event.target.classList.add("dragover");
  }
});

target.addEventListener("dragleave", (event) => {
  // 在可拖动元素离开潜在放置目标元素时重置该目标的背景
  if (event.target.classList.contains("dropzone")) {
    event.target.classList.remove("dragover");
  }
});

target.addEventListener("drop", (event) => {
  // 阻止默认行为(会作为某些元素的链接打开)
  event.preventDefault();
  // 将被拖动元素移动到选定的目标元素中
  if (event.target.classList.contains("dropzone")) {
    event.target.classList.remove("dragover");
    dragged.parentNode.removeChild(dragged);
    event.target.appendChild(dragged);
  }
});
</script>
<style>
body {
  /* Prevent the user selecting text in the example */
  user-select: none;
}

#draggable {
  text-align: center;
  background: white;
}

.dropzone {
  width: 200px;
  height: 20px;
  background: blueviolet;
  margin: 10px;
  padding: 10px;
}

.dropzone.dragover {
  background-color: purple;
}

.dragging {
  opacity: 0.5;
}
</style>

mouse事件

拥有更高的自定义能力,整个拖拽的过程更可控,其中的核心事件:mousedown,mouseover,mouseup事件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>事件形式实现拖拽</title>
    <style>
        body {
            width: 100vw;
            height: 100vh;
            margin: 0;
            overflow: hidden;
        }
        .source {
            background-color: red;
            width: 100px;
            height: 100px;
            position: absolute;
            cursor: grab;
            user-select: none;
        }
        .source:active {
            cursor: grabbing;
        }
    </style>    
</head>
<body>
    <div class="source" id="dragDiv"></div>

    <script>
        const source = document.getElementById('dragDiv');
        
        let left = 0;
        let topPos = 0;     
        let lastPageX = 0;
        let lastPageY = 0;
        let isDrag = false;

        source.addEventListener('mousedown', (e) => {
            if (e.button !== 0) return;
            
            isDrag = true;
            left = parseInt(source.style.left) || 0;
            topPos = parseInt(source.style.top) || 0;
            lastPageX = e.pageX;
            lastPageY = e.pageY;
            
            e.preventDefault();
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDrag) return;
            
            // 记录上次的移动位置
            const deltaX = e.pageX - lastPageX;
            const deltaY = e.pageY - lastPageY;
            
            left += deltaX;
            topPos += deltaY;
            
            source.style.left = `${left}px`;
            source.style.top = `${topPos}px`; 
            
            lastPageX = e.pageX;
            lastPageY = e.pageY;
            
            e.preventDefault();
        });

        document.addEventListener('mouseup', () => {
            isDrag = false;
        });
    </script>
</body>
</html>

如果使用vue项目的话,可以对如下部分进行封装下:

const useDrag = (target) => {
    const left = ref(0);
    const top = ref(0);
    const isDragging = ref(false); 
    let lastPageX = 0;
    let lastPageY = 0;

    const handleMouseDown = (e) => {
        if (e.button !== 0) return; // 只响应左键
        
        isDragging.value = true;
        left.value = parseInt(target.style.left) || 0; // 转换为数字
        top.value = parseInt(target.style.top) || 0;   // 转换为数字
        lastPageX = e.pageX;
        lastPageY = e.pageY;
        
        e.preventDefault();
    };

    const handleMouseMove = (e) => {
        if (!isDragging.value) return;
        
        const deltaX = e.pageX - lastPageX;
        const deltaY = e.pageY - lastPageY;
        
        left.value += deltaX;
        top.value += deltaY;
        
        target.style.left = `${left.value}px`;
        target.style.top = `${top.value}px`;
        
        lastPageX = e.pageX;
        lastPageY = e.pageY;
        
        e.preventDefault();
    };

    const handleMouseUp = () => {
        isDragging.value = false;
    };

    onMounted(() => {
        target.addEventListener('mousedown', handleMouseDown);
        document.body.addEventListener('mousemove', handleMouseMove);
        document.body.addEventListener('mouseup', handleMouseUp);
    });

    onUnmounted(() => {
        target.removeEventListener('mousedown', handleMouseDown);
        document.body.removeEventListener('mousemove', handleMouseMove);
        document.body.removeEventListener('mouseup', handleMouseUp);
    });

    return {
        left,
        top,
        isDragging
    };
};

区别和适用场景

特性mousedowndrag
触发时机鼠标按下时触发。当元素开始被拖动时触发。
使用场景用于实现自定义拖拽逻辑,如监听鼠标位置,计算位移等。用于拖放操作的处理,配合 HTML5 的拖放 API。
事件流是鼠标事件流的一部分,通常与 mousemovemouseup 配合使用。是 HTML5 拖放 API 的一部分,事件流包含 dragstartdragoverdragend 等。
可自定义拖动内容需要手动控制元素的拖动过程。使用浏览器原生的拖放机制,自动处理元素位置变化。
应用场景自定义的拖拽行为,通常用于实现复杂的拖拽交互。用于简单的拖拽文件、数据、或者元素交换等。