一文教懂你在前端性能优化上“抄近道”

586 阅读6分钟

“抄近道”的背景

项目背景是海外主站,技术上采用服务端渲染,并且一套代码构建多国别、跨屏、多端的响应式设计页面。

这类糅合了N种产品策略、设计理念的页面。在高效率背后是,代码混乱、性能参差不奇、优化工作复杂,短时间内提速难度很高。

同时谷歌SEO收录过程中,Google Search Console 反馈很多页面的核心指标未通过,例如:M端(CLS超过0.25)、PC端(CLS超过0.1、LCP超过2.5)。

难度高、SEO收效工作紧急,因此寻找正确的切入点很关键!

“关键点”分析

使用Goolge的页面性能分析工具生成PSI 报告,发现实验室数据和真实环境中样本数据均不理想。例如:M端FCP大于1.8秒以上、CLS大于0.2。(在谷歌度量页面体验分数中 Mobile Firendly 比重大,因此数据分析上侧重点也放在移动端)

代码层面分析(正常的屎山分析),数据化配置、丰富动态内容类需求,增加了网络开销。导致出现阻塞渲染问题、加载过晚问题,用户体验较差。

例如其中一个详情页分析如下:

1. PSI报告核心网页指标分析,跑分结果M端 29 分、PC 73 分

Google给出优化建议如下:

  • 减少未使用的 JavaScript 有望节省 4.06 s

  • 推迟加载屏幕外图片 有望节省 2.15 s

  • 减少未使用的 CSS

  • 移除阻塞渲染的资源

2. 页面首屏渲染时,Network控制台分析(禁用本地缓存)

  • DOM解析完成后,网络资源加载时长占用总加载时长的 70%/

  • 图片资源加载占网络总传输的 50%

据以上分析可知,图片类资源加载时长占比大、在整个项目中流量比重高,应用渲染的资源加载效率较低。所以对图片加载精细化控制,即可快速提升用户体验、提升核心指标分数,也可降本增效,收益显著。

“图片加载”方案分析

现有图片资源处理方案,已采用了后端的动态切图服务(跨端流量适配)和前端懒加载(首屏流量适配)。

其中前端懒加载代码如下:

window.addEventListner('scroll',throttle(funciton(){
    const imgs = document.querySelectorAll('img[data-src]')
    imgs.forEach((img)=>{
      const imgSrc = img.getAttribute('data-src');
      if (imgSrc && this.inView(img)) {
        img.src = imgSrc;
        img.removeAttribute('data-src');
        img.addEventListener('load', this.fadeOut.bind(this));
      }
    })
}))

即所有的图片标签,如果标记了自定义属性data-src,则会启动懒加载模式。具体如下:

1. 首屏内图片即时加载

2. 视口外图片资源滚动至视口内触发加载

这样降低了网络负载提升页面性能,也节约了流量成本。但是随着业务场景复杂化,图片加载和不加载标记分类简单,加载触发机制太单一,所以需重新先进行分类归纳。

根据问题特征分为如下四类:

  • 用户体验优先类_(不一定要在首屏视口内)_

       首屏展示资源,在DOM中解析渲染后直接触发资源请求。例如:首屏展示区域、非SVG大图标、品牌位广告等,需要优先保证用户体验、商业利益的。

  • 资源加载优先类_(渐进式加载)_

       通过自定义文件路径属性和标记属性,绑定这类图片资源在用户首次互动(通常选用滚动)时触发优先加载出来。例如:视频资源、动态图片资源、非视区内主题背景图等需要保证体验但是不是首屏打开就需要的,让用户来主动触发。

  • 通用型懒加载类_(不属于1类和2类,就属于3类即通用)_

       通过自定义文件路径属性和标记属性。滚动内容且在视区内可见时加载出来,即最常见的懒加载模式。例如:常见feeds流、图文并茂的文章、视频库、图库等这类侧重于内容展示,避免网络带宽浪费,按需加载。

  • 自定义类_(特殊类是满足需求,hack手段)_

       定义触发器权限暴露给当前调用过程。例如:焦点轮播图、依赖数据化配置、自定义渐进式加载等要求更高的控制场景,一般是为了追求预加载和懒加载之间的平衡。

根据以上问题基本,进一步抽象出控制器、触发器装置在页面整体的资源加载过程中,区分模块、区分时机、区分特定条件的控制请求、延迟触发时机。同时为每项资源精准匹策略亦可批量处理。

图片分级加载策略实践

步骤如下:

1. 根据图片类型、体积、位置、体验进行综合判定,打上对应数据标记,例如:0(用户体验优先)、1(资源加载优先)、2(通用懒加载)、3(自定义场景)。

2. 定义场景触发事件,直接请求、用户交互触发、用户滚动可见触发、自定义触发事件

3. 数据标记及触发定义就绪后,初始化控制器并绑定触发器。

4. 当客户端解析图片标签并调用控制器时,会判断资源是直接请求还是增加至延迟队列。

5. 直接请求资源会根据控制器控制请求优先级别,分为DOM解析时发送、DOM渲染完成后发送。

6. 管控资源进入延迟队列,直接加载资源会更快的装载并进行展示。

7. 延迟队列在外部进行场景触发时会根据触发器类型不同不同分割成若干个请求队列。

8. 例如在第1步中建立的0、1、2、3四种标记,1类对应的触发器时机是用户首次滚动即加载,2类对应时触发器时机是用户滚动至可视区域时加载。

9. 自定义场景即当部分图片资源加载,需要在懒加载和预加载中寻求平衡,做到体验和加载效率最佳。

class DyanmicLoad {
   
   // 代码省略
}

在详情页中得到改造效果,如下:

  • 最大限度减少主线程工作 下降 37.6%

  • 缩短javascript执行时间 下降 40.7%

  • 应避免出现长时间运行的主线程任务 下降 10%

  • 降低第三方代码的影响 下降 99%

在项目中增加的公共代码不到20行, 成效极快。策略是个好东西!

“大道至简,衍化至繁”

图片加载策略的改造,最大节省了网络开销。其实图片优化这个问题不重要,策略重要。JS模块、API接口、流媒体内容、字体等之类的加载均可做分级策略。策略大同小异,只不过如何提升开发者体验。通过接口、配置可快速使用。这就需要挑战工程代码,因为涉及一系列的改造,从构建模块、请求库、播放类组件、共享状态管理、本地化数据管理,需要二次封装,侵入性的改造来适配项目。

话到此处,现在的前端框架走入了一个怪圈,违背了“大道至简”。太多的项目开始只能shim,不能pollyfill。但是核心库要关注的是渲染效率问题,画蛇添足之处就是茅坑,终将变成屎山。