复杂图片列表实战及性能优化

182 阅读6分钟

该需求是一个由可配置高度的容器,和容器内的自适应图片框组成。其中容器可以配置图片列数,容器内的图片框宽根据容器的宽还有配置的图片列数决定,图片框的高是要和图片框的宽保持一致。

需求拆解

image.png

如上图所示,外层容器包裹着几张图片框,图片框的背景色我用粉色突出,这样看的更清晰一点。其中容器高是配置的,可以动态设置,容器宽跟着页面走,我这里给了一个定值方便演示,粉色的图片框的宽度自适应,高度根据宽度来。而且图片框里的图片要自适应最大程度铺满图片框且不能变形。

合在一起感觉挺难实现,我们不妨拆成外层容器和图片框两部分看。

解决方案

  • 外层容器:内部的布局是自适应的且还有根据配置的固定列,如何接触过grid的同学应该想到了,这简直是为grid量身定制的需求
  • 内部图片框:这个框宽高都不确定,宽可以根据grid布局来自适应,那么高如何跟宽保持一致呢,这里就要轮到aspect-ratio出马了,该属性可以指定元素的宽高比,那我们指定成1比1那不就是我们要的正方形了么。最后还有实际图片在图片框里如何铺满的问题,很多同学第一时间应该联想到了background的解决方案,但是这个方案并不方便我们后续给图片设置懒加载,这里就算用img标签,我们也可以通过 object-fit属性来实现类似background的效果。

以下为实现代码

<div class="box">
    <div class="item"> <img src="https://q7.itc.cn/images01/20240208/8058045af46f4984abea548d5b2243bf.jpeg" alt="">
    </div>
    <div class="item"> <img src="https://pic.pngsucai.com/00/89/38/5608e2c42a1cc769.webp" alt="">
    </div>
    <div class="item"><img src="https://pic.pngsucai.com/00/89/38/5608e2c42a1cc769.webp" alt=""
        style="transform:rotate(-90deg);">
    </div>
    <div class="item"> <img src="https://q7.itc.cn/images01/20240208/8058045af46f4984abea548d5b2243bf.jpeg" alt="">
    </div>
    <div class="item"> <img src="https://q7.itc.cn/images01/20240208/8058045af46f4984abea548d5b2243bf.jpeg" alt="">
    </div>
  </div>
  .box {
    border: 1px solid black;
    width: 800px;
    height: 350px;
    padding: 4px;
    overflow-y: auto;
    display: grid;
    /* repeat的第一位列数实际开发中是配置的变量 */
    grid-template-columns: repeat(4, 1fr);
    grid-gap: 4px 4px;
    align-items: start;
  }

  .item {
    width: 100%;
    /* 设置宽高比为1比1 保证是正方形*/
    aspect-ratio: 1/1;
    background: pink;
    display: grid;
    place-items: center center;

    img {
      width: 100%;
      height: 100%;
      /* 设置图片填充方式为contain实现不变形的情况下铺满 */
      object-fit: contain;
    }
  }

我们可以看到box容器用了grid的方式实现起来非常优雅,然后我们通过 aspect-ratio来保证图片框是正方形,最后在设置图片本身的object-fit来实现从外到内的完美自适应效果。我们可以通过上图看到,哪怕是细长的图片,不论横竖都能在图片框里以一个理想的方式展示。这就是现代css强大之处。

性能优化

由于用户页面可以配置的插件非常多,加上用户数据量不固定,图片数量也不固定,就有可能出现例如100条数据,每个属于的图片容器有100张图片的极端情况。这对设备的负载就非常的重。经过分析,性能优化决定从交互和代码两个层面入手。

交互优化

图片懒加载

图片组件为固定高度的一个容器,超出的图片可以滚动展示。这里很容易联想到滚动触底懒加载的方式。 但是有些用户习惯缩放整个浏览器页面,这会有一定概率导致缩放之后,原本最后一行的图片没法触底,从而无法触发懒加载。其次这种加载方式每次往下滑动都要给一定的加载缓冲时间,体验并不顺滑,所以这里选用了图片原生的懒加载。

    <div class="item">
      <img src="https://q7.itc.cn/images01/20240208/8058045af46f4984abea548d5b2243bf.jpeg" alt="" loading="lazy">
    </div>

原理是浏览器会自动延迟加载带有loading="lazy"属性的图片,直到图片出现在用户视口附近时才开始加载。 具体的原理如下:

1.当页面加载时,所有带有loading="lazy"属性的图片标签的src属性都会被设置为空字符串或一个占位符。这意味着这些图片不会在初始加载时进行请求和下载,从而减少了页面加载时间。

2.当用户滚动页面或者将页面视口移动到图片附近时,浏览器会检测到图片即将出现在可视区域内,然后开始加载图片资源。

3.浏览器会根据图片的位置和优先级来决定加载图片的顺序。通常情况下,先加载距离视口近的图片,然后再加载距离视口远的图片。

4.加载图片时,浏览器会发起相应的网络请求,下载图片资源。 如此所有的图片框都会预加载,避免了不会因为浏览器缩放导致不能加载的问题,而且下滑顺畅无阻体验更好。

整个组件的懒加载

但是仅仅懒加载图片这样还是不够的,在用户实际工作中发现,那些4g内存的用户,一开始还可以流畅的工作,但是一旦任务超过某个阈值,比如超过50条任务之后,浏览器就会因为爆内存变的非常卡,甚至页面直接崩溃 。造成这个的原因是因为已经加载的图片和元素过多,那我们就要针对这个情况,减少页面实时的元素数量,对于已经已经加载过的任务,要清空节点,释放内存给新的任务用。

这里使用的是react-lazyload插件

<LazyLoad height={1000} unmountIfInvisible={true} offset={1000}>
    <div>
      这里是数据循环体的容器
    </div>
 </LazyLoad>

通过设置容器高度和offset预加载量达到整个组件的懒加载效果,再设置unmountIfInvisible属性使得延迟加载的组件在视口中不再可见时将被卸载并替换为占位符。以此达到减少渲染节点的目的。

代码优化

在我们实际开发中图片组件除了本身容器组件和图片框组件外,其实还有很多根据功能拆分的子组件,这么多组件层层渲染耗费的性能资源也不少,所以为了最大程度减少不必要的渲染,我们把链路上所有的组件都加上了memo,如下案例

export default memo(GridImgBox);

如果实际代码中还有什么影响性能的点,还要针对性的做出优化。

总结

如若遇到高度自适应或者多行布局复杂且灵活的,可以尝试使用grid来完成布局,这往往比flex实现来的更容易且优雅。

同时在面对一个页面有庞大的组件数量和嵌套的时候,可以尝试从交互和代码两个层面入手来做性能优化。 交互方面可以通过各种手段或者插件来对数据做懒加载或者分页操作,从而缓解首次加载的压力,对于页面持续加载内容也很多的,可以尝试清除之前加载的内容来保证整个页面渲染内容在一个较低水平。代码方面为每个高频渲染组件套上memo来减少不必要的渲染次数,同时检查业务代码,看看有没有占用内存的元素没有及时清除,或者哪些算法或者复杂逻辑效率低下的。