建構可拖曳元素

為現有的浮動元素 (例如面板、調色盤、工具列、檢查器或對話方塊) 新增拖曳功能。拖曳動作不會決定元素的顯示、關閉、堆疊或聚焦方式。

使用專屬帳號代碼

如果元素包含可選取的文字、按鈕、輸入內容或連結,請從專用控點開始拖曳。這樣一來,正常內容仍可使用。讓控制點可透過鍵盤移動,並為控制點提供無障礙元素名稱,說明如何使用方向鍵互動。

請勿將 role="application" 套用至控制代碼。這會不必要地變更輔助技術互動。

將 cursor: grab/grabbing、touch-action: none,以及 user-select: none 和 -webkit-user-select: none 套用至控制代碼,而非整個可拖曳元素,這樣控制代碼外的文字仍可選取,觸控手勢也能維持正常行為:

.drag-handle {
  cursor: grab;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}

.drag-handle.dragging {
  cursor: grabbing;
}

保留指標位置和可觸及性

將移動範圍限制在可視區域時,請使用與可視區域相對的定位模型,例如 position: fixed。

  • 僅針對主要指標按鈕 (event.button === 0) 開始拖曳。
  • 記錄 pointerdown 上的指標偏移,並在每次移動時使用,這樣元素就不會在從角落抓取時跳動。
  • 指標離開控點後,繼續拖曳。預設為 setPointerCapture(),並清除 lostpointercapture 的拖曳狀態。如果控點可能會在拖曳過程中移除或重新算繪,請改用文件層級的 pointermove、pointerup 和 pointercancel 監聽器,因為在這種情況下會失去擷取功能。
  • 您也可以選擇等待移動約 4 像素後,再進入拖曳狀態,這樣點選控點就不會觸發拖曳樣式。

使用指標擷取功能,移動事件監聽器可以保留在控點上:

handle.addEventListener('pointerdown', (event) => {
  if (event.button !== 0 || event.target.closest('button, input, select, textarea, a')) return;

  const rect = draggable.getBoundingClientRect();
  offsetX = event.clientX - rect.left;
  offsetY = event.clientY - rect.top;
  handle.setPointerCapture(event.pointerId);
});

handle.addEventListener('pointermove', (event) => {
  if (!handle.hasPointerCapture(event.pointerId)) return;
  handle.classList.add('dragging');
  moveTo(event.clientX - offsetX, event.clientY - offsetY);
});

handle.addEventListener('lostpointercapture', () => {
  handle.classList.remove('dragging');
});

function moveTo(left, top) {
  const maxLeft = Math.max(0, window.innerWidth - draggable.offsetWidth);
  const maxTop = Math.max(0, window.innerHeight - draggable.offsetHeight);

  draggable.style.left = `${Math.max(0, Math.min(left, maxLeft))}px`;
  draggable.style.top = `${Math.max(0, Math.min(top, maxTop))}px`;
}

透過同一個輔助函式限制每個指標和鍵盤位置,讓元素保持可觸及。在可視區域或元素大小變更後,重新限制目前的位置。對於版面配置繁重的元素,建議在拖曳期間進行平移,避免每影格版面配置,然後在放開時將最終位置提交至 left 和 top。

提供鍵盤移動功能

當控點取得焦點時,請使用方向鍵移動元素,並為這些按鍵呼叫 preventDefault()。使用與指標移動相同的限制定位輔助程式。建議一開始先以 15 像素為增量單位,再根據元件的大小和精確度需求調整。

對話方塊專屬附註

<dialog> 或 [popover] 可以使用這項機制,並以其標頭做為控制代碼。請先設定 margin: 0,再指派 left 和 top,因為瀏覽器的預設邊界會干擾手動定位。如要瞭解對話方塊和彈出式視窗的開啟、關閉、焦點和關閉行為,請參閱「宣告式對話方塊和彈出式視窗控制項」和「平台控制項關閉對話方塊」。

備用策略

如果拖曳動作只會重新定位元件,請將其視為漸進增強:在沒有 JavaScript 的情況下,請將元素、其內容和控制項保留在合理的預設位置,並保持可見、可讀和可運作。請勿顯示無法運作的拖曳控點:如果控點是獨立的抓取控制項,請預設隱藏,並在 JavaScript 初始化時顯示;如果標頭兼作控點,請在 JavaScript 初始化時附加拖曳功能。

在 user-select: none 前加入 -webkit-user-select: none,這樣在 Safari 中拖曳時,文字選取功能就會保持停用狀態。