React onWheel 預設 passive 必須綁原生事件才能 preventDefault

React 17+ 的合成 wheel/touch 事件預設是 passive: true,在裡面呼叫 e.preventDefault() 不會生效(瀏覽器會印警告)。要阻止滾輪預設行為(例如阻止頁面滾動、做縮放),必須跳過 React 合成事件、直接綁原生 listener 並指定 passive: false

做法

const handleWheel = useCallback((e: WheelEvent) => {
  e.preventDefault()
  // 縮放邏輯...
}, [])
 
useEffect(() => {
  const el = containerRef.current
  if (!el) return
  el.addEventListener('wheel', handleWheel, { passive: false })
  return () => el.removeEventListener('wheel', handleWheel)
}, [handleWheel])

為什麼 React 這樣做

passive: true 告訴瀏覽器「我不會阻止預設行為」,瀏覽器就能立刻開始滾動、不必等 JS 跑完。這對普通頁面是效能大勝利。但對需要自訂滾輪行為的元件(無限畫布、自製 carousel、自製 zoom)就是阻礙 — 而 React 為了避免每個人都踩到效能洞,預設選了 passive。

同樣會中招的事件

  • wheel
  • touchstarttouchmove

onClickonPointerDown 等不受影響。

偵測方式

Chrome DevTools Console 會印:

[Intervention] Unable to preventDefault inside passive event listener
due to target being treated as passive.

看到這行就是中招了,改成原生 listener。

相關