当前位置: 首页 > Web前端 > HTML

页面白屏时间和首屏时间优化

时间:2023-03-28 10:59:49 HTML

前言不知不觉,马上就要过年了,突然发现自己今年没写技术文章。上半年工作比较忙,下半年节奏慢了下来,时间太长了。我还没有开始写作,因为我还没有开始写作。看得出来,你会习惯懈怠。所以趁着几年前的时间,把今年工作中自己做的一些比较有意思的事情整理一下,做一个总结。这篇文章先说说今年我们重点关注的页面性能优化方面的一些经验。背景现有业务中的3D场景预览页面一直在进行功能迭代,但并未关注页面的白屏时间和首屏时间。通常用手机打开是很快的。偶尔,我会收到页面加载的反馈,我没有太注意那些加载时间太长的页面。直到有一天,同一批客户反映页面打开时间很长。与客户沟通后发现问题的原因是他们所在地区的网络环境不是很好。也正是因为这个机会,让我们意识到不能以自己所在的正常网络作为衡量标准,而且页面打开速度也会受到手机本身性能的影响。大多数情况下,页面的打开时间应该是可以接受的,所以我们决定对预览页面的白屏时间和首屏时间进行一次彻底的优化。为什么是白屏时间和首屏时间?因为这是最能体现页面用户体验的两个指标。用户对页面质量的第一印象在于打开时间。目标设定我们找了行业标杆产品的页面,和我们自己产品的页面对比。在公司的页面性能测试平台上,在与中端机型、中网相同的测试条件下进行了多轮对比测试。结果让我们尴尬的是,在同等条件下,好友页面的平均数据为:白屏时间约500ms,首屏时间:约3.5s。而我们的页面数据是:白屏时间约2s,首屏约12s。面对较大的差距,羞于不关注页面性能的同时,我们也为自己找到了一个优化目标:优化后,仍然在同样的测试条件下,我们页面的白屏时间和首屏时间都不慢A行业标杆竞争对手。优化白屏时间关于白屏时间,我们页面的原逻辑是这样的:当页面上的获取配置的接口(暂且调用getBasicConfig接口,获取title和页面的徽标)返回,获取徽标图像徽标图像在配置的url之后加载。好像没什么大问题。白屏时间的消耗主要在两个地方:页面index.html加载完成后,加载index.js和index.css所花费的时间(单页面应用,产品为index.html、index.js、index.css)getBasicConfig接口需要时间。所以针对性的优化就是基于这两点。首先,看第一点。为了节省加载文件资源的时间,我们的做法是将加载标志的逻辑放在index.html中,这样就可以加载标志,而无需等待其他两个文件加载完成。具体方法也很简单。直接把logo的css布局和压缩后的js加载逻辑写在html中。大体效果如下:看第二点耗时界面。其实我们这里的业务是因为需要支持用户自定义logo设置,所以需要从接口中获取logo配置。如果是固定logo,这一步不做优化。为了解决这个问题,同时兼顾后期首屏时间的优化,界面化是个不错的方式。由于我们的页面渲染使用的是服务端渲染,而这个页面配置的接口没有敏感信息,所以我们最后去掉了getBasicConfig接口,而是通过服务端渲染的方式在数据中插入了一个全局配置window.config,节省了时间getBasicConfig接口响应。这样就可以在html中获取到当前页面的logo配置url。通过这两步优化,页面的白屏时间完全取决于index.html的加载时间,index.html的加载速度是最快的。优化完成后,我们的白屏时间在同样的测试条件下成功降低到500ms以内。总结一下,白屏时间的优化主要有两点:将logo的加载提前到html中,而不是传统的js文件逻辑中(如果一定要在js文件逻辑中做,可以考虑如何减少js的体积,也可以起到优化的作用)如果有接口依赖,尽量去除接口依赖(通过服务端数据注入,或者业务允许的情况下写页面logo地址)优化首屏时间首屏时间的优化是一个非常复杂的过程,这里要补充一点,如何设置首屏时间其实是一个重点。是用fcp还是lcp,还是根据业务自定义一个埋点定时作为首屏时间,也有一些特殊性。我不会在这里详细介绍。我们页面的首屏时间是3D页面渲染完成的时间点,通过自定义埋点获取。具体表现大致就是logo加载的时间(logo消失的时间就是monitor3D页面渲染完成的时间),从对比h5性能上页面每一帧的截图序列可以很容易的得到测试平台。标志结束后的下一张截图是我们比较的首屏时间。经过一系列的优化手段,我们的页面首屏时间成功达到了预期的目标。基于整个页面加载过程,我们来详细说一下首屏时间的优化点:压缩索引体积。我做的是优化index.js的体积(index.css一般很小,优化意义不大)。我们页面的index.js原来的大小是1.2mb,显然有很大的优化空间。经过优化,页面的打包大小从1.2mb成功缩减到750kb。包体积优化的几个要点:第一步:使用可视化包体积分析工具,从包体积比由大到小,找到需要优化的目标。Step2:进行针对性优化,从以下几个角度寻找优化点:大文件模块的类似替换case1:找一个类似的可以满足需求但体积小很多的模块包来替换case2:如果导入某个包只用其他方法,可以考虑去掉包,自己写这个函数大文件引入cdn代替npm包(引入cdn会带来http加载耗时,所以效果受文件大小影响。原则上文件越大效果越好,实际效果需要实际测试)按需引入工具包(如antd、lodash)静态资源图片处理case1:大图cdn化(也面临时间问题)-耗费http加载,真实效果需要实际测试)case2:小图base64处理(大图base64化在页面显示分析会耗时较长,所以真实效果需要实践检验,小图肯定会有效果)去除无用代码,检查代码是否有冗余或者去除无用代码和无用npm包,也需要仔细检查production在打包配置中开启环境中console.log和注释代码的自动删除,去掉大sourceMap配置文件的压缩,因为有一些3D模型相关的obj,gltf文件,贴图jpg文件需要压缩在页面中从oss读取,所以这块其实和index.js的逻辑是一样的。它通过减小资源体积的大小来缩短首屏时间,从而减少HTTP响应时间。网络请求优化,网络请求直接关系到页面资源的响应时间,所以优化这块是重点之一,可以做的优化项有很多。这块的原理也可以概括为2点:尽量减少不必要的http请求,在必要的http请求下加快请求的响应。具体来说,我们页面做了如下工作:接口请求优化。尽量减少页面加载结束前的接口请求数。上面提到的白屏时间优化去掉了接口getBasicConfig,去掉了页面的基本配置信息,通过服务端渲染向页面窗口注入数据。这是一个接口异步加载优化和业务迭代的例子。接口大概率是批量添加的,所以不同批次接口之间的接口可能会出现用await同步加载的情况。可以把这块逻辑梳理一下,并发异步加载没有依赖的接口,也就是使用promise.all同时处理多个没有上下文依赖的接口请求,节省所有请求完成的总时间。接口合并。通过梳理所有相关接口,检查业务逻辑相关的接口是否存在合并请求中无用或重复的接口。整理后对于所有相关接口,丢弃一些无用或重复的接口,优化js和css请求。一方面检查是否有无用或重复的资源请求。另一方面,这两个资源的下载和解析默认会造成页面阻塞。可以,但是大多数情况下异步加载这些资源不会影响页面的正常解析。因此,可以针对此类资源进行如下操作:检查无用或重复的资源请求,删除这些多余的请求。或者在代码中构建一些小的资源(比如一些第三方的css样式文件,如果文件内容不多,可以复制出来放在页面组件的css样式中,减少一次网络请求)indexscript中的.js加上asyncscript第三方js资源加上deferlink中的index.css加上preload在请求链接上预加载资源,合理利用缓存。如果可以预测资源在未来某个时间点会被访问,那么如果提前使用空闲时间线访问一次,由于缓存的作用,后续实际访问的加载速度会大大加快.而这就是prefetch和dns-prefetch的效果(具体说明见相关资料)。请注意,这与上一点中提到的预加载不同。前者是针对当前页面的资源,后者是针对未来可能的访问。页面资源。我用我们页面的一个实例来说明一下:我们预览页面的打开路径之一是通过查看列表页面的详情来打开的。这个列表页面包含了查询到的所有预览页面,并且这些预览页面的域名是相同的。所以在列表页中,我们可以dns-prefetch预览页的域名地址,预取预览页中的重要资源。这样,当用户通过列表页打开某个预览页时,预览页的加载速度会大大提高。CDN优化,这块优化是针对需要从CDN获取大量静态资源的场景。比如我们的预览页面中有大量的图片资源需要从CDN中读取,所以起到了非常显着的作用。具体优化方式如下:如果没有接入CDN服务,则接入CDN服务。很多情况下,资源直接存放在oss的公共bucket中,可以通过oss提供的域名地址直接访问。如果是这种情况,通过cdn域名访问cdn,访问资源,访问速度会有所提升。CDN缓存能力,开启CDN缓存和CDN预热,加快页面二次访问时的打开速度,开启缓存的方式取决于所连接的CDN提供商,一般默认开启缓存。如果没有打开,可以打开这个配置开关。CDN预热相当于提前访问资源,这样即使用户第一次访问资源,也已经可以命中CDN缓存,提高CDN的配置。CDN服务根据具体情况有更细的区分。如果成本足够,可以考虑以下措施进一步提高资源访问速度:增加CDN节点数量,尤其是有国内外访问需求的,国内也要考虑东部的地域影响,西、北、南。多设置几个节点,靠近人流量大的地方。CDN加速、CDN带宽扩展、业务逻辑优化,加快响应速度。这一块需要根据具体的业务场景进行优化。可能没有优化的余地,也可能会起到举足轻重的作用。优化的原则是尽可能减少加载结束前的业务逻辑。看一下我们页面上的两个实际例子:在页面加载结束之前,场景中还有获取“活动轨迹”的逻辑,具体包括获取相关界面数据,对数据进行处理,存储在页面存储中。而这个数据的具体使用时间是在用户手动点击页面上的一个“StartTrack”按钮之后,所以这块业务逻辑可以完全延迟。页面首屏加载完成后,做这个逻辑处理。通过这种逻辑上的时序变化,可以加快首屏的渲染速度。原页面加载结束的时机是接收到3D场景触发的sceneLoaded事件。所以我们研究的重点是能否改进这个3D场景sdk中事件的触发时机。通过这个sdk的优化,我们最终成功的将这个事件的触发时机提前了很多,从而达到了缩短页面首屏时间的目的优化过程在整个优化过程中,也是非常重要的一点制定具体的优化措施,制定合理的优化方案,分阶段进行。一开始就要找出主要矛盾。影响页面加载性能的因素有很多,但每个因素所占的比例是完全不同的。这个需要根据测试数据和开发经验来合理判断,将影响因素降序排列,影响最大的因素先解决。解决1个大影响因素的效果很可能比解决10个小影响因素更好。整个优化过程并不是一蹴而就,可以分成多个时间节点进行效果验证。一方面,及时看到效果,有正反馈。另一方面,也可以有计划有节奏地提高优化效率。我们这次页面的整个优化过程大致分为三个阶段:第一个优化主要是进行了页面js分包压缩和大文件压缩。优化后,首屏时间缩短至9.5s左右。二次优化主要进行网络请求优化。优化后,首屏时间缩短至7.5s左右。第三次优化主要进行CDN优化和业务逻辑优化。优化后,首屏时间缩短至3.5s左右。感觉在业务快速迭代的过程中,页面性能往往很容易被忽视。而白屏时间和首屏时间是用户体检的第一印象,这恰恰是前端贴近用户侧的优势和责任。经过这次优化,我们的页面打开速度明显比之前快了很多。看着前后效果对比,一种满足感油然而生。也希望这些优化经验可以帮助到有需要的朋友~