React中双击图片缩放后无法滚动到点击位置的修复问询
React图片查看器双击缩放后滚动位置修正
问题描述
实现React图片查看器的双击缩放功能时,双击放大后无法将点击位置正确滚动到容器可视区域内,滚动位置计算偏差,导致点击部分偏移。
现有代码
组件代码
import clsx from 'clsx' import React, { useRef, useState } from 'react' import classes from './styles.module.scss' import { IImageViewerProps } from '.' const DOUBLE_CLICK_ZOOM_RATE = 0.5 const ZOOM_RATE = 0.1 const DOUBLE_CLICK_MAX = 2 export function ImageViewer({ src, title }: IImageViewerProps) { const containerRef = useRef<HTMLDivElement>(null) const ref = useRef<HTMLImageElement>(null) const [imgScale, setImgScale] = useState(1) const [doubleClickCount, setDoubleClickCount] = useState(0) const [isDragging, setIsDragging] = useState(false) const [dragStart, setDragStart] = useState({ x: 0, y: 0 }) const handleDoubleClick = ( e: React.MouseEvent<HTMLImageElement, MouseEvent> ) => { e.stopPropagation() e.preventDefault() if (doubleClickCount < DOUBLE_CLICK_MAX) { const newScale = imgScale + DOUBLE_CLICK_ZOOM_RATE // After zooming, calculate the new scroll position to keep the clicked part visible if (containerRef.current && ref.current) { const containerRect = containerRef.current.getBoundingClientRect() const imageRect = ref.current.getBoundingClientRect() const x = (e.clientX - containerRect.left) / containerRect.width const y = (e.clientY - containerRect.top) / containerRect.height const newScrollLeft = x * (imageRect.width * newScale - containerRect.width) const newScrollTop = y * (imageRect.height * newScale - containerRect.height) console.log(newScrollLeft) containerRef.current.scrollLeft = 10 containerRef.current.scrollTop = newScrollTop setImgScale(newScale) setDoubleClickCount((prevState) => prevState + 1) } } else { setImgScale(1) setDoubleClickCount(0) } } const handleMouseDown = ( e: React.MouseEvent<HTMLImageElement, MouseEvent> ) => { e.stopPropagation() e.preventDefault() setIsDragging(true) setDragStart({ x: e.clientX, y: e.clientY }) } const handleMouseMove = ( e: React.MouseEvent<HTMLImageElement, MouseEvent> ) => { e.stopPropagation() e.preventDefault() if (isDragging && containerRef.current) { const deltaX = e.clientX - dragStart.x const deltaY = e.clientY - dragStart.y containerRef.current.scrollLeft -= deltaX containerRef.current.scrollTop -= deltaY setDragStart({ x: e.clientX, y: e.clientY }) } } const handleMouseUp = (e: React.MouseEvent<HTMLImageElement, MouseEvent>) => { e.stopPropagation() e.preventDefault() setIsDragging(false) } return ( <div className={clsx(classes.imgContainer)} ref={containerRef} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} > <img ref={ref} style={{ width: `${imgScale * 100}%` }} src={src} alt={title} className={clsx(classes.imgContainer__image)} onDoubleClick={handleDoubleClick} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} /> </div> ) }
SCSS样式
.imgContainer{ width: 100%; max-height: 30vh; overflow: scroll; &__image{ width: 100%; height: auto; max-height: 30vh; margin: 0 auto; } }
修正方案
关键问题分析
- 滚动位置计算逻辑错误:原代码使用容器宽度的比例计算滚动偏移,未考虑图片当前的滚动位置和缩放前后的坐标映射关系。
- DOM更新时机问题:原代码在图片缩放前设置滚动位置,此时图片尺寸尚未更新,导致滚动设置无效。
修改后的核心代码
新增状态保存双击位置,配合useEffect在图片缩放完成后调整滚动位置:
import clsx from 'clsx' import React, { useRef, useState, useEffect } from 'react' import classes from './styles.module.scss' import { IImageViewerProps } from '.' const DOUBLE_CLICK_ZOOM_RATE = 0.5 const ZOOM_RATE = 0.1 const DOUBLE_CLICK_MAX = 2 export function ImageViewer({ src, title }: IImageViewerProps) { const containerRef = useRef<HTMLDivElement>(null) const ref = useRef<HTMLImageElement>(null) const [imgScale, setImgScale] = useState(1) const [doubleClickCount, setDoubleClickCount] = useState(0) const [isDragging, setIsDragging] = useState(false) const [dragStart, setDragStart] = useState({ x: 0, y: 0 }) // 新增:保存双击时的鼠标位置 const [lastDoubleClickPos, setLastDoubleClickPos] = useState<{x: number, y: number} | null>(null) const handleDoubleClick = ( e: React.MouseEvent<HTMLImageElement, MouseEvent> ) => { e.stopPropagation() e.preventDefault() if (doubleClickCount < DOUBLE_CLICK_MAX) { const newScale = imgScale + DOUBLE_CLICK_ZOOM_RATE // 保存当前点击位置 setLastDoubleClickPos({x: e.clientX, y: e.clientY}) // 更新缩放比例 setImgScale(newScale) setDoubleClickCount(prev => prev + 1) } else { setImgScale(1) setDoubleClickCount(0) setLastDoubleClickPos(null) } } // 新增:监听缩放比例变化,调整滚动位置 useEffect(() => { if (!lastDoubleClickPos || !containerRef.current || !ref.current) return const container = containerRef.current const img = ref.current const containerRect = container.getBoundingClientRect() const {x: clientX, y: clientY} = lastDoubleClickPos // 计算点击位置相对于容器的偏移 const clickOffsetX = clientX - containerRect.left const clickOffsetY = clientY - containerRect.top // 计算点击位置在当前缩放图片上的绝对坐标(包含滚动偏移) const clickAbsX = clickOffsetX + container.scrollLeft const clickAbsY = clickOffsetY + container.scrollTop // 计算缩放比例的变化倍数 const scaleRatio = imgScale / (imgScale - DOUBLE_CLICK_ZOOM_RATE) // 缩放后点击位置的新绝对坐标 const newClickAbsX = clickAbsX * scaleRatio const newClickAbsY = clickAbsY * scaleRatio // 计算新的滚动位置,让点击点保持在容器原位置 const newScrollLeft = newClickAbsX - clickOffsetX const newScrollTop = newClickAbsY - clickOffsetY // 应用滚动位置 container.scrollLeft = newScrollLeft container.scrollTop = newScrollTop }, [imgScale, lastDoubleClickPos]) // 其余拖拽逻辑保持不变 const handleMouseDown = ( e: React.MouseEvent<HTMLImageElement, MouseEvent> ) => { e.stopPropagation() e.preventDefault() setIsDragging(true) setDragStart({ x: e.clientX, y: e.clientY }) } const handleMouseMove = ( e: React.MouseEvent<HTMLImageElement, MouseEvent> ) => { e.stopPropagation() e.preventDefault() if (isDragging && containerRef.current) { const deltaX = e.clientX - dragStart.x const deltaY = e.clientY - dragStart.y containerRef.current.scrollLeft -= deltaX containerRef.current.scrollTop -= deltaY setDragStart({ x: e.clientX, y: e.clientY }) } } const handleMouseUp = (e: React.MouseEvent<HTMLImageElement, MouseEvent>) => { e.stopPropagation() e.preventDefault() setIsDragging(false) } return ( <div className={clsx(classes.imgContainer)} ref={containerRef} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} > <img ref={ref} style={{ width: `${imgScale * 100}%` }} src={src} alt={title} className={clsx(classes.imgContainer__image)} onDoubleClick={handleDoubleClick} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} /> </div> ) }
修改说明
- 保存双击位置:新增
lastDoubleClickPos状态存储双击时的鼠标坐标,确保在图片缩放后能获取到正确的点击位置。 - 延迟滚动设置:使用
useEffect监听imgScale变化,确保在图片缩放完成、DOM更新后再计算并设置滚动位置。 - 正确的坐标映射:
- 计算点击位置在当前缩放图片上的绝对坐标(包含滚动偏移)
- 根据缩放比例计算该点在新缩放级别下的绝对位置
- 调整滚动位置,让点击点保持在容器中的原始位置,确保点击区域始终可见
内容的提问来源于stack exchange,提问作者Pouya Afraz
相关产品推荐
相关产品推荐

