首页 > 其他分享 >聊聊懒加载以及优化方案

聊聊懒加载以及优化方案

时间:2024-03-07 10:34:54浏览次数:25  
标签:const img 元素 视口 聊聊 加载 优化 页面

我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。

本文作者:霁明

什么是懒加载(lazy loading)

懒加载是一种将资源标识为非阻塞(非关键)资源并仅在需要时加载它们的策略。这是一种缩短关键渲染路径长度的方法,可以缩短页面加载时间。

懒加载可以在应用程序的不同时刻发生,但通常会在某些用户交互(例如滚动和导航)上发生。

图片懒加载

为什么要对图片懒加载

1、提升性能
减少对图片资源的请求,减少了对网络资源的占用,使得加载其他资源变得更快。与没有使用图片懒加载的页面相比,页面会更快。(图片数量越多、体积越大差异越明显)
2、降低成本
网络资源一般根据传输字节数收费,减少对图片资源的请求,减少了传输字节数,从而降低成本。

实现图片懒加载方式

1、img 标签的 loading 属性

<img src="image.png" loading="lazy" width={300} height={200} />

loading 属性有两个值:

eager 立即加载图像,不管它是否在视口之外(默认值);

lazy 延迟加载图像,直到它和视口接近到一个计算得到的距离(由浏览器定义,chrome、firfox、edge、safari 都是出现则立即加载)。注意,img 元素需要指定宽高 lazy 才能生效。

兼容性

file

兼容性在整体还可以,但如果要兼容低版本就不行了。

优点:使用简单,兼容性也还可以。

缺点:图片加载完成前,会看到空白区域,用户体验不是很好。

2、JS事件监听

监听 scroll、resize 等事件,当事件触发时,获取图片元素的位置信息、滚动高度及视口高度,计算出当前图片元素是否出现在视口内,如果出现了则加载图片。

useEffect(() => {
  const container = ref.current;
  const lazyLoad = () => {
    if (!container) return;

    let timer = null;
    if (timer) {
      clearTimeout(timer);
    }

    timer = setTimeout(() => {
      const imgElements =
        container?.querySelectorAll<HTMLImageElement>('.lazy');
      imgElements?.forEach((img) => {
        if (
          img.offsetTop <
          window.innerHeight + container.scrollTop
        ) {
          img.src = img.dataset.src || '';
          img.classList.remove('lazy');
        }
      });
      if (!imgElements?.length) {
        container.removeEventListener('scroll', lazyLoad);
      }
    }, 100);
  };
  lazyLoad();
  container?.addEventListener('scroll', lazyLoad);
  return () => {
    container?.removeEventListener('scroll', lazyLoad);
  };
}, []);

以上示例通过 offsetTop、scrollTop、innerHeight 来判断图片是否在视口内,另外也可以通过 getBoundingClientRect 方法来获取元素的位置信息,进而进行判断,原理类似。

优点:所有浏览器都兼容,可以比较灵活,自定义触发加载的时机,可以结合图片加载预览提升用户体验。

缺点:需要通过事件触发,图片的位置需要手动计算,容易导致性能问题。

3、Intersection Observer API

交叉观察器 API(Intersection Observer API)提供了一种异步检测目标元素与祖先元素或顶级文档的视口相交情况变化的方法。

补充:目标元素位置是通过 getBoundingClientRect() 方法确定的,所以无论是图片在正常文档流中还是脱离文档流,或者使用了 transform 改变渲染位置,最终都会根据元素相对于视口的实际渲染位置进行处理(可以理解为和视觉上是统一的,看到了就会触发)。

通过这个API,可以检测元素是否进入视口,并在元素进入视口时进行相应的处理。以下是简单使用示例:

useEffect(() => {
  const container = ref.current;
  const imgElements =
    container?.querySelectorAll<HTMLImageElement>('.lazy');
  const imgObserver = new IntersectionObserver((entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        const img = entry.target as HTMLImageElement;
        img.src = img.dataset.src || '';
        img.classList.remove('lazy');
        imgObserver.unobserve(img);
      }
    });
  });
  imgElements?.forEach(function (img) {
    imgObserver.observe(img);
  });
}, []);

创建观察器后,使用观察器对所有懒加载 img 元素进行观察,创建观察器时传入的回调函数会在每个 img 元素进入视口时调用。

回调函数的 entries 参数是状态发生变化的元素对象,entry 对象的 isIntersecting 为 true 则说明元素进入了视口。

上面代码中,当元素进入视口后,修改 img 元素的 src 来加载图片,并取消对当前元素的观察。

兼容性
file

兼容 chrome 版本 >= 58,兼容性整体还可以。

优点:使用简单,不依赖事件监听,也不需要手动计算元素位置,性能更好。

缺点:不兼容旧的浏览器版本(例如chrome < 58)。

页面懒加载

目前数栈产品中实现

目前数栈产品中使用 React.lazy + Suspense + ReactRouter 实现页面懒加载,示例代码如下:

const createLazyRoute = (RouteComponent: any) => {
  return function (props: any) {
    return (
      <Suspense fallback={<Loading />}>
        <RouteComponent {...props} />
      </Suspense>
    );
  };
};

const DataSourceList = createLazyRoute(lazy(() => import('@/pages/dataSource/list')));

const routers = createHashRouter([
  ...
  {
    path: 'data-source',
    Component: PubManageLayout,
    children: [
      {
        path: 'list',
        Component: DataSourceList,
      },
    ],
  },
  ...
]);

页面懒加载流程及原理:

file

tips:关于 React.lazy 和 Suspense 的源码实现原理,可以查看分享《React18之Suspense》

目前这种方案实现了页面懒加载,提升了首屏加载速度,但是有一个明显的缺点,就是当第一次访问懒加载的页面时,经常需要先 loading 一段时间,普通页面还好加载时间较短,部分页面代码较多或使用到一些体积较大的第三方库时,加载时间就比较长,用户体验不是很友好。

产品中实践的优化方案

优化方案

使用预加载来优化页面懒加载等待问题,对于页面上的跳转链接或某些特定元素,当被鼠标hover时,提前加载需要跳转的页面,当用户点击时,可以快速打开对应的页面。
优化后页面加载流程如下:

file

具体实现

结合当前实现方式,进行调整:在 createLazyRoute 方法中,将 factory 挂载到 preload 属性上

const createLazyRoute = (factory: () => Promise<{ default: React.ComponentType<any> }>) => {
  const RouteComponent = lazy(factory);
  const LazyRoute = function (props: any) {
    return (
      <Suspense fallback={<LazyLoading />}>
        <RouteComponent {...props} />
      </Suspense>
    );
  };
  LazyRoute.preload = factory;
  return LazyRoute;
};

封装一个 Preload 组件,给被包裹的元素传递一个 onm ouseEnter 属性(被包裹元素须支持该属性),当鼠标 hover 时,触发 onm ouseEnter 方法。首先会根据要跳转的 pathname 和 routers,找到对应路由组件,然后执行路由组件上的 preload 方法,相当于直接执行 import('..') 进行动态导入,即会立即加载对应的页面,从而实现预加载。

function Preload({ children, pathname }: IPreload) {
  const findRouteComponent = (path, routes) => {
    try {
      const url = new URL(path, location.origin);
      const matchs = matchRoutes(routes, { pathname: url.pathname, search: url.search });
      let matchedComponent = null;
      matchs?.forEach((item: any) => {
        if (
          matchPath(item.pathname, path) &&
          item.route?.path !== '/*' &&
          item.route?.Component
        ) {
          matchedComponent = item.route.Component;
        }
      });
      return matchedComponent;
    } catch (error) {
      console.error(error);
      return null;
    }
  };

  const preloadRouteComponent = (path) => {
    const component = findRouteComponent(path, routes);
    if (typeof component === 'function') {
      const preload = component.preload || component()?.type?.preload;
      preload?.();
    }
  };

  const onm ouseEnter = () => {
    preloadRouteComponent(pathname);
  };

  return React.cloneElement(children, { onm ouseEnter });
}

Preload 组件使用示例:

<Preload pathname="/project/create">
  <Button onClick={this.createProjectSpace}>
    创建
  </Button>
</Preload>

小结

本文针对懒加载主要的两个场景图片懒加载、页面懒加载进行介绍,介绍了图片的三种懒加载方式,以及产品中的做的页面懒加载优化方案,有问题欢迎一起讨论。

最后

欢迎关注【袋鼠云数栈UED团队】~
袋鼠云数栈 UED 团队持续为广大开发者分享技术成果,相继参与开源了欢迎 star

标签:const,img,元素,视口,聊聊,加载,优化,页面
From: https://www.cnblogs.com/dtux/p/18058336

相关文章

  • 常见性能优化方案与实用工具
    微信工程师:常见性能优化方案与实用工具https://mp.weixin.qq.com/s/glrqsyBSIVCDp7oZw2rO_w......
  • Unity3D 多人战场Animation优化详解
    在多人战场游戏中,动画的优化是非常重要的,因为动画是游戏中的核心元素之一,直接影响玩家的游戏体验。对啦!这里有个游戏开发交流小组里面聚集了一帮热爱学习游戏的零基础小白,也有一些正在从事游戏开发的技术大佬,欢迎你来交流学习。在本文中,我们将详细介绍如何在Unity3D中优化多人战......
  • clang在指定-O2时对函数局部变量的优化
    在我们将编译器从g++迁移到clang++的过程中,遇到一个问题,有个工具程序只要一运行就会出现coredump问题,并且用gdb调试core文件也无法获得任何有用的堆栈信息。通过不断尝试,发现只有在clang++使用-O2编译时得到的程序才会发生这个问题,使用clang++-O0或者g++编译时不会发生问题。......
  • bitset 相关优化
    bitset基础用法operator[]:访问其特定的一位。operator==/!=:比较两个bitset内容是否完全一样。operator&/&=/|/|=/^/^=/~:进行按位与/或/异或/取反操作。bitset只能与bitset进行位运算,若要和整型进行位运算,要先将整型转换为bitset。operator<>/<<=/>>=:进行......
  • iOS使用Unity容器动态加载3D模型
    项目背景我们的APP是一个数字藏品平台,里面的很多藏品需要展示3D模型,3D模型里面可能会包含场景,动画,交互。而对应3D场景来说,考虑到要同时支持iOS端,安卓端,Unity是个天然的优秀方案。对于Unity容器来说,需要满足如下的功能:1.在APP启动时,需要满足动态下载最新的模型文件。2.在点击藏......
  • springboot3+vue3(四.2)ThreadLocal优化
    解决痛点:我们在拦截器内已经获取并解析了一遍token数据,如图:然后在获取当前登录用户详细信息时又做了一遍同样的操作,如图:后续如果说需要用到当前登录用户的信息时都要带上token参数,这样是很冗余的。这时就会用到ThreadLocal来进行优化处理。 ThreadLocal工具类/***......
  • SQL优化实战分析
    分享一个案例,一条SQL引发的“血案”!技术人人都可以磨炼,但处理问题的思路和角度各有不同,希望这篇文章可以抛砖引玉。以一个例子为切入点一、问题背景这是一个数据仓库系统,正常情况下每天0~6点会跑批,生成前一天的业务报表,供管理层分析使用。某天凌晨,监控系统频繁发出告警,大批业务报......
  • 搜维尔科技:捕获、分析、优化,使用 Xsens Ergo 创建更安全的工作空间
    简化人体工程学分析,优先考虑员工福祉,并利用客观数据和见解提高生产力。捕获。分析。优化。使用XsensErgo创建更安全的工作空间1.质量数据使用高质量、客观且经过验证的运动数据进行详细的人体工程学分析2.随处使用在最具挑战性的工作环境中随时测量和分析3.快速、实时分......
  • mybatis面试高频问题---执行流程/延迟加载/缓存
    mybatis一.mybatis执行流程理解了各个组件的关系Sql的执行过程(参数映射、sql解析、执行和结果处理)二.mybatis支持延迟加载1.立即加载查询用户信息的同时也可以查询到相关订单信息UserMapper:OrderMapper:UserTest.java打印输出用户信息执行结果:2.延迟加载f......
  • 性能优化 flutter
     1.widgetbuild()方法避免执行重复耗时的非必要操作避免在widget或者state的build()方法中进行重复且耗时的非必要工作,因为当父widget重建时,子widget的build()方法会被频繁地调用。因此确保非必要的耗时工作不放在build()方法中。2.控制widgetsetState()的重建范围......