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

Context 跟 Compound Component

Compound Component 的介紹應該可以不用提太細,詳細概念可以參考: https://www.patterns.dev/react/compound-pattern/

簡單來說是將複雜的相依關聯用同個 Context 串起來同步狀態,方便外部 caller 不需要手動傳入每個 props,只需要使用需要的部分就可以。

比較值得注意的是為了避免過度 rerender,這裡使用的 Context 只提供三個 state:

  • mode: 'lightbox' | 'thumbnail':代表現在在縮圖還是放大的狀態,Item、Overlay 會需要這個 state 來決定是否要顯示某些東西、或是是否需要綁定滑鼠、滾輪、觸控事件。
  • currentFocusIndex:目前開啟或關閉的 item index,這個會用來決定哪張圖片需要 ViewTransition。這個 state 只會在 openLightbox/closeLightbox 時從 ref 取出同步,不會常態更新觸發不必要的 re-render 跟 reflow。
  • isControlsHidden:用來控制互動控制元件(上/下一張圖片、關閉 Lightbox、影片播放控制)是否應該隱藏。

其他的狀態都是用 ref 的方式隱藏在 Context Provider 內,提供 set function 給 children component 使用。這樣可以確保各種動畫(捲動、縮放、平移)不會觸發 reflow。

狀態切換行為使用 React 19.2 ViewTransition 搭配 startTransition

前陣子把 nextjs 升級到 16 之後,最大的好處就是現在可以直接使用 React 19.2 的 ViewTransition Component 了。

過去的 Lightbox 雖然也有使用瀏覽器的 SPA view-transition API,但使用的方式有點獵奇。

在使用 ViewTransition 之後可以搭配 useTransition hook 的 startTransition 進行狀態及動畫的觸發管理,省掉不少時間。

但在搭配上一頁關閉圖片的功能時還是有點小問題沒辦法...

...顯示更多 (306 字)

use-gesture 及 event threshold

為了簡化 event binding,我們選擇使用簡單的 use-gesture。

事後才發現其實沒有簡化多少,可以完全自己來

這裡我們只簡單綁定幾個事件:

  • onClick:單擊關閉控制元件層、雙擊縮放
  • onDrag:拖曳(pan)圖片,根據縮放狀態不同有不同行為
  • onDragEnd:計算是否需要關閉 Lightbox,或是將圖片拉回 boundary 內。
  • onWheel/onWheelEnd 將 macOS 的觸控板事件綁到 onDrag/onDragEnd 上。實際上 onWheelEnd 是模擬事件,並沒有辦法精確知道手勢放開的時...
...顯示更多 (683 字)

offset 補償與 transform: translate/scale

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

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

為了解...

...顯示更多 (327 字)