From 31d7d1171eb242bce570386ccf594a49a869ea04 Mon Sep 17 00:00:00 2001 From: songyiping Date: Mon, 10 Aug 2026 16:13:26 +0800 Subject: [PATCH] fix: prevent browser zoom in image preview --- src/Preview/index.tsx | 3 +-- src/hooks/useMouseEvent.ts | 22 +++++++++++++++++----- tests/preview.test.tsx | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/Preview/index.tsx b/src/Preview/index.tsx index 03a43f47..5ce67464 100644 --- a/src/Preview/index.tsx +++ b/src/Preview/index.tsx @@ -219,7 +219,7 @@ const Preview: React.FC = props => { maxScale, onTransform, ); - const { isMoving, onMouseDown, onWheel } = useMouseEvent( + const { isMoving, onMouseDown } = useMouseEvent( imgRef, movable, open, @@ -284,7 +284,6 @@ const Preview: React.FC = props => { }} fallback={fallback} src={src} - onWheel={onWheel} onMouseDown={onMouseDown} onDoubleClick={onDoubleClick} onTouchStart={onTouchStart} diff --git a/src/hooks/useMouseEvent.ts b/src/hooks/useMouseEvent.ts index 96b22b25..915ada30 100644 --- a/src/hooks/useMouseEvent.ts +++ b/src/hooks/useMouseEvent.ts @@ -1,4 +1,4 @@ -import { warning } from '@rc-component/util'; +import { useEvent, warning } from '@rc-component/util'; import type React from 'react'; import { useEffect, useRef, useState } from 'react'; import getFixScaleEleTransPosition from '../getFixScaleEleTransPosition'; @@ -83,8 +83,9 @@ export default function useMouseEvent( } }; - const onWheel = (event: React.WheelEvent) => { - if (!open || !wheel || event.deltaY == 0) return; + const onWheel = useEvent((event: WheelEvent) => { + if (!open || !wheel || event.target !== imgRef.current || event.deltaY == 0) return; + event.preventDefault(); // Scale ratio depends on the deltaY size const scaleRatio = Math.abs(event.deltaY / 100); // Limit the maximum scale ratio @@ -95,7 +96,19 @@ export default function useMouseEvent( ratio = BASE_SCALE_RATIO / ratio; } dispatchZoomChange(ratio, 'wheel', event.clientX, event.clientY); - }; + }); + + // React delegates wheel events through a passive listener, so use a native listener + // to prevent the browser's trackpad zoom gesture. + useEffect(() => { + if (open && wheel) { + window.addEventListener('wheel', onWheel, { passive: false }); + } + + return () => { + window.removeEventListener('wheel', onWheel); + }; + }, [open, wheel, onWheel]); useEffect(() => { if (movable) { @@ -134,6 +147,5 @@ export default function useMouseEvent( onMouseDown, onMouseMove, onMouseUp, - onWheel, }; } diff --git a/tests/preview.test.tsx b/tests/preview.test.tsx index 2d48c43d..4e036ba1 100644 --- a/tests/preview.test.tsx +++ b/tests/preview.test.tsx @@ -323,6 +323,39 @@ describe('Preview', () => { }); }); + it('should prevent browser zoom when zooming with a trackpad', () => { + const { container } = render( + , + ); + + fireEvent.click(container.querySelector('.rc-image')!); + act(() => { + jest.runAllTimers(); + }); + + const previewImage = document.querySelector('.rc-image-preview-img'); + let defaultPrevented = false; + window.addEventListener( + 'wheel', + event => { + defaultPrevented = event.defaultPrevented; + }, + { once: true }, + ); + fireEvent.wheel(previewImage!, { ctrlKey: true, deltaY: -50 }); + act(() => { + jest.runAllTimers(); + }); + + expect(defaultPrevented).toBe(true); + expect(previewImage).toHaveStyle({ + transform: 'translate3d(0px, 0px, 0) scale3d(1.25, 1.25, 1) rotate(0deg)', + }); + }); + it('should not zoom with wheel when wheel is false', () => { const { container } = render(