Post
2025年12月2日 下午09:31

offset 補償與 transform: translate/scale

為了使用瀏覽器本身的 HW accel,我們改用 transform: translate/scale 控制圖片位置。同時也把圖片的 CSS position 固定在 absolute,讓圖片在平常的垂直水平置中過渡到 freeform 之間的動畫不會因為 reflow 而卡頓。

不過,同時帶來一個小問題:scale 過後的圖片是由本身的 origin 為中心平移,圖片本身座標空間不能直接對應到畫面上的顯示座標空間。也就是說,double tap 跟 pinch 事件取得的座標不能夠直接用來更新 transform。

為了解決這個問題我們使用了前端常用的空間映射轉換公式:

content = (screen − origin − prevTx) / prevScale

固定 content 的狀況下可得:

(screen − origin − prevTx) / prevScale = (screen − origin − newTx) / nextScale

搭配我們使用的 origin 是 50% 50%,也就是

originX = elementLeft + elementWidth / 2

originY = elementTop + elementHeight / 2

最終會得到下圖公式:

tx, ty 是縮放後的 offset,我們就可以代入 CSS trasnform。

useTransform 與 MotionValue

use-gesture 的 handler 相對來說有效率,但不可避免的必須依賴 gesture-mode 對事件進行不同處理。

我們可以將目前圖片的 x, y 儲存在 useState 內交由 React 管理,不過我們實際上不需要因為圖片動了一個 pixel 就觸發 rerender。

我們也可以在各個 handler 內計算目前應該有的 gesture-mode,但這時除了邏輯重複外,也可能會有 race condition。

因此,這邊我們將 x, y 保留在 MotionValue 中,由 motion 管理動畫,並且藉由一個 useTransform...

...顯示更多 (648 字)

useResizeObserver

圖片的顯示範圍並不是固定的,例如當進入放大模式後左右兩邊的黑邊會需要消失。這時使用 ResizeObserver 解耦就變得很方便。(而且效能也不錯)

方便使用,我們參考 useHooks 的 useIntersectionObserver 另外寫了一個 useResizeObserver 觸發 rerender。

// useResizeObserver hook to observe size changes of a DOM element, return a ResizeObserver entry

export const useResizeObserve...

...顯示更多 (547 字)

Lightbox pointer event 穿透

黑邊關閉圖片是這次重構的重點之一,但也是最麻煩的。

以附圖為例:

  • 橘色斜線區域是應該要觸發關閉的黑邊區域。
  • 綠色網狀區是圖片本身,應該要觸發拖曳或縮放效果。而且拖曳時橘色區域應該關閉。
  • 紅色是切換圖片的手勢區域。

理想的做法是在橘色區域綁 onClick⋯⋯但 HTML 沒有 event mask 這種東西。而且黑邊的大小跟位置也不固定,用 event position 判斷並不實際。

CSS 有 pointer-event: none 可以使用,理論上在 Lightbox wrapper 設定事件穿透,讓後面的 backdrop 收得到事件就沒有問...

...顯示更多 (367 字)