博客 / 詳情

返回

前端拖拽,看似簡單,其實處處是坑

🧑‍💻 寫在開頭

點贊 + 收藏 === 學會🤣🤣🤣

拖拽功能是前端開發裏最常見的交互之一:
百度網盤的文件拖拽,到 Figma 的畫布操作,都離不開拖拽能力。

很多人會覺得——拖拽不就是 mousedown + mousemove + mouseup 嗎?三行代碼就能搞定!

但當你真正落地到生產環境時,坑點就會接踵而來:

  • PC 和移動端事件機制不同
  • 元素拖拽會“飛出”容器
  • iframe 下事件直接丟失
  • 移動端拖拽還會和頁面滾動衝突
  • 在 Vue、React 裏,組件更新導致狀態丟失

要做一個“能用”的拖拽很容易,要做一個“好用”的拖拽卻很難。
今天我們就來拆解:如何實現一個健壯的拖拽能力,並規避常見問題

拖拽的基本原理

拖拽的實現,主要依賴三個核心事件:

  1. 鼠標按下事件 (mousedown) - 開始拖拽
  2. 鼠標移動事件 (mousemove) - 執行拖拽
  3. 鼠標鬆開事件 (mouseup) - 結束拖拽

最基礎的代碼實現如下:

const box = document.getElementById('box');
let isDragging = false;
let offsetX = 0, offsetY = 0;

// 鼠標按下:開始拖拽
box.addEventListener('mousedown', (e) => {
    isDragging = true;
    // 記錄鼠標相對於盒子的偏移
    offsetX = e.clientX - box.offsetLeft;
    offsetY = e.clientY - box.offsetTop;
});

// 鼠標移動:更新位置
document.addEventListener('mousemove', (e) => {
    if (isDragging) {
        box.style.left = (e.clientX - offsetX) + 'px';
        box.style.top = (e.clientY - offsetY) + 'px';
    }
});

// 鼠標釋放:停止拖拽
document.addEventListener('mouseup', () => {
    isDragging = false;
});

👉在線 Demo:codesandbox

總結一句話: 拖拽就是在 mousemove 時不斷更新元素的 left/top

實際開發中的坑點與解決方案

這裏列舉一些常見的:

1. 多設備兼容性

不同設備(PC、平板、手機)的事件機制不同

  • PC 用 鼠標事件 (mousedown/mousemove/mouseup)
  • 移動端用 觸摸事件 (touchstart/touchmove/touchend)
function isMobile() {
  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
       ('ontouchstart' in window) ||
       (navigator.maxTouchPoints > 0);
}

2. 邊界限制和約束

元素拖拽時需要限制在特定區域內,避免拖出可視範圍

解決方案:

// 邊界檢測和限制
updatePosition(newX, newY) {
  // 獲取容器邊界
  const containerRect = this.container.getBoundingClientRect();
  const elementRect = this.element.getBoundingClientRect();
  
  // 計算有效範圍
  const minX = 0;
  const minY = 0;
  const maxX = containerRect.width - elementRect.width;
  const maxY = containerRect.height - elementRect.height;
  
  // 限制位置
  newX = Math.max(minX, Math.min(newX, maxX));
  newY = Math.max(minY, Math.min(newY, maxY));
  
  this.element.style.left = newX + 'px';
  this.element.style.top = newY + 'px';
}

3. iframe 兼容性問題

當頁面有 iframe 時,鼠標一旦移到 iframe 上,就捕獲不到事件了。
常見的做法是:臨時禁用 iframe 的點擊穿透。

 
const iframes = document.getElementsByTagName('iframe');
for (const iframe of iframes) {
  iframe.style.pointerEvents = 'none';
}

4. 框架兼容性問題

在 Vue、React 等框架中,組件重新渲染可能導致拖拽狀態丟失。 可以用 MutationObserver 監控 DOM 變化,防止樣式被重置。

 
// 監聽元素屬性變化
this.observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      if (mutation.attributeName === "style" && 
          element.style.display === "none") {
        element.style.display = "block";
      }
    });
});

this.observer.observe(element, {
    attributes: true,
    attributeFilter: ["style"],
});

5. 移動端滾動衝突

在移動設備上拖拽時,容易觸發頁面滾動

handleTouch(event) {
  // 阻止默認滾動行為
  event.preventDefault();
  event.stopPropagation();
  
  // 處理拖拽邏輯
  this.startDrag(event);
}

高級功能實現

除了基本的拖拽,還常見一些高級需求:

  • 網格對齊(拖拽時自動吸附到網格)
  • 邊緣吸附(拖動靠近邊緣時自動貼邊)
  • 位置持久化(刷新後記住拖拽位置)

這些功能都需要額外邏輯支持。

更好的選擇:drag-kit

通過上面的拆解你會發現,實現一個健壯的拖拽功能,遠不止三行代碼,涉及到 事件抽象、邊界檢測、iframe 兼容、性能優化、框架集成 等一大堆細節。

這就是為什麼推薦使用成熟的庫 —— drag-kit

ScreenShot_2026-01-22_095057_807

drag-kit 的特點

  1. 開箱即用:幾行代碼即可啓用拖拽
  2. 跨平台:自動處理 PC、移動端、iPad
  3. 豐富功能:內置邊界限制、網格對齊、邊緣吸附
  4. 框架兼容:支持 Vue 2/3、React
  5. 性能優化:流暢不卡頓,避免頻繁重繪
  6. TypeScript 支持

快速上手示例

import { createDraggable } from 'drag-kit';

// 基礎用法
createDraggable('myElement');        // 自動檢測設備類型,支持手機、平板(iPad)、PC端,使用統一 API。

// 高級配置
createDraggable('myElement', {
  mode: 'screen',                    // 拖拽模式('screen' | 'page' | 'container'):屏幕、頁面或容器
  initialPosition: { x: '100px', y: '200px' }, // 元素的初始位置,默認 x = 0,y = 0。支持calc等
  lockAxis: 'y',                     // 鎖定軸向('x' | 'y' | 'none')
  gridSize: 50,                      // 網格對齊,拖動網格大小
  snapMode: 'auto',                  // 自動吸附('none' | 'auto' | 'right' | 'left' | 'top' | 'bottom')
  shouldSave: true,                  // 位置持久化
  onDragStart: (element) => {        // 事件回調
    console.log('開始拖拽', element);
  },
  onDrag: (element) => {
    console.log('拖拽中', element);
  },
  onDragEnd: (element) => {
    console.log('拖拽結束', element);
  }
});

安裝和使用

npm install drag-kit

// Vue 3 示例
import { onMounted } from 'vue';
import { createDraggable } from 'drag-kit';

export default {
  setup() {
    onMounted(() => {
      createDraggable('draggableElement', {
        initialPosition: { x: '100px', y: '200px' }
      });
    });
  }
};

總結

實現一個拖拽功能,從原理上看很簡單,但真正落地到生產環境,就會遇到各種坑:

  • 多設備事件差異
  • 拖拽邊界處理
  • iframe 兼容性
  • 框架下狀態丟失
  • 移動端滾動衝突

如果你只需要寫一個 Demo,原生三行代碼足夠。
但如果你要在項目裏用,建議直接用成熟的庫,比如 drag-kit,能幫你繞開大多數坑,快速上線一個穩定、流暢、功能完整的拖拽體驗。

如果對您有所幫助,歡迎您點個關注,我會定時更新技術文檔,大家一起討論學習,一起進步。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.