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

如何在React中实现JSON文件懒加载?适配多视口按需加载

如何在React中按需加载Bodymovin生成的JSON动画文件

好问题!咱们先把核心问题说清楚:React.lazy() 是专门为加载React组件设计的,没办法直接用来加载JSON文件——这就是你之前尝试 const treeMedium = lazy(() => import('./tree-medium.json')) 失效的原因。不过别担心,我们完全可以用其他方式实现你想要的按需加载(包括只在移动端加载专属JSON,桌面端完全不加载非必要文件)。

为什么React.lazy()不适用?

React.lazy() 的要求很明确:动态 import() 必须返回一个包含默认导出React组件的模块。而JSON文件被导入后是普通的JavaScript对象,不符合这个要求,所以直接用它加载JSON肯定行不通。

解决方案:动态import + React状态管理

我们可以结合动态 import()、useState 和 useEffect 来实现按需加载,甚至可以根据视口尺寸精准加载对应大小的JSON文件。

基础实现:单个动画的按需加载

下面是针对你场景的基础示例,以背景动画为例:

import { useState, useEffect } from 'react';
import Lottie from 'react-lottie';

const BackgroundAnimation = () => {
  // 存储加载后的动画数据、加载状态和错误信息
  const [animationData, setAnimationData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const loadAnimationData = async () => {
      setIsLoading(true);
      try {
        // 根据视口宽度判断加载哪个JSON
        const viewportWidth = window.innerWidth;
        let jsonPath;
        
        if (viewportWidth < 375) {
          jsonPath = './background-smallMobile.json';
        } else if (viewportWidth < 768) {
          jsonPath = './background-mobile.json';
        } else if (viewportWidth < 1024) {
          jsonPath = './background-medium.json';
        } else if (viewportWidth < 1440) {
          jsonPath = './background-large.json';
        } else {
          jsonPath = './background.json';
        }

        // 动态加载JSON,注意取default属性(这是ES模块导入JSON的规范)
        const animationModule = await import(jsonPath);
        setAnimationData(animationModule.default);
      } catch (err) {
        setError('加载动画资源失败');
      } finally {
        setIsLoading(false);
      }
    };

    // 初始化加载
    loadAnimationData();

    // 可选:监听窗口 resize,切换视口时自动加载对应尺寸的JSON
    const handleResize = () => {
      loadAnimationData();
    };
    window.addEventListener('resize', handleResize);

    // 组件卸载时移除监听
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  // 加载状态和错误处理
  if (isLoading) return <div>Loading background animation...</div>;
  if (error) return <div className="error">{error}</div>;
  if (!animationData) return null;

  // 配置Lottie组件
  const backgroundOptions = {
    loop: true,
    autoplay: true,
    animationData: animationData,
  };

  return <Lottie options={backgroundOptions} />;
};

export default BackgroundAnimation;

优化:封装可复用的自定义Hook

因为你有多个动画系列(background、tree),可以把加载逻辑封装成自定义Hook,避免重复代码:

import { useState, useEffect } from 'react';

// 自定义Hook:根据视口尺寸加载对应动画JSON
const useLazyLottieData = (sizeMap) => {
  const [animationData, setAnimationData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const loadData = async () => {
      setIsLoading(true);
      try {
        const viewportWidth = window.innerWidth;
        // 找到匹配当前视口的JSON路径
        const matchedPath = Object.entries(sizeMap).reduce((matched, [widthStr, path]) => {
          if (viewportWidth < parseInt(widthStr) && !matched) {
            return path;
          }
          return matched;
        }, sizeMap.default);

        const module = await import(matchedPath);
        setAnimationData(module.default);
      } catch (err) {
        setError('加载动画失败');
      } finally {
        setIsLoading(false);
      }
    };

    loadData();

    // 防抖处理resize事件,避免频繁触发加载
    let resizeTimer;
    const handleResize = () => {
      clearTimeout(resizeTimer);
      resizeTimer = setTimeout(loadData, 200);
    };
    window.addEventListener('resize', handleResize);

    return () => {
      window.removeEventListener('resize', handleResize);
      clearTimeout(resizeTimer);
    };
  }, [sizeMap]);

  return { animationData, isLoading, error };
};

// 使用示例:Tree动画组件
const TreeAnimation = () => {
  // 定义不同尺寸对应的JSON路径
  const treeSizeMap = {
    '375': './tree-smallMobile.json',
    '768': './tree-mobile.json',
    '1024': './tree-medium.json',
    '1440': './tree-large.json',
    default: './tree.json',
  };

  const { animationData, isLoading, error } = useLazyLottieData(treeSizeMap);

  if (isLoading) return <div>Loading tree animation...</div>;
  if (error) return <div>{error}</div>;
  if (!animationData) return null;

  const options = { loop: true, autoplay: true, animationData };
  return <Lottie options={options} />;
};

export default TreeAnimation;

额外注意事项

  1. 打包工具支持:主流打包工具(Webpack、Vite)都默认支持动态导入JSON,但如果你的项目有特殊配置,需要确保JSON文件被正确处理。
  2. 缓存机制:动态import()会被浏览器和打包工具缓存,所以同一JSON不会重复加载,切换视口时如果路径不变,不会触发新的请求。
  3. 移动端优先:如果想优化移动端加载速度,可以把移动端JSON的判断逻辑前置,确保移动端只加载最小的必要文件。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:47:22