fix: Giữ nguyên zoom/center của người dùng khi bản đồ resize trên mobile

- Thêm userInteractedRef để theo dõi người dùng đã zoom/drag chưa
- FitBounds chỉ chạy khi người dùng chưa tương tác
- MapSizeHandler lưu center/zoom trước khi invalidateSize, khôi phục sau nếu user đã tương tác
- MapRotationHandler xóa transform thay vì set rotate(0) khi compass tắt
- Ngăn map tự động reset về mức zoom ban đầu sau khi người dùng pinch-zoom
This commit is contained in:
2026-06-25 11:08:13 +07:00
parent 828bc89890
commit ed0fb8dd64
+35 -9
View File
@@ -16,13 +16,13 @@ interface TourNavigationPageProps {
onBack: () => void;
}
const FitBounds = ({ coords }: { coords: [number, number][] }) => {
const FitBounds = ({ coords, userInteractedRef }: { coords: [number, number][], userInteractedRef: React.MutableRefObject<boolean> }) => {
const map = useMap();
useEffect(() => {
if (coords.length > 0) {
if (coords.length > 0 && !userInteractedRef.current) {
map.fitBounds(coords, { padding: [60, 60], maxZoom: 15 });
}
}, [map, coords]);
}, [map, coords, userInteractedRef]);
return null;
};
@@ -69,14 +69,24 @@ const CompassInteractionDetector = ({
return null;
};
const MapSizeHandler = () => {
const MapSizeHandler = ({ mapRef, userInteractedRef }: { mapRef: React.MutableRefObject<L.Map | null>, userInteractedRef: React.MutableRefObject<boolean> }) => {
const map = useMap();
useEffect(() => {
const timer = setTimeout(() => {
map.invalidateSize();
if (!mapRef.current) return;
const hadUserInteracted = userInteractedRef.current;
const currentCenter = map.getCenter();
const currentZoom = map.getZoom();
mapRef.current.invalidateSize();
if (hadUserInteracted) {
mapRef.current.setView(currentCenter, currentZoom, { animate: false });
}
}, 100);
return () => clearTimeout(timer);
}, [map]);
}, [map, mapRef, userInteractedRef]);
return null;
};
@@ -85,12 +95,28 @@ export const TourNavigationPage: React.FC<TourNavigationPageProps> = ({ tourId,
const [currentHeading, setCurrentHeading] = useState(0);
const [routeGeometry, setRouteGeometry] = useState<[number, number][] | null>(null);
const [error, setError] = useState<string | null>(null);
const [mapReady, setMapReady] = useState(false);
const userInteractedRef = useRef(false);
const mapRef = useRef<L.Map | null>(null);
const watchIdRef = useRef<number | null>(null);
const fetchLockRef = useRef(false);
useEffect(() => {
const map = mapRef.current;
if (!map) return;
const onZoomEnd = () => { userInteractedRef.current = true; };
const onMoveEnd = () => { userInteractedRef.current = true; };
map.on('zoomend', onZoomEnd);
map.on('moveend', onMoveEnd);
return () => {
map.off('zoomend', onZoomEnd);
map.off('moveend', onMoveEnd);
};
}, [mapRef.current]);
const originLat = routeData?.origin?.lat;
const originLng = routeData?.origin?.lng;
const destLat = routeData?.destination?.lat;
@@ -234,8 +260,9 @@ export const TourNavigationPage: React.FC<TourNavigationPageProps> = ({ tourId,
minZoom={2}
>
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" attribution="" />
<MapSizeHandler />
<MapSizeHandler mapRef={mapRef} userInteractedRef={userInteractedRef} />
<MapRefSetter mapRef={mapRef} />
<FitBounds coords={routeGeometry || []} userInteractedRef={userInteractedRef} />
{routeData.origin && (
<Marker position={[routeData.origin.lat, routeData.origin.lng]} icon={userIcon}>
<Popup>
@@ -250,7 +277,6 @@ export const TourNavigationPage: React.FC<TourNavigationPageProps> = ({ tourId,
</Popup>
</Marker>
)}
{routeGeometry && <FitBounds coords={routeGeometry} />}
{routeGeometry && (
<Polyline positions={routeGeometry} color="#2563eb" weight={6} opacity={0.9} smoothFactor={1} />
)}