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
|
<svg
|
||||||
|
ref={zoom.svgRef}
|
||||||
className={styles.svg}
|
className={styles.svg}
|
||||||
onWheel={zoom.onWheel}
|
|
||||||
onPointerDown={zoom.onPointerDown}
|
onPointerDown={zoom.onPointerDown}
|
||||||
onPointerMove={zoom.onPointerMove}
|
onPointerMove={zoom.onPointerMove}
|
||||||
onPointerUp={zoom.onPointerUp}
|
onPointerUp={zoom.onPointerUp}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
interface ZoomPanState {
|
interface ZoomPanState {
|
||||||
scale: number;
|
scale: number;
|
||||||
@@ -20,19 +20,24 @@ export function useZoomPan() {
|
|||||||
const isPanning = useRef(false);
|
const isPanning = useRef(false);
|
||||||
const panStart = useRef({ x: 0, y: 0 });
|
const panStart = useRef({ x: 0, y: 0 });
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const svgRef = useRef<SVGSVGElement>(null);
|
||||||
|
|
||||||
const clampScale = (s: number) => Math.min(MAX_SCALE, Math.max(MIN_SCALE, s));
|
const clampScale = (s: number) => Math.min(MAX_SCALE, Math.max(MIN_SCALE, s));
|
||||||
|
|
||||||
/** Returns the CSS transform string for the content <g> element. */
|
/** Returns the CSS transform string for the content <g> element. */
|
||||||
const transform = `translate(${state.translateX}px, ${state.translateY}px) scale(${state.scale})`;
|
const transform = `translate(${state.translateX}px, ${state.translateY}px) scale(${state.scale})`;
|
||||||
|
|
||||||
const onWheel = useCallback(
|
// Attach wheel listener with { passive: false } so preventDefault() stops page scroll.
|
||||||
(e: React.WheelEvent<SVGSVGElement>) => {
|
// 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();
|
e.preventDefault();
|
||||||
const direction = e.deltaY < 0 ? 1 : -1;
|
const direction = e.deltaY < 0 ? 1 : -1;
|
||||||
const factor = 1 + direction * ZOOM_STEP;
|
const factor = 1 + direction * ZOOM_STEP;
|
||||||
|
|
||||||
const rect = (e.currentTarget as SVGSVGElement).getBoundingClientRect();
|
const rect = svg.getBoundingClientRect();
|
||||||
const cursorX = e.clientX - rect.left;
|
const cursorX = e.clientX - rect.left;
|
||||||
const cursorY = e.clientY - rect.top;
|
const cursorY = e.clientY - rect.top;
|
||||||
|
|
||||||
@@ -45,9 +50,10 @@ export function useZoomPan() {
|
|||||||
translateY: cursorY - scaleRatio * (cursorY - prev.translateY),
|
translateY: cursorY - scaleRatio * (cursorY - prev.translateY),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
[],
|
svg.addEventListener('wheel', handler, { passive: false });
|
||||||
);
|
return () => svg.removeEventListener('wheel', handler);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onPointerDown = useCallback(
|
const onPointerDown = useCallback(
|
||||||
(e: React.PointerEvent<SVGSVGElement>) => {
|
(e: React.PointerEvent<SVGSVGElement>) => {
|
||||||
@@ -158,10 +164,10 @@ export function useZoomPan() {
|
|||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
containerRef,
|
containerRef,
|
||||||
|
svgRef,
|
||||||
transform,
|
transform,
|
||||||
panTo,
|
panTo,
|
||||||
resetView,
|
resetView,
|
||||||
onWheel,
|
|
||||||
onPointerDown,
|
onPointerDown,
|
||||||
onPointerMove,
|
onPointerMove,
|
||||||
onPointerUp,
|
onPointerUp,
|
||||||
|
|||||||
Reference in New Issue
Block a user