fix: use non-passive wheel listener to prevent page scroll during diagram zoom
React's onWheel is passive by default, so preventDefault() doesn't stop
page scrolling. Attach native wheel listener with { passive: false } via
useEffect instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -193,8 +193,8 @@ export function ProcessDiagram({
|
||||
)}
|
||||
|
||||
<svg
|
||||
ref={zoom.svgRef}
|
||||
className={styles.svg}
|
||||
onWheel={zoom.onWheel}
|
||||
onPointerDown={zoom.onPointerDown}
|
||||
onPointerMove={zoom.onPointerMove}
|
||||
onPointerUp={zoom.onPointerUp}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface ZoomPanState {
|
||||
scale: number;
|
||||
@@ -20,19 +20,24 @@ export function useZoomPan() {
|
||||
const isPanning = useRef(false);
|
||||
const panStart = useRef({ x: 0, y: 0 });
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const svgRef = useRef<SVGSVGElement>(null);
|
||||
|
||||
const clampScale = (s: number) => Math.min(MAX_SCALE, Math.max(MIN_SCALE, s));
|
||||
|
||||
/** Returns the CSS transform string for the content <g> element. */
|
||||
const transform = `translate(${state.translateX}px, ${state.translateY}px) scale(${state.scale})`;
|
||||
|
||||
const onWheel = useCallback(
|
||||
(e: React.WheelEvent<SVGSVGElement>) => {
|
||||
// Attach wheel listener with { passive: false } so preventDefault() stops page scroll.
|
||||
// React's onWheel is passive by default and cannot prevent scrolling.
|
||||
useEffect(() => {
|
||||
const svg = svgRef.current;
|
||||
if (!svg) return;
|
||||
const handler = (e: WheelEvent) => {
|
||||
e.preventDefault();
|
||||
const direction = e.deltaY < 0 ? 1 : -1;
|
||||
const factor = 1 + direction * ZOOM_STEP;
|
||||
|
||||
const rect = (e.currentTarget as SVGSVGElement).getBoundingClientRect();
|
||||
const rect = svg.getBoundingClientRect();
|
||||
const cursorX = e.clientX - rect.left;
|
||||
const cursorY = e.clientY - rect.top;
|
||||
|
||||
@@ -45,9 +50,10 @@ export function useZoomPan() {
|
||||
translateY: cursorY - scaleRatio * (cursorY - prev.translateY),
|
||||
};
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
};
|
||||
svg.addEventListener('wheel', handler, { passive: false });
|
||||
return () => svg.removeEventListener('wheel', handler);
|
||||
}, []);
|
||||
|
||||
const onPointerDown = useCallback(
|
||||
(e: React.PointerEvent<SVGSVGElement>) => {
|
||||
@@ -158,10 +164,10 @@ export function useZoomPan() {
|
||||
return {
|
||||
state,
|
||||
containerRef,
|
||||
svgRef,
|
||||
transform,
|
||||
panTo,
|
||||
resetView,
|
||||
onWheel,
|
||||
onPointerDown,
|
||||
onPointerMove,
|
||||
onPointerUp,
|
||||
|
||||
Reference in New Issue
Block a user