You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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='&copy; <a href="https://stadiamaps.com/">Stadia Maps</a>, &copy; <a href="https://openmaptiles.org/">OpenMapTiles</a> &copy; <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='&copy; <a href="https://stadiamaps.com/">Stadia Maps</a>, &copy; <a href="https://openmaptiles.org/">OpenMapTiles</a> &copy; <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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 21:13:20