Skip to content

[Firefox] drag 事件 client/page 與 offset 等座標位置不正常運作

Published: at 11:12 AM (6 min read)

前陣子在回答 StackOverflow 上的問題 時,發現在 Firefox 瀏覽器上,存在了一個長年的 bug,當使用 drag 事件時,clientXclientYoffsetXoffsetY 等座標位置總是顯示 0,導致無法正確取得拖曳元素的座標位置。

問題

你可以嘗試在 ChromeFirefox 分別拖拉下方藍色方塊,比較 拖拉事件 與座標位置的變化。

Drag me
Event Type client page screen offset x,y
drag
dragenter
dragover
dragleave
dragstart
dragend

Chrome 上,可以看到 drag 事件的座標位置是正常的,但在 Firefox 上都不會更新且為 0

🤔 讓我們深入看看為什麼會有這樣的差異,這個行為是正確的嗎?

HTML5 Drag and Drop API 規格怎麼說

根據 HTML5 Drag and Drop API 規格, DragEvent 應該符合這個型態定義:

Create a DragEvent object and initialize it to have the given name e, to bubble, to be cancelable unless e is dragleave or dragend, and to have the detail attribute set to zero, the mouse and key attributes set according to the state of the input devices as they would be for user interaction events…

📌 If there is no relevant pointing device, the object must have its screenX, screenY, clientX, clientY, and button attributes set to 0.

interface DragEvent : MouseEvent {
  readonly attribute DataTransfer dataTransfer;

  void initDragEvent(
   in DOMString typeArg,
   in boolean canBubbleArg,
   in boolean cancelableArg,
   in any dummyArg,
   in long detailArg,
   in long screenXArg,
   in long screenYArg,
   in long clientXArg,
   in long clientYArg,
   in boolean ctrlKeyArg,
   in boolean altKeyArg,
   in boolean shiftKeyArg,
   in boolean metaKeyArg,
   in unsigned short buttonArg,
   in EventTarget relatedTargetArg,
   in DataTransfer dataTransferArg
  );
};

有趣的是,當你去查 mozilla bugs 也會發現這個 bug 其實存在非常多年,討論串中還曾經在 2009 年發信給 W3C 來爭論上面提到的 HTML5 Drag and Drop API 的以下敘述定義是否正確

The spec should explicitly specify which MouseEvent properties are available during the various drag events to avoid assumptions.

The spec requires them to all be set on all drag events, currently.

This issue is related to whether or not a DragEvent is infact a MouseEvent.

It is.

📨 對詳細信件內容有興趣可以參考這個 👉 連結

但可惜的是,這個 bug 在 2022 年 mozilla 以下回覆後就暫時沒有下文了:

The patch here had some issues with how the coordinates were handled in some cases, but I don’t remember what they were and don’t have time right now to investigate.

The workaround is to just use the dragover event as described above to get behaviour similar to Chrome/Webkit/etc.

目前的解決方案

以目前 Firefox 官方的建議 workaround 是使用更上層的 dragover 事件來取得座標位置,這樣就可以在當前 element 的 drag 事件觸發時拿上層的 dragover 事件的座標位置作為參考。

例如:

// Initialize fallback client coordinates
let windowClientX = 0,
  windowClientY = 0;

// Listen for the "dragover" event on the window to capture the client coordinates.
// This serves as a workaround for Firefox's bug where drag events may return 0 for these values.
window.addEventListener("dragover", event => {
  windowClientX = event.clientX;
  windowClientY = event.clientY;
});

// Select the draggable block element
const draggableBlock = document.querySelector(".draggable-block");

// Attach a listener for the "drag" event on the draggable block.
// If the drag event does not provide valid client coordinates (Firefox bug),
// fall back to the coordinates captured during the window's "dragover" event.
draggableBlock.addEventListener("drag", function (e) {
  const clientX = e.clientX || windowClientX,
    clientY = e.clientY || windowClientY;
  // Here, you can use clientX and clientY for further processing, such as updating the UI.
});

🤷‍♂️ 又度過了一天前端日常:來自同樣的規格但各家瀏覽器不一樣的解讀與實作 🫠


Next Post
[LeetCode] 15. 3Sum