本文首发于:github.com/bigo-fronte… 欢迎关注、转载。
一、背景
LikeeLive首次举办线上年度盛典,不管是用户玩法内容还是主播玩法内容都是月度活动的2-3倍,安排了前端4个人投入开发,在各自开发完成后合并代码,在自测阶段发现测试环境页面加载过慢,性能评分未能到达上线要求。
二、问题分析
先放大图
在Performance这方面的评分只有32分,一个对用户体验极差的分数。首屏的加载时间需要6.2s,阻塞时间480ms,css文件最大文件大小超过1.7m,js文件最大文件大小超过800kb+。
三、问题方案
首先加载时间与资源文件大小是有一定关系的,所以先对1.7m的css文件与800kb的js文件进行瘦身。
css文件分析
项目构建过程中会将小于10kb的图片转成base64字符串储存到css文件中,这次活动用到的本地图片数量700+,icon小图标100+,检查了下编译后的css文件,存在大量的base64编码的图片,所以考虑接入雪碧图构建,对小icon进行合并处理,减少图片转成base64编码数量以及网络请求数。
目前调用icon小图标的方式是通过mixin方式进行调用的,调用方式如下:
@mixin Ricon($width, $height, $url, $important: '') {
@include background(#{$baseURL}#{$url}.png, $important);
display: inline-block;
width: $width;
height: $height;
background-size: 100% auto;
background-position: center center;
}
@mixin background($url: '', $important: '') {
@if $url != '' {
@if ($important != '') {
background-image: url($url) !important;
}
@else {
background-image: url($url);
}
}
background-repeat: no-repeat;
background-size: 100% auto;
}
复制代码
考虑到改造成本以及雪碧图的接入成本,解决方案是在构建过程中接入了webpack-spritesmith,优点是
- 构建过程中可根据指定目录自动生成及更新雪碧图与scss文件
- 自动生成的scss文件模版允许自定义化
- 无需手动引入生成的scss文件,可在webpack配置中配置自动引入
构建配置如下:
// 配置代码
new SpritesmithPlugin({
src: {
// icon小图标目录
cwd: './src/like/act_30083/assets/img/icon/',
// 合成图片格式
glob: '*.png'
},
target: {
// 合成雪碧图本地文件地址
image: path.resolve(__dirname, './assets/img/sprite-ignore.png'),
css: [
[
// 生成雪碧图样式文件地址
path.resolve(__dirname, './styles/mixins/_sprite-ignore.scss'),
{
// scss文件模板
format: 'function_based_template'
}
]
]
},
customTemplates: {
// 自定义模板
'function_based_template': templateFunction,
},
apiOptions: {
// 雪碧图引用地址
cssImageRef: '~@assets/img/sprite-ignore.png',
},
spritesmithOptions: {
// 合成规则
algorithm: 'binary-tree',
// icon之间的距离
padding: 10,
}
});
复制代码
考虑到对mixin的改造成本以及对现有代码的影响成本,所以对模版的定义如下:
// 模板代码
function templateFunction(data) {
const spritesheet = data.spritesheet // 精灵图对象
const unit = 'px' // 单位
const w = spritesheet.width // 精灵图宽
const h = spritesheet.height // 精灵图高
//循环雪碧图并且设置对应图标样式的宽度与高度
// data.sprites: 精灵图小图标遍历
const perSprite = data.sprites.map(function (sprite) {
const offsetX = sprite.offset_x // 距离左上角的x轴距离, 这里为负数,已经默认算好
const offsetY = sprite.offset_y // 距离左上角的y轴距离, 这里为负数,已经默认算好
// icon小图标类名都会以icon-前缀开头,如果图片名称不包含icon-前缀,则自动补充前缀
const prefix = sprite.name.indexOf('icon-') > -1 ? '' : 'icon-'
// 需要return出去,并且返回一个默认样式
return `.${sprite.name} {
background-image: url(${spritesheet.image});
background-size: ${w}${w === 0 ? '' : unit} ${h}${h === 0 ? '' : unit};
background-repeat: no-repeat;
width: ${sprite.width + 2}${unit};
height: ${sprite.height + 2}${unit};
background-position: ${offsetX !== 0 ? offsetX - 1 : offsetX }${offsetX === 0 ? '' : unit} ${offsetY !== 0 ? offsetY - 1 : offsetY }${offsetY === 0 ? '' : unit};
display: inline-block;
}
`
}).join('\n')
return perSprite
}
复制代码
对现有mixin的修改如下:
@mixin Ricon($width, $height, $url, $important: '') {
@extend .#{$url};
}
复制代码
修改构建方式,引入雪碧图的构建,通过保证雪碧图生成的规则与图片小icon的命名规则一致,来确保代码不需要进行大面积修改,实现最小粒度的代码改动达成的代码优化,修改完成后css文件从1.7m减小到了300kb+
接下来就是对js文件进行处理,对首页js文件代码进行review,发现由于活动内容过多,首页组件20+,入口文件加载了全部第三方组件库,处理方案如下:
- 首屏可视区域组件同步加载,首屏非可视区域组件进行异步加载,通过
() => import()
的使用方式在webpack的构建过程中将异步加载的组件代码从首屏加载的js文件中拆分出来单独加载,并且设置首屏可视区域组件加载渲染完成后再进行首屏非可视区域组件的加载渲染。 - 第三方组件库区分全局引用与局部引用,将局部引用的第三方库组件拆分到对应的组件进行异步加载。
优化完成后js文件从800kb+减小到了200kb+
对scss文件及js文件优化完成后,加载时间从6.2s减少到了3.8s,减少了2.4s的加载时间,但加载时间仍然过长,对页面的network进行分析,发现主要原因是
- 页面前置的js必需资源较多,且必需在业务代码之前进行加载
- 头图动图使用了svga进行加载显示,加载大小为1.7m,加载时间较长,阻塞渲染时间也较久。
对于前置资源加载时间较长的问题主要通过在head头添加preconnect以及dns-prefetch进行优化处理
preconnect
preconnect 允许浏览器在一个 HTTP 请求正式发给服务器前预先执行一些操作,这包括 DNS 解析,TLS 协商,TCP 握手,这消除了往返延迟并为用户节省了时间。
dns-Prefetching
DNS prefetching 允许浏览器在用户浏览页面时在后台运行 DNS 的解析。可以在一个 link 标签的属性中添加 rel=”dns-prefetch’ 来对指定的 URL 进行 DNS prefetching.
“DNS 请求在带宽方面流量非常小,可是延迟会很高,尤其是在移动设备上。通过 prefetching 指定的 DNS 可以在特定的场景显著的减小延迟。
头图svga分为开场播放以及循环播放两部分,大小以及加载速度如下:
没有缓存的情况下加载2s,造成页面打开时存在大片空白。
处理方案有两种,一种是使用静态图替代加载时的空白,提升用户的体验,但对加载时长没有任何帮助,第二种是使用其他格式代替svga来减少加载时间,提升用户体验。
目前主流的动图实现方式有Gif,png帧动画,svga以及Lottie。
其中Gif/帧动画都是通过播放每一帧图片来实现动画,需要加载较大的资源,实现效果较长,相对与svga的实现方式更差。
svga与Lottie的比较如下:
两者的比较来说Lottie是较优的,但存在较大的改动,改动成本比较大。考虑到当前的头图不需要使用aplha通道,所以可以考虑通过使用分割的视频来实现,改动较小,开发成本小,实现代码如下:
<!-- 地址为后端返回的视频地址,支持视频分割,设置封面避免弱网情况下视频加载过慢 -->
<video
ref="video"
preload="auto"
:poster="headImg"
:src="headerMp4"
autoplay
muted
loop
webkit-playsinline="true"
playsinline="true"></video>
复制代码
总结
这次活动是入职团队以来负责最大的一个活动,投入4个人力历时15天开发完成。开发过程中还面临着线上业务的紧急处理以及开发过程中需求方对需求内容的频繁变动,不过还是在deadline前完成开发任务并交付验收提测。经过这次需求的优化,也在前端的性能优化领域方面也有所收获,获得了一定的成长。希望团队能够继续在这样的氛围下共同奋斗,一起进步。
欢迎大家留言讨论,祝工作顺利、生活愉快!
我是bigo前端,下期见。