Die Betriebsumgebung dieses Tutorials: Windows 10-System, Reaktionsversion 18.0.0, Dell G3-Computer.
Wie implementiert man die Skalierung in React?
Reagieren Sie auf das Zoomen und Schwenken von Bildern (Position, Transformationsimplementierung)
Viele Webseiten hängen einige Bilder an, um die Beschreibung der Kopie zu ergänzen. Wenn beispielsweise über die Adresse gesprochen wird, wird daneben eine Karte angehängt , und auf der Karte Markieren Sie diese Adresse. Wenn das angehängte Bild sehr klein ist und es schwierig ist, die spezifischen Informationen der Adresse klar zu erkennen, entwickeln einige Produktmanager eine Funktion zum Schwenken, Vergrößern und Verkleinern des Bildes. In diesem Artikel werden die oben genannten Funktionen einzeln implementiert. 🔜 : onMouseDown, onMouseMove, onMouseUp
. Das Ereignis onMouseDown
zeichnet die Koordinatenposition jedes Mausklicks auf; das Ereignis onMouseMove
berechnet den Abstand jeder Verschiebung und den Abstand plus Ziehen Das vorherige Bild und das übergeordnete Element entsprechen dem Abstand zwischen dem gezogenen Bild und dem übergeordneten Element;onMouseUp
Wenn das Ereignis ausgelöst wird, melden Sie sich ab oder verhindern Sie die Ausführung vononMouseDown, onMouseMove
Ereignis, um zu verhindern, dass das Bild geschwenkt wird, wenn die Maus hinein bewegt wird.
Diese drei Ereignisse müssen das Standardverhalten des Browsers verhindern, sonst wird das Bild beim Verschieben automatisch geöffnet.
const WIDTH = 1200;const HEIGHT = 900;const DynamicStyle= () => { const imgRef = React.createRef<HTMLImageElement>(); /** 图片样式 */ const [imgStyle, setImgStyle] = useState<React.CSSProperties>({}); /** 记录鼠标是否按下 */ const [mouseDowmFlag, setMouseDowmFlag] = useState(false); /** 记录鼠标按下的坐标 */ const [mouseDowmPos, setMouseDowmPos] = useState<{x: number, y: number}>({x: 0, y: 0}) /** 鼠标可视区域时,重置鼠标按下的布尔值为false */ useEffect(() => { document.onmouseover = () => { if (mouseDowmFlag) { setMouseDowmFlag(false); } }; return () => { document.onmouseover = null; }; }, [mouseDowmFlag]) /** 平移 */ const handleMouseDown = (event: React.MouseEvent<HTMLImageElement>) => { const { clientX, clientY } = event; event.stopPropagation(); event.preventDefault(); // 阻止浏览器默认行为,拖动会打开图片 setMouseDowmFlag(true); // 控制只有在鼠标按下后才会执行mousemove setMouseDowmPos({ x: clientX, y: clientY, }); }; const handleMouseMove = (event: React.MouseEvent<HTMLImageElement>) => { event.stopPropagation(); event.preventDefault(); const { clientX, clientY } = event; const diffX = clientX - mouseDowmPos.x; const diffY = clientY - mouseDowmPos.y; if (!mouseDowmFlag || (diffX === 0 && diffY === 0)) return; const { offsetLeft, offsetTop } = imgRef.current as HTMLImageElement; const offsetX = parseInt(`${diffX + offsetLeft}`, 10); const offsetY = parseInt(`${diffY + offsetTop}`, 10); setMouseDowmPos({ x: clientX, y: clientY, }); setImgStyle({ ...imgStyle, left: offsetX, top: offsetY, }); }; const handleMouseUp = (event: React.MouseEvent<HTMLImageElement>) => { event.stopPropagation(); event.preventDefault(); setMouseDowmFlag(false); }; return ( <div className={styles.imgArea}> <img src={mapImg} alt='part' ref={imgRef} height={HEIGHT} style={imgStyle} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} > </img> </div> ) }
Picture zoom
Picture zoom kann auf das EreignisonWheel
hören. Das Ereignisobjekt event
hat ein Attribut deltaY
, das das Scrollrad aufzeichnet. Beim Scrollen nach oben deltaY, <code>deltaY>0
beim Scrollen nach unten. Bei jedem Scrollen ändert sich das Skalierungsverhältnis und der transform
-Stil wird entsprechend angepasst.
const WIDTH = 1200;const HEIGHT = 900;const SCALE = 0.2;const DynamicStyle= () => { const imgRef = React.createRef<HTMLImageElement>(); /** 初始化缩放比例,默认为1 */ const [rate, setRate] = useState(1); /** 图片样式 */ const [imgStyle, setImgStyle] = useState<React.CSSProperties>({}); /** 记录鼠标是否按下 */ const [mouseDowmFlag, setMouseDowmFlag] = useState(false); /** 记录鼠标按下的坐标 */ const [mouseDowmPos, setMouseDowmPos] = useState<{x: number, y: number}>({x: 0, y: 0}) /** 图片现在大小 */ const [initial, setInitial] = useState<{width: number, height: number}>({width: WIDTH, height: HEIGHT}); useEffect(() => { const { naturalWidth, naturalHeight, width, height } = imgRef.current as HTMLImageElement; setInitial({ width, height }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []) // console.log(natural, initial) useEffect(() => { document.onmouseover = () => { if (mouseDowmFlag) { setMouseDowmFlag(false); } }; return () => { document.onmouseover = null; }; }, [mouseDowmFlag]) /** 缩放 */ const handleWheelImage = (event: React.WheelEvent<HTMLImageElement>) => { // 向上为负,向下为正 const bigger = event.deltaY > 0 ? -1 : 1; // transform偏移量 const transformX = -initial.width / 2; const transformY = -initial.height / 2; if (bigger > 0 && rate < 2) { const enlargeRate = rate + SCALE; setImgStyle({ ...imgStyle, transform: `matrix(${enlargeRate}, 0, 0, ${enlargeRate}, ${transformX}, ${transformY})`, // 默认以图片中心为原点进行缩放 }); setRate(enlargeRate); } else if (bigger < 0 && rate > 1) { const shrinkRate = rate - SCALE; setImgStyle({ ...imgStyle, transform: `matrix(${shrinkRate}, 0, 0, ${shrinkRate}, ${transformX}, ${transformY})`, }); setRate(shrinkRate); } } /** 平移 */ const handleMouseDown = (event: React.MouseEvent<HTMLImageElement>) => { const { clientX, clientY } = event; event.stopPropagation(); event.preventDefault(); // 阻止浏览器默认行为,拖动会打开图片 setMouseDowmFlag(true); // 控制只有在鼠标按下后才会执行mousemove setMouseDowmPos({ x: clientX, y: clientY, }); }; const handleMouseMove = (event: React.MouseEvent<HTMLImageElement>) => { event.stopPropagation(); event.preventDefault(); const { clientX, clientY } = event; const diffX = clientX - mouseDowmPos.x; const diffY = clientY - mouseDowmPos.y; if (!mouseDowmFlag || (diffX === 0 && diffY === 0)) return; const { offsetLeft, offsetTop } = imgRef.current as HTMLImageElement; const offsetX = parseInt(`${diffX + offsetLeft}`, 10); const offsetY = parseInt(`${diffY + offsetTop}`, 10); setMouseDowmPos({ x: clientX, y: clientY, }); setImgStyle({ ...imgStyle, left: offsetX, top: offsetY, }); }; const handleMouseUp = (event: React.MouseEvent<HTMLImageElement>) => { event.stopPropagation(); event.preventDefault(); setMouseDowmFlag(false); }; return ( <div className={styles.imgArea}> <img src={mapImg} alt='part' height={HEIGHT} style={imgStyle} ref={imgRef} onWheel={handleWheelImage} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} > </img> </div> ) }
.imgArea { position: relative; width: 1200px; height: 900px; margin: auto; border: 1px solid #da2727; overflow: hidden; & > img { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); cursor: move; } }
transformOrigin
nicht festgelegt ist, wird standardmäßig relativ zur Bildmitte skaliert, zunächst jedoch, um das Bild horizontal und vertikal im sichtbaren Bereich zu zentrieren, transform: Translate(- 50 %, - 50 %);
. Um also relativ zum Mittelpunkt des Bildes zu skalieren, müssen Sie den fünften und sechsten Parameter von matrix
so einstellen, dass korrigiert wird transformOrigin
, transform : matrix(${shrinkRate}, 0, 0, ${shrinkRate}, ${transformX}, ${transformY})
const imgInfo = { lableLeft: "1900", lableTop: "2000",}Erklären Sie hier das Konzept des Originalbilds:
Schauen Sie sich einfach ein Bildelement im Internet an, wie das oben. 1200 x 900 ist die auf der Seite angegebene Bildgröße, das Bild hat jedoch auch eine tatsächliche Größe von 4535 x 3402.
onMouseDown、onMouseMove、onMouseUp
。onMouseDown
事件记录每次鼠标按下的坐标位置;onMouseMove
事件计算出每次平移的距离,该距离加上拖动前图片距离父元素的距离就等于拖动后图片相对于父元素的距离;onMouseUp
事件触发时,注销或者不让执行onMouseDown、onMouseMove
事件,防止只要鼠标移入图片就会平移。
这三个事件需要阻止浏览器的默认行为,不然在移动时会自动打开图片。
/** 图片原始大小,默认设置为1是防止计算图片原始大小与初始大小比例出现无穷大 */const [natural, setNatural] = useState<{width: number, height: number}>({width: 1, height: 1});/** 图片现在大小 */const [initial, setInitial] = useState<{width: number, height: number}>({width: WIDTH, height: HEIGHT});useEffect(() => { const { naturalWidth, naturalHeight, width, height } = imgRef.current as HTMLImageElement; setNatural({ width: naturalWidth, height: naturalHeight }); setInitial({ width, height }); // eslint-disable-next-line react-hooks/exhaustive-deps}, []) // 初始图片缩放比例(图片有原始的图片大小)const imgScaleRateX = initial.width / natural.width;const imgScaleRateY = initial.height / natural.height;
图片缩放
图片缩放可以监听onWheel
事件,事件对象event
有一个记录滚轮滚动的属性deltaY
,当向上滚动时deltaY,向下滚动时<code>deltaY>0
。每次滚动修改其缩放的比例,同时更改transform
样式按比例进行缩放。
const labelLeft = parseInt(`${imgInfo.lableLeft}`, 10) * imgScaleRateX;const labelTop = parseInt(`${imgInfo.lableTop}`, 10) * imgScaleRateY;
// 图标相对父元素坐标 = 图标位置坐标 + 图片坐标const labelLeft = parseInt(`${imgInfo.lableLeft}`, 10) * imgScaleRateX + Number(imgStyle.left || WIDTH / 2); const labelTop = parseInt(`${imgInfo.lableTop}`, 10) * imgScaleRateY + Number(imgStyle.top || HEIGHT / 2);
如果没有设置transformOrigin
,默认是相对于图片中心进行缩放,但是在初始为了让图片在可视区域内水平垂直居中,使用了transform: translate(-50%, -50%);
,因此为了缩放时相对于图片中心点,需要设置matrix
的第5、6个参数矫正transformOrigin
,transform: matrix(${shrinkRate}, 0, 0, ${shrinkRate}, ${transformX}, ${transformY})
车站标注
首先,定义一个常量表示图标的坐标,这个坐标是相对于原始图片左上角的定位。
const labelTransformOrigin = () => { return `${initial.width / 2 - Number(imgInfo.lableLeft) * imgScaleRateX}px ${ initial.height / 2 - Number(imgInfo.lableTop) * imgScaleRateY }px`; }
这里,解释一下原始图的概念:
随便在网上查看一个图片元素,比如上面。1200 x 900是页面定的图片大小,但图片还有一个真实大小4535 x 3402。
要计算图标在没有平移缩放时的初始坐标之前,需要算出图片的缩放比例(不是上面的rate
):
const imgInfo = { lableLeft: "1900", lableTop: "2000", } const WIDTH = 1200; const HEIGHT = 900; const SCALE = 0.2; const DynamicStyle= () => { const imgRef = React.createRef<HTMLImageElement>(); /** 初始化缩放比例,默认为1 */ const [rate, setRate] = useState(1); /** 图片样式 */ const [imgStyle, setImgStyle] = useState<React.CSSProperties>({}); /** 记录鼠标是否按下 */ const [mouseDowmFlag, setMouseDowmFlag] = useState(false); /** 记录鼠标按下的坐标 */ const [mouseDowmPos, setMouseDowmPos] = useState<{x: number, y: number}>({x: 0, y: 0}) /** 图片原始大小,默认设置为1是防止计算图片原始大小与初始大小比例出现无穷大 */ const [natural, setNatural] = useState<{width: number, height: number}>({width: 1, height: 1}); /** 图片现在大小 */ const [initial, setInitial] = useState<{width: number, height: number}>({width: WIDTH, height: HEIGHT}); useEffect(() => { const { naturalWidth, naturalHeight, width, height } = imgRef.current as HTMLImageElement; setNatural({ width: naturalWidth, height: naturalHeight }); setInitial({ width, height }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []) useEffect(() => { document.onmouseover = () => { if (mouseDowmFlag) { setMouseDowmFlag(false); } }; return () => { document.onmouseover = null; }; }, [mouseDowmFlag]) /** 缩放 */ const handleWheelImage = (event: React.WheelEvent<HTMLImageElement>) => { // 向上为负,向下为正 const bigger = event.deltaY > 0 ? -1 : 1; // transform偏移量 const transformX = -initial.width / 2; const transformY = -initial.height / 2; if (bigger > 0 && rate < 2) { const enlargeRate = rate + SCALE; setImgStyle({ ...imgStyle, transform: `matrix(${enlargeRate}, 0, 0, ${enlargeRate}, ${transformX}, ${transformY})`, // 默认以图片中心为原点进行缩放 }); setRate(enlargeRate); } else if (bigger < 0 && rate > 1) { const shrinkRate = rate - SCALE; setImgStyle({ ...imgStyle, transform: `matrix(${shrinkRate}, 0, 0, ${shrinkRate}, ${transformX}, ${transformY})`, }); setRate(shrinkRate); } } /** 平移 */ const handleMouseDown = (event: React.MouseEvent<HTMLImageElement>) => { const { clientX, clientY } = event; event.stopPropagation(); event.preventDefault(); // 阻止浏览器默认行为,拖动会打开图片 setMouseDowmFlag(true); // 控制只有在鼠标按下后才会执行mousemove setMouseDowmPos({ x: clientX, y: clientY, }); }; const handleMouseMove = (event: React.MouseEvent<HTMLImageElement>) => { event.stopPropagation(); event.preventDefault(); const { clientX, clientY } = event; const diffX = clientX - mouseDowmPos.x; const diffY = clientY - mouseDowmPos.y; if (!mouseDowmFlag || (diffX === 0 && diffY === 0)) return; const { offsetLeft, offsetTop } = imgRef.current as HTMLImageElement; const offsetX = parseInt(`${diffX + offsetLeft}`, 10); const offsetY = parseInt(`${diffY + offsetTop}`, 10); setMouseDowmPos({ x: clientX, y: clientY, }); setImgStyle({ ...imgStyle, left: offsetX, top: offsetY, }); }; const handleMouseUp = (event: React.MouseEvent<HTMLImageElement>) => { event.stopPropagation(); event.preventDefault(); setMouseDowmFlag(false); }; // 初始图片缩放比例(图片有原始的图片大小) const imgScaleRateX = initial.width / natural.width; const imgScaleRateY = initial.height / natural.height; const labelTransformOrigin = () => { return `${initial.width / 2 - Number(imgInfo.lableLeft) * imgScaleRateX}px ${ initial.height / 2 - Number(imgInfo.lableTop) * imgScaleRateY }px`; } /** 图标位置计算 */ const labelStyle = (): React.CSSProperties => { const transformX = -initial.width / 2; const transformY = -initial.height / 2; // 图标相对父元素坐标 = 图标初始位置坐标 + 平移量 const labelLeft = parseInt(`${imgInfo.lableLeft}`, 10) * imgScaleRateX + Number(imgStyle.left || WIDTH / 2); const labelTop = parseInt(`${imgInfo.lableTop}`, 10) * imgScaleRateY + Number(imgStyle.top || HEIGHT / 2); return { left: labelLeft, top: labelTop, transformOrigin: labelTransformOrigin(), transform: `matrix(${rate}, 0, 0, ${rate}, ${transformX}, ${transformY})`, } } return ( <div className={styles.imgArea}> <img src={mapImg} alt='part' height={HEIGHT} style={imgStyle} ref={imgRef} onWheel={handleWheelImage} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} > </img> <span className={styles.label} style={labelStyle()}></span> </div> ) }
图标初始的坐标就可以计算出:
rrreee当图片平移时,图标也需要跟着平移,这是的坐标计算:
rrreee当图片缩放时,图标需要随着图片一起缩放。如果没有对图标设置transformOrigin
,默认时相对图标的中心缩放的。为了保证图标随着图片一起缩放,那就必须使得图片和图标的缩放参照原点相同,图标的transformOrigin
Um die Anfangskoordinaten des Symbols ohne Übersetzung und Skalierung zu berechnen, müssen Sie das Skalierungsverhältnis des Bildes berechnen (nicht die Rate
oben):
Die Anfangskoordinaten des Symbols können sein berechnet: rrreee
Wenn das Bild übersetzt wird, muss auch das Symbol übersetzt werden. Dies ist die Koordinatenberechnung:rrreee