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

无React Strict Mode时Mapillary无法工作,DOM初始化失败求助

问题:Mapillary Viewer生产环境崩溃,DOM初始化异常

已尝试修复该问题许久,AI工具无法提供有效帮助。

开发环境下一切正常(因Strict Mode导致函数多次执行),但生产环境完全崩溃。无Strict Mode时Mapillary DOM无法完成初始化,Viewer对象没有挂载目标。

我是否对Mapillary的核心机制存在误解?Mapillary DOM是如何初始化的?我怀疑useEffect引发了竞态条件。

添加了[FLOW]日志来追踪应用流程和崩溃节点,但无论开发环境(符合Strict Mode预期)还是生产环境(超出预期),流程表现都很随机,这证明了竞态条件的存在。

以下是GameScreen组件代码:

import React, { useEffect, useState, useRef, useLayoutEffect } from 'react';
import axios from 'axios';
import { Viewer } from 'mapillary-js';
import '../assets/loader.css'

console.log("[FLOW] 1. Module loading");
const MAPILLARY_TOKEN = import.meta.env.VITE_MAPILLARY_TOKEN;
console.log("[FLOW] 2. MAPILLARY_TOKEN:", !!MAPILLARY_TOKEN);

const GameScreen = ({setCurrentCoordinates}) => {
  console.log("[FLOW] 3. Component function executing");
  const viewerRef = useRef(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  // 生成指定范围内的随机坐标
  const getRandomCoordinates = () => {
    const lat = Math.random() * 120 - 60; // 纬度范围:-60 至 60
    const lon = Math.random() * 360 - 180; // 经度范围:-180 至 180
    return { lat, lon };
  };

  useLayoutEffect(() => {
    console.log("[FLOW] 4. Main useEffect running");
    
    const fetchRandomImageAndInitViewer = async () => {
      console.log("[FLOW] 5. fetchRandomImageAndInitViewer started");
      setLoading(true);
      const maxAttempts = 100;
      let attempt = 0;
      let found = false;
      let imageKey = null;
      const delta = 5; // 边界框大小

      console.log("[FLOW] 6. Starting search loop");
      while (!found && attempt < maxAttempts) {
        attempt++;
        const { lat, lon } = getRandomCoordinates();
        const bbox = `${lon - delta},${lat - delta},${lon + delta},${lat + delta}`;
        console.log(`[FLOW] 7. 第${attempt}次尝试:使用边界框${bbox}请求`);

        try {
          const res = await axios.get(
            `https://graph.mapillary.com/images`,
            {
              params: {
                access_token: MAPILLARY_TOKEN,
                fields: 'id,thumb_2048_url,computed_geometry',
                limit: 1,
                bbox,
                is_pano: true
              },
            }
          );
          console.log(`[FLOW] 8. 第${attempt}次尝试获取到API响应`);

          if (res.data.data && res.data.data.length > 0) {
            const imageData = res.data.data[0];
            imageKey = imageData.id;
            found = true;
            console.log("[FLOW] 9. 找到图片:", imageKey, imageData.computed_geometry.coordinates);
            setCurrentCoordinates({
              lat: imageData.computed_geometry.coordinates[1],
              lon: imageData.computed_geometry.coordinates[0],
            });
          } else {
            console.log(`[FLOW] 9a. 边界框${bbox}内无图片`);
          }
        } catch (err) {
          console.log(`[FLOW] 9b. 请求图片出错:`, err.message);
        }
      }

      console.log("[FLOW] 10. 搜索循环结束,是否找到图片:", found);
      if (!found) {
        console.log('[FLOW] 11a. 达到最大尝试次数,未找到可用图片');
      } else {
        console.log('[FLOW] 11b. 使用图片Key初始化Viewer:', imageKey);
        
        if (!viewerRef.current) {
          console.log("[FLOW] 12. 创建新的Viewer实例");
          try {
            const mlyContainer = document.getElementById('mly');
            console.log("[FLOW] 12a. mly容器元素是否存在:", !!mlyContainer);
            
            // 延迟确保DOM渲染完成
            setTimeout(() => {
              console.log("[FLOW] 12b. 延迟后创建Viewer");
              viewerRef.current = new Viewer({
                accessToken: MAPILLARY_TOKEN,
                container: mlyContainer,
                imageKey: imageKey,
                component: {
                  cover: false,
                  direction: false,
                  sequence: false,
                  zoom: false,
                  attribution: false,
                  bearing: false,
                  spatial: false,
                  tag: false,
                  popup: false,
                  image: true,
                  navigation: false,
                  cache: true,
                  keyboard: false
                }
              });
              console.log("[FLOW] 13. Viewer实例创建成功:", !!viewerRef.current);
              
              // 添加事件监听
              viewerRef.current.on('load', () => {
                console.log("[FLOW] 14a. VIEWER加载事件触发!");
              });
              
              viewerRef.current.on('error', (err) => {
                console.log("[FLOW] 14b. VIEWER错误事件:", err);
              });
            }, 100); // 100ms延迟足够
            
          } catch (err) {
            console.log("[FLOW] 13a. 创建Viewer出错:", err.message);
          }
        } else {
          console.log("[FLOW] 12a. 将现有Viewer移动到图片:", imageKey);
          try {
            viewerRef.current.moveTo(imageKey);
            console.log("[FLOW] 13b. Viewer移动成功");
          } catch (err) {
            console.log("[FLOW] 13c. 移动Viewer出错:", err.message);
          }
        }
      }

      console.log("[FLOW] 15. 设置loading为false");
      setLoading(false);
    };

    fetchRandomImageAndInitViewer();

    return () => {
      console.log("[FLOW] 16. 清理函数执行");
      if(viewerRef.current) {
        try {
          console.log("[FLOW] 17. 移除Viewer");
          viewerRef.current.remove();
          viewerRef.current = null;
          console.log("[FLOW] 18. Viewer移除成功");
        } catch (err) {
          console.log("[FLOW] 18a. 移除Viewer出错:", err.message);
        }
      }
    }
  }, [setCurrentCoordinates]);

  useEffect(() => {
    console.log("[FLOW] 19. CSS检查effect执行");
    // 检查CSS是否加载
    setTimeout(() => {
      const cssLoaded = Array.from(document.styleSheets).some(sheet => {
        try {
          return sheet.href && sheet.href.includes('mapillary');
        } catch (e) {
          return false;
        }
      });
      console.log("[FLOW] 20. Mapillary CSS是否加载:", cssLoaded);
      
      // 检查mly容器是否存在
      const container = document.getElementById('mly');
      console.log("[FLOW] 21. mly容器是否存在:", !!container);
      
      // 检查是否存在Mapillary DOM元素
      const mapillaryElements = document.querySelectorAll('.mapillary-js-dom');
      console.log("[FLOW] 22. mapillary-js-dom元素数量:", mapillaryElements.length);
    }, 1000);
  }, []);

  console.log("[FLOW] 23. 渲染组件");
  return (
    <>
      <style>
        {`
          .mapillary-js-dom {
            position: absolute;
            width: 100%;
            height: 100%;
            overflow: hidden;
          }
          .mapillary-js-canvas {
            position: absolute;
            width: 100%;
            height: 100%;
            background-color: #000;
          }
        `}
      </style>
      <div className="game-screen h-screen w-screen flex items-center justify-center bg-gray-100 overflow-hidden">
        <div className="street-view-container w-screen h-full bg-black flex items-center justify-center">
            <div
              id='mly'
              ref={viewerRef.current}
              className="object-cover h-full w-full"
              onLoad={() => console.log("[FLOW] 24. mly div加载事件触发")}
            >
              {loading && (
                <div className="absolute inset-0 flex items-center justify-center bg-black bg-opacity-50">
                  <div className="loader"></div>
                </div>
              )}
            </div>
        </div>
      </div>
    </>
  );
};

console.log("[FLOW] 0. 模块解析完成");
export default GameScreen;

内容的提问来源于stack exchange,提问作者Krystian Turek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 01:45:55