效果图:
注:该案例使用的是地图,需要先将碎片的地图模块通过定位属性将其拼接为一个整体
废话不多说直接上代码:
index.tsx
import useDrag from '@/hooks/useDrag';
import useZoom from '@/hooks/useZoom';
const Index = () => {
// 拖拽
const { handleMouseDown, handleMouseMove, handleMouseUp } = useDrag();
//
const { handleWheel, scale } = useZoom();
const [mapActive, setMapActive] = useState(1);
// 切换地图
const handleMapChange = (value: number) => {
console.log('value :>> ', value);
setMapActive(value);
};
<div
className={styles.map}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onWheel={handleWheel}
style={{
transform: `scale(${scale})`,
}}
>
<div className={styles['map-item-1']}>
<img
src={mapActive === 1 ? mapmengheActive : mapmenghe}
className={styles['map-item-img']}
draggable="false"
></img>
<div
className={styles['map-item-mark']}
onClick={() => {
handleMapChange(1);
}}
>
<div className={styles['mark-img']}>
<CollectionBinsIcon />
</div>
</div>
</div>
<div className={styles['map-item-12']}>
<img
src={mapActive === 12 ? mapweicunActive : mapweicun}
className={styles['map-item-img']}
draggable="false"
></img>
<div
className={styles['map-item-mark']}
onClick={() => {
handleMapChange(12);
}}
>
<div className={styles['mark-img']}>
<CollectionBinsIcon />
</div>
</div>
</div>
<div className={styles['map-item-2']}>
<img
src={mapActive === 2 ? mapcunjiangActive : mapcunjiang}
className={styles['map-item-img']}
draggable="false"
></img>
<div
className={styles['map-item-mark']}
onClick={() => {
handleMapChange(2);
}}
>
<div className={styles['mark-img']}>
<CollectionBinsIcon />
</div>
</div>
</div>
<div className={styles['map-item-11']}>
<img
src={mapActive === 11 ? mapbinkaiActive : mapbinkai}
className={styles['map-item-img']}
draggable="false"
></img>
<div
className={styles['map-item-mark']}
onClick={() => {
handleMapChange(11);
}}
>
<div className={styles['mark-img']}>
<CollectionBinsIcon />
</div>
</div>
</div>
<div className={styles['map-item-3']}>
<img
src={mapActive === 3 ? mapxixiayeActive : mapxixiaye}
className={styles['map-item-img']}
draggable="false"
></img>
<div
className={styles['map-item-mark']}
onClick={() => {
handleMapChange(3);
}}
>
<div className={styles['mark-img']}>
<CollectionBinsIcon />
</div>
</div>
</div>
<div className={styles['map-item-4']}>
<img
src={mapActive === 4 ? maplonghutangActive : maplonghutang}
className={styles['map-item-img']}
draggable="false"
></img>
<div
className={styles['map-item-mark']}
onClick={() => {
handleMapChange(4);
}}
>
<div className={styles['mark-img']}>
<CollectionBinsIcon />
</div>
</div>
</div>
<div className={styles['map-item-5']}>
<img
src={mapActive === 5 ? mapxinqiaoActive : mapxinqiao}
className={styles['map-item-img']}
draggable="false"
></img>
<div
className={styles['map-item-mark']}
onClick={() => {
handleMapChange(5);
}}
>
<div className={styles['mark-img']}>
<CollectionBinsIcon />
</div>
</div>
</div>
<div className={styles['map-item-6']}>
<img
src={mapActive === 6 ? mapxuejiaActive : mapxuejia}
className={styles['map-item-img']}
draggable="false"
></img>
<div
className={styles['map-item-mark']}
onClick={() => {
handleMapChange(6);
}}
>
<div className={styles['mark-img']}>
<CollectionBinsIcon />
</div>
</div>
</div>
<div className={styles['map-item-7']}>
<img
src={mapActive === 7 ? mapluoxiActive : mapluoxi}
className={styles['map-item-img']}
draggable="false"
></img>
<div
className={styles['map-item-mark']}
onClick={() => {
handleMapChange(7);
}}
>
<div className={styles['mark-img']}>
<CollectionBinsIcon />
</div>
</div>
</div>
<div className={styles['map-item-8']}>
<img
src={mapActive === 8 ? maphehaiActive : maphehai}
className={styles['map-item-img']}
draggable="false"
></img>
<div
className={styles['map-item-mark']}
onClick={() => {
handleMapChange(8);
}}
>
</span> */}
<div style={{ left: 5 }} className={styles['mark-img']}>
<CollectionBinsIcon />
</div>
</div>
</div>
<div className={styles['map-item-9']}>
<img
src={mapActive === 9 ? mapsanjingActive : mapsanjing}
className={styles['map-item-img']}
draggable="false"
></img>
<div
className={styles['map-item-mark']}
onClick={() => {
handleMapChange(9);
}}
>
<div className={styles['mark-img']}>
<CollectionBinsIcon />
</div>
</div>
</div>
<div className={styles['map-item-10']}>
<img
src={mapActive === 10 ? mapbenniuActive : mapbenniu}
className={styles['map-item-img']}
draggable="false"
></img>
<div
className={styles['map-item-mark']}
onClick={() => {
handleMapChange(10);
}}
>
<div className={styles['mark-img']}>
<CollectionBinsIcon />
</div>
</div>
</div>
</div>
}
index.scss
.container {
width: 1920px;
position: relative;
height: 1080px;
overflow: hidden;
box-sizing: border-box;
background-image: url(/src/assets/images/bg.jpg) no-repeat;
.map {
position: absolute;
top: 0;
left: 600px;
background: transparent;
width: 900px;
height: 730px;
overflow: hidden;
z-index: 1;
cursor: grab;
transition: transform 0.3s ease;
&>div {
transform: scale(0.30);
position: relative;
}
.map-item-mark {
transform: scale(2.5);
position: absolute;
cursor: pointer;
color: #fff;
font-weight: 600;
&>.mark-img {
position: absolute;
top: 25px;
left: 20px;
z-index: 3;
}
&>.mark-text {
width: 100px;
text-align: center;
position: absolute;
z-index: 3;
left: 40px;
}
}
.map-item-1 {
position: absolute;
top: -45px;
right: -290px;
&>.map-item-mark {
position: absolute;
top: 100px;
left: 700px;
}
}
.map-item-2 {
position: absolute;
top: 121px;
right: -164px;
z-index: 2;
&>.map-item-mark {
position: absolute;
top: 140px;
left: 210px;
}
}
.map-item-11 {
position: absolute;
top: -34px;
right: -239px;
&>.map-item-mark {
position: absolute;
top: 38px;
left: 253px;
}
}
.map-item-12 {
position: absolute;
top: 8px;
right: -212px;
&>.map-item-mark {
position: absolute;
top: 190px;
left: 540px;
}
}
.map-item-3 {
position: absolute;
top: 109px;
right: -18px;
&>.map-item-mark {
position: absolute;
top: 115px;
left: 380px;
}
}
.map-item-4 {
position: absolute;
top: 251px;
right: -160px;
&>.map-item-mark {
position: absolute;
top: 86px;
left: 368px;
&>.mark-text {
top: -10px;
}
}
}
.map-item-5 {
position: absolute;
top: 234px;
right: -66px;
z-index: 2;
&>.map-item-mark {
position: absolute;
top: 36px;
left: 310px;
&>.mark-text {
top: 5px;
}
}
}
.map-item-6 {
position: absolute;
top: 222px;
right: -22px;
z-index: 2;
&>.map-item-mark {
position: absolute;
top: 215px;
left: 510px;
}
}
.map-item-7 {
position: absolute;
top: 192px;
right: 4px;
z-index: 2;
&>.map-item-mark {
position: absolute;
top: 96px;
left: 505px;
&>.mark-text {
left: -58px;
}
}
}
.map-item-8 {
position: absolute;
top: 366px;
right: 52px;
z-index: 2;
&>.map-item-mark {
position: absolute;
top: 125px;
left: 116px;
&>.mark-text {
top: -37px;
left: -9px;
}
}
}
.map-item-9 {
position: absolute;
top: 349px;
right: -169px;
&>.map-item-mark {
position: absolute;
top: 208px;
left: 277px;
&>.mark-text {
top: -31px;
left: 29px;
}
}
}
.map-item-10 {
position: absolute;
top: 172px;
right: 266px;
z-index: 2;
&>.map-item-mark {
position: absolute;
top: 126px;
left: 330px;
&>.mark-text {
left: -58px;
}
}
}
}
}
/hook/useDrag.ts
import { useCallback, useState } from 'react';
// 拖拽
export default function useDrag() {
const [isDragging, setIsDragging] = useState(false);
const [initialPosition, setInitialPosition] = useState({ x: 0, y: 0 });
const [initialMousePosition, setInitialMousePosition] = useState({ x: 0, y: 0 });
const handleMouseDown = useCallback((e: { clientX: any; clientY: any; currentTarget: { offsetLeft: any; offsetTop: any; }; }) => {
setIsDragging(true);
setInitialMousePosition({ x: e.clientX, y: e.clientY });
setInitialPosition({ x: e.currentTarget.offsetLeft, y: e.currentTarget.offsetTop });
}, []);
const handleMouseMove = useCallback(
( e: { clientX: number; clientY: number; currentTarget: { style: { left: string; top: string; }; }; }) => {
if (isDragging) {
const dx = e.clientX - initialMousePosition.x;
const dy = e.clientY - initialMousePosition.y;
e.currentTarget.style.left = `${initialPosition.x + dx}px`;
e.currentTarget.style.top = `${initialPosition.y + dy}px`;
}
},
[isDragging, initialMousePosition, initialPosition]
);
const handleMouseUp = useCallback(() => {
setIsDragging(false);
}, []);
return {
handleMouseDown,
handleMouseMove,
handleMouseUp,
};
}
/hook/useZoom.ts
import { useCallback, useState } from 'react';
// 缩放
export default function useZoom(initialScale = 1) {
const [scale, setScale] = useState(initialScale);
const handleWheel = useCallback(
( e: { deltaY: number; currentTarget: { style: { transform: string; }; }; }) => {
if (e.deltaY !== 0) {
const newScale = scale + (e.deltaY > 0 ? -0.05 : 0.05);
setScale(newScale < 0.5 ? 0.5 : newScale); // 防止缩放过小
e.currentTarget.style.transform = `scale(${newScale})`;
}
},
[scale]
);
return {
handleWheel,
scale,
};
}