React でスケーリングを実現するにはどうすればよいですか?
React 画像のスケーリングとパン (位置、変換の実装)
多くの Web ページでは、コピーの説明を補足するためにいくつかの画像が添付されます。たとえば、次のようになります。住所について話すときは、その隣に地図が添付され、地図上に住所がマークされます。添付された写真が小さすぎて、住所の特定の情報をはっきりと見ることが難しい場合、製品マネージャーによっては、写真のパン、ズームイン、ズームアウトの機能を設計することがあります。この記事では上記の機能を一つずつ実装していきます。 早速、最初にレンダリングをお見せします: 主な 3 つの機能ポイント:- 画像のパン
- 画像ズーム
- ステーション ラベル
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> ) }PicturezoomPicturezoom は
onWheel イベントをリッスンできます。イベント オブジェクト
event には次の属性があります。ホイールのスクロールを記録します。
deltaY、上にスクロールする場合は
deltaY、下にスクロールする場合は 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%); を使用したため、画像の中心点を基準にしてスケールするには、5 番目と 6 番目の値を設定する必要があります。
matrix
transformOrigin,
transform:matrix(${shrinkRate}, 0, 0, ${shrinkRate}, ${transformX}, ${transformY})# のパラメータ修正##ステーションのラベル付け
#まず、アイコンの座標を表す定数を定義します。この座標は、元の画像の左上隅を基準とします。
const imgInfo = { lableLeft: "1900", lableTop: "2000",}
ここで、元の画像のコンセプトを説明します:
インターネット上の画像要素 (上記のような画像要素) を確認してください。ページ上で指定されている画像サイズは 1200 x 900 ですが、実際のサイズは 4535 x 3402 もあります。
移動や拡大縮小を行わずにアイコンの初期座標を計算する前に、画像の拡大縮小率 (上記の
rate ではありません) を計算する必要があります。アイコンの初期座標は次のように計算できます: <pre class="brush:php;toolbar:false;">/** 图片原始大小,默认设置为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;</pre>
画像が翻訳されると、アイコンも翻訳される必要があります。これは座標計算です:
const labelLeft = parseInt(`${imgInfo.lableLeft}`, 10) * imgScaleRateX;const labelTop = parseInt(`${imgInfo.lableTop}`, 10) * imgScaleRateY;
##画像をズームすると、アイコンも画像に合わせて拡大縮小する必要があります。
transformOriginがアイコンに設定されていない場合、デフォルトではアイコンの中心を基準にしてスケールされます。アイコンが画像に合わせてスケーリングされるようにするには、画像とアイコンのスケーリング基準原点が同じである必要があり、アイコンの transformOrigin
は、画像の原点からの相対距離に設定する必要があります。画像。// 图标相对父元素坐标 = 图标位置坐标 + 图片坐标const labelLeft = parseInt(`${imgInfo.lableLeft}`, 10) * imgScaleRateX + Number(imgStyle.left || WIDTH / 2); const labelTop = parseInt(`${imgInfo.lableTop}`, 10) * imgScaleRateY + Number(imgStyle.top || HEIGHT / 2);
全体的なコード例: const labelTransformOrigin = () => { return `${initial.width / 2 - Number(imgInfo.lableLeft) * imgScaleRateX}px ${ initial.height / 2 - Number(imgInfo.lableTop) * imgScaleRateY }px`; }
推奨学習: 「react ビデオ チュートリアル
」