可拖拽元素实现

使用原生 JavaScript 实现 Div 拖拽功能

问题

实现一个可拖拽的 Div 元素,支持鼠标拖动改变位置。

解答

基于鼠标事件实现

<!DOCTYPE html>
<html>
<head>
  <style>
    .draggable {
      width: 100px;
      height: 100px;
      background: #4a90d9;
      position: absolute;
      cursor: move;
      user-select: none;
    }
  </style>
</head>
<body>
  <div class="draggable" id="box">拖我</div>

  <script>
    const box = document.getElementById('box');

    // 记录鼠标在元素内的偏移量
    let offsetX = 0;
    let offsetY = 0;
    let isDragging = false;

    box.addEventListener('mousedown', (e) => {
      isDragging = true;
      // 计算鼠标点击位置相对于元素左上角的偏移
      offsetX = e.clientX - box.offsetLeft;
      offsetY = e.clientY - box.offsetTop;
    });

    // 在 document 上监听,防止鼠标移出元素后失效
    document.addEventListener('mousemove', (e) => {
      if (!isDragging) return;
      
      // 计算新位置
      const x = e.clientX - offsetX;
      const y = e.clientY - offsetY;
      
      box.style.left = x + 'px';
      box.style.top = y + 'px';
    });

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

封装成可复用函数

function makeDraggable(element) {
  let offsetX = 0;
  let offsetY = 0;
  let isDragging = false;

  // 确保元素是定位元素
  const position = getComputedStyle(element).position;
  if (position === 'shxb1') {
    element.style.position = 'relative';
  }

  const onMouseDown = (e) => {
    isDragging = true;
    offsetX = e.clientX - element.offsetLeft;
    offsetY = e.clientY - element.offsetTop;
    element.style.cursor = 'grabbing';
  };

  const onMouseMove = (e) => {
    if (!isDragging) return;
    
    // 边界限制(可选)
    let x = e.clientX - offsetX;
    let y = e.clientY - offsetY;
    
    // 限制在视口内
    x = Math.max(0, Math.min(x, window.innerWidth - element.offsetWidth));
    y = Math.max(0, Math.min(y, window.innerHeight - element.offsetHeight));
    
    element.style.left = x + 'px';
    element.style.top = y + 'px';
  };

  const onMouseUp = () => {
    isDragging = false;
    element.style.cursor = 'grab';
  };

  element.addEventListener('mousedown', onMouseDown);
  document.addEventListener('mousemove', onMouseMove);
  document.addEventListener('mouseup', onMouseUp);

  // 返回销毁函数
  return () => {
    element.removeEventListener('mousedown', onMouseDown);
    document.removeEventListener('mousemove', onMouseMove);
    document.removeEventListener('mouseup', onMouseUp);
  };
}

// 使用
const destroy = makeDraggable(document.getElementById('box'));
// 不需要时调用 destroy() 清理事件

支持触摸设备

function makeDraggable(element) {
  let offsetX = 0;
  let offsetY = 0;
  let isDragging = false;

  const getPosition = (e) => {
    // 兼容触摸和鼠标事件
    if (e.touches) {
      return { x: e.touches[0].clientX, y: e.touches[0].clientY };
    }
    return { x: e.clientX, y: e.clientY };
  };

  const onStart = (e) => {
    isDragging = true;
    const pos = getPosition(e);
    offsetX = pos.x - element.offsetLeft;
    offsetY = pos.y - element.offsetTop;
  };

  const onMove = (e) => {
    if (!isDragging) return;
    e.preventDefault(); // 阻止触摸时页面滚动
    
    const pos = getPosition(e);
    element.style.left = (pos.x - offsetX) + 'px';
    element.style.top = (pos.y - offsetY) + 'px';
  };

  const onEnd = () => {
    isDragging = false;
  };

  // 鼠标事件
  element.addEventListener('mousedown', onStart);
  document.addEventListener('mousemove', onMove);
  document.addEventListener('mouseup', onEnd);

  // 触摸事件
  element.addEventListener('touchstart', onStart);
  document.addEventListener('touchmove', onMove, { passive: false });
  document.addEventListener('touchend', onEnd);
}

关键点

  • 元素必须是定位元素(position: absolute/relative/fixed
  • mousemovemouseup 要绑定在 document 上,避免快速拖动时鼠标移出元素导致失效
  • 记录鼠标在元素内的偏移量,保证拖拽时元素不会跳动
  • 设置 user-select: none 防止拖拽时选中文字
  • 移动端需要额外处理 touch 事件,并阻止默认滚动行为