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。
同樣會中招的事件
wheeltouchstart、touchmove
onClick、onPointerDown 等不受影響。
偵測方式
Chrome DevTools Console 會印:
[Intervention] Unable to preventDefault inside passive event listener
due to target being treated as passive.
看到這行就是中招了,改成原生 listener。