refactor(canvas): install wheel zoom via a passive-safe native listener
Move canvas wheel zoom off the React onWheel prop into a manually attached non-passive listener so preventDefault reliably suppresses page scroll, and read the viewport from the ref to keep the handler referentially stable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,7 @@ import { CanvasColorPopover } from "./canvas/CanvasColorPopover";
|
||||
import { buildAlignmentGuides, clamp, minGroupNodeScale, nodeBounds, nodeIntersectsRect, normalizeScreenRect, pointInsideNode, scaleGroupNode, screenBoundsForNodes, screenRectToWorldRect, type AlignmentGuide, type NodeAlignment, type NodeDistribution, type NodeTransform, type ResizeHandle, type SelectionBox } from "@/ui/pages/CanvasWorkspace/canvas/canvasGeometry";
|
||||
import { alignNodes, attachNodesToContainingFrames, autoLayoutAroundNode as autoLayoutAroundNodeOp, autoLayoutNodes, canResizeCanvasNode, canUseNodeContextMenu, cloneCanvasNode, distributeNodes, groupNodes, isCanvasPenStrokeNode, isFrameChildNode, isFrameContainerNode, isRealCanvasImageNode, moveNodesByDelta, nodeIdsWithFrameChildren, orderedCanvasNodesForRender, patchFrameStyleNode, removeNodesById, reorderLayer as reorderLayerOp, reorderNode as reorderNodeOp, resizeFrameWithChildren, resizeVisualNodeByHandle, toggleSetValues, ungroupNodes, updateNodeContent as updateNodeContentOp, updateNodeTransform as updateNodeTransformOp, visibleCanvasNodes as getVisibleCanvasNodes, type LayerReorderRequest } from "@/ui/pages/CanvasWorkspace/canvas/canvasNodeOps";
|
||||
import { CanvasSelectionFrame } from "@/ui/pages/CanvasWorkspace/canvas/CanvasSelectionFrame";
|
||||
import { installCanvasWheelListener } from "./canvas/canvasWheel";
|
||||
import { beginFrameDrawing, FrameDraftOverlay, type FrameDraft, type FrameWorldRect } from "./canvas/FrameToolOverlay";
|
||||
import { FrameStyleToolbar } from "./canvas/FrameStyleToolbar";
|
||||
import { defaultFrameStrokeColor, defaultFrameStrokeStyle, defaultFrameStrokeWidth, normalizeHexColor } from "./canvas/frameStyle";
|
||||
@@ -1233,26 +1234,33 @@ export function CanvasWorkspace({ projectId, shareAccess = ownerWorkspaceAccess
|
||||
return () => window.clearTimeout(timer);
|
||||
}, [imageActionMode, locale, moveSelection, selectedImageNode?.content]);
|
||||
|
||||
const handleWheel = (event: React.WheelEvent<HTMLDivElement>) => {
|
||||
const handleWheel = useCallback((event: WheelEvent) => {
|
||||
if (shouldBypassCanvasWheel(event.target)) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
const rect = containerRef.current?.getBoundingClientRect();
|
||||
if (!rect) return;
|
||||
const currentViewport = viewportRef.current;
|
||||
const factor = Math.pow(1.1, -event.deltaY / 120);
|
||||
const nextScale = clamp(viewport.k * factor, minCanvasZoom, maxCanvasZoom);
|
||||
const nextScale = clamp(currentViewport.k * factor, minCanvasZoom, maxCanvasZoom);
|
||||
const mouseX = event.clientX - rect.left;
|
||||
const mouseY = event.clientY - rect.top;
|
||||
const worldX = (mouseX - viewport.x) / viewport.k;
|
||||
const worldY = (mouseY - viewport.y) / viewport.k;
|
||||
const worldX = (mouseX - currentViewport.x) / currentViewport.k;
|
||||
const worldY = (mouseY - currentViewport.y) / currentViewport.k;
|
||||
setViewport({
|
||||
x: mouseX - worldX * nextScale,
|
||||
y: mouseY - worldY * nextScale,
|
||||
k: nextScale
|
||||
});
|
||||
setDirty(true);
|
||||
};
|
||||
}, [setViewport, viewportRef]);
|
||||
|
||||
useEffect(() => {
|
||||
const stage = containerRef.current;
|
||||
if (!stage) return;
|
||||
return installCanvasWheelListener(stage, handleWheel);
|
||||
}, [handleWheel, project?.id]);
|
||||
|
||||
const zoomCanvasTo = (nextZoom: number) => {
|
||||
const current = viewportRef.current;
|
||||
@@ -3603,7 +3611,6 @@ export function CanvasWorkspace({ projectId, shareAccess = ownerWorkspaceAccess
|
||||
className={`canvas-stage ${showGrid ? "show-grid" : ""} ${canvasFileDragging ? "dragging-upload" : ""} ${activeTool === "pan" ? "is-pan-tool" : ""} ${canvasPanning ? "is-panning" : ""} ${activeTool === "frame" ? "is-frame-tool" : ""} ${activeTool === "shape" ? "is-shape-tool" : ""} ${activeTool === "pen" ? "is-pen-tool" : ""} ${activeTool === "text" ? "is-text-tool" : ""}`}
|
||||
style={{ "--canvas-background": canvasBackgroundColor } as CSSProperties}
|
||||
ref={containerRef}
|
||||
onWheel={handleWheel}
|
||||
onPointerDown={canEdit ? handleBackgroundPointerDown : undefined}
|
||||
onPointerMove={handleCanvasPointerMove}
|
||||
onDragOver={canEdit ? handleCanvasDragOver : undefined}
|
||||
|
||||
Reference in New Issue
Block a user