如何在React-Leaflet中获取地图可见标记并实现Airbnb式效果
React + Leaflet 实现地图可见标记同步列表(Airbnb式效果)
我在React中使用Leaflet,想要实现类似Airbnb的效果:将地图上可见的标记存储到数组中,每次地图移动时更新该数组,再根据可见标记在地图旁展示地点列表。我知道需要使用
map.getBounds(),但不清楚如何将其集成到现有代码中。基础代码如下:
function FranceMap() { const { latitude, longitude }: ParisProps = ParisGpsCoordinates; return ( <div id="map-container" className="..." > <MapContainer center={[latitude, longitude]} zoom={11}> <TileLayer attribution='© <a href="https://stadiamaps.com/">Stadia Maps</a>, © <a href="https://openmaptiles.org/">OpenMapTiles</a> © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors' url="https://tiles.stadiamaps.com/tiles/osm_bright/{z}/{x}/{y}{r}.png" /> <MarkerClusterGroup chunkedLoading> {SitesLocation} </MarkerClusterGroup> </MapContainer> </div> </div> ); }
实现步骤
1. 获取Leaflet Map实例
要操作地图边界,首先需要拿到Leaflet的Map对象。用useRef存储实例,通过MapContainer的whenCreated回调赋值:
const mapRef = useRef<L.Map | null>(null); // 在MapContainer中添加whenCreated回调 <MapContainer center={[latitude, longitude]} zoom={11} whenCreated={(map) => mapRef.current = map} >
2. 管理可见地点状态
用useState创建状态,存储当前地图视野内的地点数组:
// 替换SiteType为你实际的地点数据类型 const [visibleSites, setVisibleSites] = useState<SiteType[]>([]);
3. 监听地图移动事件,过滤可见标记
在useEffect中绑定地图的moveend事件(地图移动/缩放结束后触发),每次触发时获取当前地图边界,过滤出在边界内的标记:
useEffect(() => { const map = mapRef.current; if (!map) return; // 过滤可见地点的核心逻辑 const updateVisibleSites = () => { const bounds = map.getBounds(); // 假设SitesLocation数组中每个元素包含latitude/longitude属性 const filtered = SitesLocation.filter(site => { return bounds.contains(L.latLng(site.latitude, site.longitude)); }); setVisibleSites(filtered); }; // 绑定事件监听 map.on('moveend', updateVisibleSites); // 组件初始化时执行一次,获取初始可见地点 updateVisibleSites(); // 组件卸载时移除事件监听,避免内存泄漏 return () => map.off('moveend', updateVisibleSites); }, [SitesLocation]);
4. 渲染侧边地点列表
在地图旁添加列表组件,根据visibleSites状态渲染当前可见的地点:
return ( <div className="flex h-screen"> {/* 地图容器 */} <div id="map-container" className="w-3/4"> {/* 原地图代码 */} </div> {/* 侧边地点列表 */} <div className="w-1/4 p-4 overflow-y-auto"> <h3 className="text-lg font-bold mb-4">当前可见地点</h3> {visibleSites.length === 0 ? ( <p>暂无可见地点</p> ) : ( <ul> {visibleSites.map(site => ( <li key={site.id} className="mb-3 p-2 border rounded"> <h4>{site.name}</h4> <p className="text-sm text-gray-600">{site.address}</p> </li> ))} </ul> )} </div> </div> );
完整整合代码
import { useState, useEffect, useRef } from 'react'; import { MapContainer, TileLayer, MarkerClusterGroup } from 'react-leaflet'; import L from 'leaflet'; // 自定义地点数据类型,根据实际业务调整 type SiteType = { id: string; name: string; address: string; latitude: number; longitude: number; }; function FranceMap() { const { latitude, longitude }: ParisProps = ParisGpsCoordinates; const mapRef = useRef<L.Map | null>(null); const [visibleSites, setVisibleSites] = useState<SiteType[]>([]); useEffect(() => { const map = mapRef.current; if (!map) return; const updateVisibleSites = () => { const bounds = map.getBounds(); const filtered = SitesLocation.filter(site => { return bounds.contains(L.latLng(site.latitude, site.longitude)); }); setVisibleSites(filtered); }; map.on('moveend', updateVisibleSites); updateVisibleSites(); return () => map.off('moveend', updateVisibleSites); }, [SitesLocation]); return ( <div className="flex h-screen"> <div id="map-container" className="w-3/4"> <MapContainer center={[latitude, longitude]} zoom={11} whenCreated={(map) => mapRef.current = map} style={{ height: '100%' }} > <TileLayer attribution='© <a href="https://stadiamaps.com/">Stadia Maps</a>, © <a href="https://openmaptiles.org/">OpenMapTiles</a> © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors' url="https://tiles.stadiamaps.com/tiles/osm_bright/{z}/{x}/{y}{r}.png" /> <MarkerClusterGroup chunkedLoading> {SitesLocation} </MarkerClusterGroup> </MapContainer> </div> <div className="w-1/4 p-4 overflow-y-auto"> <h3 className="text-lg font-bold mb-4">当前可见地点</h3> {visibleSites.length === 0 ? ( <p>暂无可见地点</p> ) : ( <ul> {visibleSites.map(site => ( <li key={site.id} className="mb-3 p-2 border rounded"> <h4>{site.name}</h4> <p className="text-sm text-gray-600">{site.address}</p> </li> ))} </ul> )} </div> </div> ); } export default FranceMap;
内容的提问来源于stack exchange,提问作者ImEmo
相关产品推荐
相关产品推荐

