L'environnement d'exploitation de ce tutoriel : système Windows 10, React version 18.0.0, ordinateur Dell G3.
Comment implémenter la mise à l'échelle en réaction ?
réagir à la mise à l'échelle et au panoramique de l'image (position, mise en œuvre de la transformation)
De nombreuses pages Web joindront des images pour compléter la description de la copie. Par exemple, lorsque l'on parle de l'adresse, une carte sera jointe à côté de celle-ci. , et sur la carte Marquez cette adresse. Si l'image ci-jointe est très petite et qu'il est difficile de voir clairement les informations spécifiques de l'adresse, certains chefs de produit concevront une fonction de panoramique, de zoom avant et arrière sur l'image. Cet article implémentera les fonctions ci-dessus une par une.
Sans plus tarder, voici les rendus :
Trois points de fonction principaux :
- Panorama d'image
- Zoom d'image
- Étiquette de station
Panoramique d'image
Le panoramique d'image peut surveiller la mise en œuvre de ces trois événements : onMouseDown, onMouseMove, onMouseUp
. L'événement onMouseDown
enregistre la position des coordonnées de chaque pression de la souris ; l'événement onMouseMove
calcule la distance de chaque traduction, et la distance plus le glissement La distance entre l'image précédente et l'élément parent sont égales à la distance entre l'image déplacée et l'élément parent ;onMouseUp
Lorsque l'événement est déclenché, déconnectez-vous ou empêchez l'exécution deonMouseDown, onMouseMove
Event pour empêcher l'image de se déplacer chaque fois que la souris y est déplacée. onMouseDown、onMouseMove、onMouseUp
。onMouseDown
事件记录每次鼠标按下的坐标位置;onMouseMove
事件计算出每次平移的距离,该距离加上拖动前图片距离父元素的距离就等于拖动后图片相对于父元素的距离;onMouseUp
事件触发时,注销或者不让执行onMouseDown、onMouseMove
事件,防止只要鼠标移入图片就会平移。
这三个事件需要阻止浏览器的默认行为,不然在移动时会自动打开图片。
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> ) }
图片缩放
图片缩放可以监听onWheel
事件,事件对象event
有一个记录滚轮滚动的属性deltaY
,当向上滚动时deltaY,向下滚动时<code>deltaY>0
。每次滚动修改其缩放的比例,同时更改transform
样式按比例进行缩放。
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
,默认是相对于图片中心进行缩放,但是在初始为了让图片在可视区域内水平垂直居中,使用了transform: translate(-50%, -50%);
,因此为了缩放时相对于图片中心点,需要设置matrix
的第5、6个参数矫正transformOrigin
,transform: matrix(${shrinkRate}, 0, 0, ${shrinkRate}, ${transformX}, ${transformY})
车站标注
首先,定义一个常量表示图标的坐标,这个坐标是相对于原始图片左上角的定位。
const imgInfo = { lableLeft: "1900", lableTop: "2000",}
这里,解释一下原始图的概念:
随便在网上查看一个图片元素,比如上面。1200 x 900是页面定的图片大小,但图片还有一个真实大小4535 x 3402。
要计算图标在没有平移缩放时的初始坐标之前,需要算出图片的缩放比例(不是上面的rate
):
/** 图片原始大小,默认设置为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;
图标初始的坐标就可以计算出:
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
,默认时相对图标的中心缩放的。为了保证图标随着图片一起缩放,那就必须使得图片和图标的缩放参照原点相同,图标的transformOrigin
const labelTransformOrigin = () => { return `${initial.width / 2 - Number(imgInfo.lableLeft) * imgScaleRateX}px ${ initial.height / 2 - Number(imgInfo.lableTop) * imgScaleRateY }px`; }Picture zoom
Picture zoom peut écouter l'événement onWheel
. L'objet événement event
a un attribut deltaY
qui enregistre la molette de défilement. Lors du défilement vers le haut, deltaY, <code>deltaY>0
lors du défilement vers le bas. Chaque fois que vous faites défiler, le rapport de mise à l'échelle est modifié et le style transform
est modifié pour être mis à l'échelle proportionnellement.
rrreeconst 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>
)
}
Si transformOrigin
n'est pas défini, la valeur par défaut est de mettre à l'échelle par rapport au centre de l'image, mais initialement afin de centrer l'image horizontalement et verticalement dans la zone visible, transform: translation(- 50%, - 50%);
, donc afin d'effectuer une mise à l'échelle par rapport au point central de l'image, vous devez définir les cinquième et sixième paramètres de la matrice
pour corriger transformOrigin
, transform : Matrix(${shrinkRate}, 0, 0, ${shrinkRate}, ${transformX}, ${transformY})