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

如何在react-google-maps中完全控制InfoWindow的显隐状态?

InfoWindow点击外部关闭后残留三角元素的解决方案

我通过状态displayInfoWindow控制<InfoWindow>的显示:状态为true时渲染组件,false时卸载。点击Marker可以打开窗口,点击窗口自带的关闭按钮能关闭,但我需要实现点击页面其他区域也关闭窗口,于是加了一个监听外部点击的自定义钩子,结果现在窗口主体关闭后,会残留一个由::after伪元素生成的白色三角。

现有代码

Point.js

const Point = ({ point }) => {
  const [displayInfoWindow, setDisplayInfoWindow] = useState(false);

  const handlePointClick = () => {
    setDisplayInfoWindow(true);
  };

  return (
    <Marker
      onClick={handlePointClick}
      position={{ lat: point.coordinates.lat, lng: point.coordinates.lng }}
      key={point.key}
    >
      {displayInfoWindow && (
        <InfoWindow
          onCloseClick={() => setDisplayInfoWindow(!displayInfoWindow)}
        >
          <div className="Point">
            <h1>Info Window</h1>
          </div>
        </InfoWindow>
      )}
    </Marker>
  );
};

useOnOutsideClick.js

const useOnOutsideClick = (refArray, callback) => {
  useEffect(() => {
    const listener = (e) => {
      for (let i = 0; i < refArray.length; i++) {
        const ref = refArray[i];
        if (
          !ref.current ||
          (ref.current.contains && ref.current.contains(e.target))
        ) {
          return;
        }
      }

      callback();
    };

    document.addEventListener("mousedown", listener);
    document.addEventListener("touchstart", listener);

    return () => {
      document.removeEventListener("mousedown", listener);
      document.removeEventListener("touchstart", listener);
    };
  }, [refArray, callback]);
};

问题根源

残留的三角是<InfoWindow>组件内部生成的DOM元素(或伪元素),直接通过条件渲染卸载组件时,Google Maps API的异步DOM操作可能没来得及清理所有关联元素;另外自定义外部点击钩子没有触发组件的原生关闭流程,导致部分元素残留。

推荐解决方案:利用组件原生能力

绝大多数React版Google Maps库(比如@react-google-maps/api)的<InfoWindow>已经内置了「点击地图其他区域自动关闭」的逻辑,不需要自己写外部点击监听。只需要调整状态绑定方式:

import { Marker, InfoWindow } from '@react-google-maps/api';

const Point = ({ point }) => {
  const [isInfoWindowOpen, setIsInfoWindowOpen] = useState(false);

  const handleMarkerClick = () => {
    setIsInfoWindowOpen(true);
  };

  const handleInfoWindowClose = () => {
    setIsInfoWindowOpen(false);
  };

  return (
    <>
      <Marker
        onClick={handleMarkerClick}
        position={{ lat: point.coordinates.lat, lng: point.coordinates.lng }}
        key={point.key}
      />
      <InfoWindow
        position={{ lat: point.coordinates.lat, lng: point.coordinates.lng }}
        visible={isInfoWindowOpen}
        onCloseClick={handleInfoWindowClose}
      >
        <div className="Point">
          <h1>Info Window</h1>
        </div>
      </InfoWindow>
    </>
  );
};

关键调整点:

  • 把<InfoWindow>从<Marker>的子元素中移出,作为同级组件,通过position属性绑定位置(这是库的推荐用法)。
  • 用组件的visible(部分库是open)属性控制显示,而非条件渲染。这样组件会调用Google Maps API的原生关闭方法,确保所有关联DOM元素(包括三角伪元素)被彻底清理。
  • 此时点击地图任意非窗口区域,API会自动触发onCloseClick,同步更新你的状态,完美实现外部点击关闭。

如果你坚持用条件渲染+自定义钩子

如果一定要保留原有的条件渲染逻辑,需要确保外部点击钩子能正确识别<InfoWindow>的所有DOM节点:

const Point = ({ point }) => {
  const [displayInfoWindow, setDisplayInfoWindow] = useState(false);
  const infoWindowRef = useRef(null);

  // 传入InfoWindow的DOM引用
  useOnOutsideClick([infoWindowRef], () => {
    setDisplayInfoWindow(false);
  });

  const handlePointClick = () => {
    setDisplayInfoWindow(true);
  };

  return (
    <Marker
      onClick={handlePointClick}
      position={{ lat: point.coordinates.lat, lng: point.coordinates.lng }}
      key={point.key}
    >
      {displayInfoWindow && (
        <InfoWindow
          ref={infoWindowRef}
          onCloseClick={() => setDisplayInfoWindow(false)}
        >
          <div className="Point">
            <h1>Info Window</h1>
          </div>
        </InfoWindow>
      )}
    </Marker>
  );
};

这种方式需要确保useOnOutsideClick中的contains判断能覆盖InfoWindow的所有子元素(包括三角所在容器),但仍可能因为库的内部DOM结构问题出现残留,所以优先推荐第一种方案。

内容的提问来源于stack exchange,提问作者Willey Ohana

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 02:35:55