Leaflet 1k+的性能问题;在React传单中带有弹出窗口的标记

Leaflet 1k+的性能问题;在React传单中带有弹出窗口的标记,leaflet,react-leaflet,Leaflet,React Leaflet,我在React传单库中有一个React应用程序,我正在地图上的一个小镇上为每栋建筑显示一个标记。我总共有大约5千个标记,还有一个只显示我想要的标记的过滤器 然而,我注意到下面的代码对我的性能产生了巨大的影响。我已经研究了一些替代方案,比如PixiOverlay和marker集群,但是前者很难将当前代码库迁移到其中,而后者根本不能解决我的问题 我当前的代码: import React, { useRef, useEffect, useContext, useState, } from 'r

我在React传单库中有一个React应用程序,我正在地图上的一个小镇上为每栋建筑显示一个标记。我总共有大约5千个标记,还有一个只显示我想要的标记的过滤器

然而,我注意到下面的代码对我的性能产生了巨大的影响。我已经研究了一些替代方案,比如PixiOverlay和marker集群,但是前者很难将当前代码库迁移到其中,而后者根本不能解决我的问题

我当前的代码:

import React, {
    useRef, useEffect, useContext, useState,
} from 'react';
import ReactDOMServer from 'react-dom/server';
import {
    Marker, useLeaflet, Popup, Tooltip, CircleMarker, Circle,
} from 'react-leaflet';

import L from 'leaflet';
import styled from 'styled-components';

interface IProps {
    coords: [number, number]
    description: string,
    name: string
}

let timeoutPopupRef: any = null;
let timeoutPopupRefClose: any = null;

const DynamicMarker: React.FC<IProps> = ({ coords, description, name }) => {
    const markerRef = useRef<any>(null);
    const popupRef = useRef<Popup>(null);
    const tooltipRef = useRef<Tooltip>(null);
    const leaflet = useLeaflet();

    const divIcon: L.DivIcon = L.divIcon({
        iconSize: [25, 25],
        className: 'marker-white',
    });

    const onComponentMount = () => {
        if (!leaflet.map) return;
        if (!markerRef.current) return;

        const mapZoom: number = leaflet.map.getZoom();

        if (popupRef.current) {
            if (mapZoom <= 17) {
                markerRef.current.leafletElement.unbindPopup();
            } else if (mapZoom > 17) {
                markerRef.current.leafletElement.bindPopup(popupRef.current!.leafletElement);
            }
        }

        if (tooltipRef.current) {
            if (mapZoom <= 15) {
                markerRef.current.leafletElement.unbindTooltip();
            } else if (mapZoom > 15) {
                markerRef.current.leafletElement.bindTooltip(tooltipRef.current!.leafletElement);
            }
        }

        leaflet.map!.on('zoomend', onMapZoomEnd);
    };
    useEffect(onComponentMount, []);

    const onMapZoomEnd = () => {
        if (!markerRef.current) return;
        if (!popupRef.current) return;
        if (!leaflet.map) return;

        const zoom = leaflet.map.getZoom();

        if (zoom < 17) {
            if (!markerRef.current!.leafletElement.isPopupOpen()) {
                markerRef.current!.leafletElement.unbindPopup();
            }
        } else if (zoom >= 17) {
            markerRef.current!.leafletElement.bindPopup(popupRef.current.leafletElement);
        }
    };

    const handlePopupVisible = (value: boolean) => {
        if (!markerRef.current) return;
        if (timeoutPopupRefClose) clearTimeout(timeoutPopupRefClose);

        if (value) {
            if (!markerRef.current!.leafletElement.isPopupOpen()) {
                timeoutPopupRef = setTimeout(() => {
                markerRef.current!.leafletElement.openPopup();
                }, 400);
            }
        } else {
            if (timeoutPopupRef) {
                clearTimeout(timeoutPopupRef);
            }

            if (markerRef.current!.leafletElement.isPopupOpen()) {
                timeoutPopupRefClose = setTimeout(() => {
                markerRef.current!.leafletElement.closePopup();
                }, 100);
            }
        }
    };

    const onComponentDismount = () => {
        leaflet.map!.off('zoomend', onMapZoomEnd);

        if (!markerRef.current) return;
        markerRef.current.leafletElement.remove();
    };
    useEffect(() => onComponentDismount, []);

    return (
        <Marker
            icon={divIcon}
            position={coords}
            onmouseover={() => handlePopupVisible(true)}
            onmouseout={() => handlePopupVisible(false)}
            ref={markerRef}
        >
            <Popup className="custom-popup-content" ref={popupRef} closeButton={false}>
                <div
                    onMouseEnter={() => handlePopupVisible(true)}
                    onMouseLeave={() => handlePopupVisible(false)}
                >
                    <img
                        className="popup-img"
                        alt='image'
                        src='https://cdn.discordapp.com/attachments/578931223775281162/644181902215086094/default_geocode-1x.png'
                    />
                    <div className="popup-content">
                        <span className="popup-content-title">{name}</span>
                        {description && <span className="popup-content-subtitle">{description}</span>}
                    </div>
                </div>
            </Popup>
        </Marker>
            
    );
};

export default DynamicMarker;
import-React{
useRef、useffect、useContext、useState、,
}从"反应",;
从“react dom/server”导入ReactDOMServer;
进口{
标记器,使用传单,弹出窗口,工具提示,圆圈标记器,圆圈,
}来自“反应传单”;
从“传单”进口L;
从“样式化组件”导入样式化;
接口IProps{
coords:[数字,数字]
描述:字符串,
名称:string
}
let timeoutPopupRef:any=null;
让TimeOutpupPrefClose:any=null;
常量DynamicMarker:React.FC=({coords,description,name})=>{
常量markerRef=useRef(null);
const popupRef=useRef(null);
const tooltipRef=useRef(null);
常数传单=使用传单();
常数divIcon:L.divIcon=L.divIcon({
iconSize:[25,25],
类名:“标记白色”,
});
常量onComponentMount=()=>{
如果(!传单.地图)返回;
如果(!markerRef.current)返回;
const mapZoom:number=传单.map.getZoom();
if(popupRef.current){
如果(地图缩放17){
markerRef.current.传单元素.bindPopup(popupRef.current!.传单元素);
}
}
如果(当前工具提示参考){
如果(地图缩放15){
markerRef.current.传单元素.bindTooltip(tooltipRef.current!.传单元素);
}
}
传单.map!.on('zoomend',onMapZoomEnd);
};
useEffect(onComponentMount,[]);
const onMapZoomEnd=()=>{
如果(!markerRef.current)返回;
如果(!popupRef.current)返回;
如果(!传单.地图)返回;
const zoom=传单.map.getZoom();
如果(缩放<17){
如果(!markerRef.current!.flookelement.isPopupOpen()){
markerRef.current!.mopleElement.unbindPopup();
}
}否则如果(缩放>=17){
markerRef.current!.传单元素.bindPopup(popupRef.current.传单元素);
}
};
常量handlePopupVisible=(值:布尔)=>{
如果(!markerRef.current)返回;
如果(TimeOutpupPrefClose)清除超时(TimeOutpupPrefClose);
如果(值){
如果(!markerRef.current!.flookelement.isPopupOpen()){
TimeOutpupPref=设置超时(()=>{
markerRef.current!.mopleElement.openPopup();
}, 400);
}
}否则{
if(timeoutPopupRef){
clearTimeout(TimeOutpupPref);
}
if(markerRef.current!.mopleElement.isPopupOpen()){
TimeOutpupPrefClose=设置超时(()=>{
markerRef.current!.floapleElement.closePopup();
}, 100);
}
}
};
常量onComponentDismount=()=>{
传单.map!.off('zoomend',onMapZoomEnd);
如果(!markerRef.current)返回;
markerRef.current.floapElement.remove();
};
useEffect(()=>onComponentDismount,[]);
返回(
handlePopupVisible(真)}
onmouseout={()=>handlePopupVisible(false)}
ref={markerRef}
>
handlePopupVisible(真)}
onMouseLeave={()=>handlePopupVisible(false)}
>
{name}
{description&&{description}}
);
};
导出默认动态标记器;
如果地图缩放低于阈值,上面的代码将从标记中取消绑定弹出窗口,并在缩放高于阈值时绑定它们。我还为标记组件上的
onMouseOver
onMouseOut
事件实现了事件处理程序,以在用户悬停标记图标时打开我的弹出窗口,并且只有当光标未悬停在弹出窗口或标记图标上时,它才会关闭弹出窗口


当我在显示约2k个标记的情况下放大或缩小时,地图冻结约5-10秒,并在通过
反应传单markercluster
使用标记聚类进行测试后,更新
反应传单导出的
地图
组件中的所有组件,我注意到性能问题仍然存在。我试着注释掉作为子组件传递给marker组件的
弹出窗口
组件,我遇到的滞后问题消失了

考虑到这一点,我意识到我的瓶颈实际上是在DOM中呈现2k个弹出窗口,即使它们是不可见的。所以,经过一些尝试和错误,我找到了一个解决方案:国家

我添加了一个名为
shouldDrawPopup
的布尔状态,默认值为
false
,只在
handlePopupVisible
函数中更改了它的值。只有在以下情况下,此布尔状态的值才会更改:

  • 地图缩放高于阈值;及
  • 弹出窗口未打开
然后,我更改了组件的
render
函数,使其仅在
shouldDrawPopup
状态为true时才包含弹出窗口:

    return (
        {shouldDrawPopup && (
            <Marker
                icon={divIcon}
                position={coords}
                onmouseover={() => handlePopupVisible(true)}
                onmouseout={() => handlePopupVisible(false)}
                ref={markerRef}
            >
                <Popup className="custom-popup-content" ref={popupRef} closeButton={false}>
                    <div
                        onMouseEnter={() => handlePopupVisible(true)}
                        onMouseLeave={() => handlePopupVisible(false)}
                    >
                        <img
                            className="popup-img"
                            alt='image'
                            src='https://cdn.discordapp.com/attachments/578931223775281162/644181902215086094/default_geocode-1x.png'
                        />
                        <div className="popup-content">
                            <span className="popup-content-title">{name}</span>
                            {description && <span className="popup-content-subtitle">{description}</span>}
                        </div>
                    </div>
                </Popup>
            </Marker>
        )}     
    );
返回(
{shouldDrawPopup&&(
handlePopupVisible(真)}
onmouseout={()=>handlePopupVisible(false)}
ref={markerRef}
>
handlePopupVisible(真)}
onMouseLeave={()=>handlePopupVisible(false)}
>