前端图片引入方式神演算

前端图片引入方式神演算

| 导语 本文只提供推理方式和分析方法,不保证样本及计算的精准性,慎读!!!

先阐述一下背景:

我们团队对于图片的使用方式有一个明文规定如下:

  1. 凡是需要合并雪碧图,或是编码base64的图片,均放入slice目录下对应模块目录里,gulp-postcss会统一编译处理。
  2. 直接以单图形式引入页面的图片,放在page/aaa/bbb/img目录下(aaa表示业务单元,bbb表示具体页面),使用相对路径./xxx.png直接引用。
  3. 全局复用的单图,放入common/img目录下。

目录结构大概是这个样子:

好,那么问题来了,xxx.png这个图片应该是放入slice合雪碧图呢?还是base64编码内联呢?还是放入以传统单图方式引入呢?

这就是我们今天的议题。

众所周知,页面内图片的引入方式一般有这3种:雪碧图,base64内联,普通单图。(canvas,svg等非常规方式不在此次议题里),先简单分析一下三种方式的优劣势:


嗯,大概的情况是这样的,然后我来稍微扩展解释一下:

1. base64图本身确实无法缓存,但是base64图一般是存在于css里的,那么就可以随着css被缓存而实现间接缓存,所以它的缓存属性不是“无”。说它“差”是因为并不是直接被当做图片缓存。当然如果是直接写在html里的,那就没法缓存了,这个要注意。

2. base64额外增加html/css大小并不是主要问题,问题是,因此造成的渲染堵塞有时候是致命的!而作为图片文件加载则不存在这个问题,因为图片是不会堵塞到html和css加载的,因此也不会影响首屏渲染。(当然了,你非要把img标签写在style前面那我只能说,哥,我服~~~~)

了解了三种方式的优劣势之后,对于使用场景简单归纳一下:

1. 页面自身独有的图片,全部合并成一张雪碧图。

2. 公共模块或者公共组件,如果包含多张图片,则各自为阵合并各自的雪碧图;如果只有一两个图片,或者包含有可以被其他模块、组件、页面复用的图片,则使用灵活性好的单图模式或base64模式。

不过这种说法遗留了一个问题:例如所有页面都有的吊顶区域,假如那里有一个小图,注意,是一个喔(如果是很多的话就合并啦),这种时候是直接单图引入呢?还是base64内嵌到吊顶的css里?

好像二者都可以是吧,用单图的好处就是我在首页缓存后,逛其他页面时候就不用再加载了,当然了首页就会多一个请求;而用base64模式,会少一个图片请求,但会增加吊顶css的文件大小,从而间接增加了首页的渲染堵塞时间。好吧,又TMD陷入了纠结。。。

别急!

下面我们再对base64模式做一个简单的分析:

先明确我们对于base64图片劣势的控诉点在于,1:丫会增大原始图片文件;2:植入css之后会增大css文件大小。

做一个简单的实验,我把几个全局经常出现的小图标,用base64编码,结果:

平均增大35%


但是!

gzip压缩后 —— 4%~40%,平均增大22%


当然样本少是一个问题,但大概的我们还是能看出来一些端倪:base64确实会增大文件,而且即使做了gzip后增幅还是居高不下。这也是为什么我们一般不会对大图片进行base64编码的原因,假如对一张100KB的图片编码,将会增加20-30KB!这是蛮恐怖的了。但我们现在说的是小图片呀,一个小图片1KB左右,即使增大30%也就增加三百多字节而已。

我们思考的更进一步,究竟怎么样的文件大小增幅,是我们可以接受的?

一个常识,普通人的肉眼可识别的视觉暂留是50ms。而根据多年前端实战经验,对于网页渲染速度,肉眼可敏感识别的渲染时长间隔是500ms,所以一般一个css3过渡效果,transition-duration 为0.3s和0.8s才会有显著区别,而0.3s和0.5s的区别,除了号称“像素眼”的重构同学和有细节控的设计师能感知外,一般人很难明显感知。

那么因此我们是不是粗略的得出一个结论:对于首屏渲染时间的减少或增加,用户可明显感知的变化范围是50ms-500ms之间,也就是说,即便你优化做得再好,小于50ms的变化,是不会被感知的,另一方面,如果你因为某个原因增加了首屏渲染时间500ms,就会产生一个很大的感官变化。

好了,这么说来,我们能接受的文件大小增幅,所导致的首屏渲染时间增加,应该控制在500ms内。对于身处公司内网的我们而言,M/s的速度显然不用在意这些细节,500ms可以轻轻松松加载几MB的资源,就算是普通用户,现在宽带整体速度都6得飞起,500ms加载几百KB应该不成问题吧。

但是!我们不能这么想啊,做产品的会把用户当做小白,我们做开发优化是不是也应该假设用户还停留在拨号上网时代?哈哈哈,开玩笑了,这倒不至于,但我们确实可以假设用户网速很一般,100kb/s的网页加载速度,对自己够狠了吧我。

基于网速100kb/s的假设,500ms可以加载50kb的资源。。。。等等!总感觉哪里不对!

一个文件的加载,应该包含了这些个过程:

所以我们理论上“500ms可以加载50kb的资源”,指的是download这里的速度而已,但是一个小图片从请求到渲染,需要经过请求排队,请求堵塞,等待响应,下载等众多环节。。。那么500ms我们到底能加载多大的文件呢?这个问题我真的回答不了,因为这涉及到的环境变量太多了,请求堵塞,网速抖动,浏览器版本,服务器速度,dns解析等等都有可能影响到这个结果。这。。。文章写不下去了怎么办。。。不能放弃治疗啊!那么我们干脆就更加大概一点估算好了,就假设这500ms中,只有250ms是给我们用来下载资源的,那么100kb/s的速度我们可以下载25kb的资源,嗯。。。。看起来还蛮是合理的呢。。。。

我们多找几张小图看一下timing的分布:(10kb以内)


有没有发现一个规律?对于10kb以下的小图而言,下载时间其实几乎可以忽略不计(1%左右),而真正占用贷款的是这一次次请求经历的漫长的流程(请求排队,请求堵塞,等待响应....)

补充验证:当图片大小增加到100kb以上时,下载耗时平均是总耗时的50%不到。

经过上面一大推的推演和样本测试后,我们得到了一些相对合理的参数值,接下来要抛大招(计算公式)了!

终于!我们拿到了我们想要的计算结果!2.6倍base64图片总大小的下载时间,是我们增加的首屏负荷。之前我们已经说了,在不影响用户感官明显变化的情况下,我们仁慈的允许多500ms的下载时间,在100kb/s的弱网条件下,最终计算出,允许内嵌的base64图片大小是20kb!20kb!20kb!这和我们刚刚大概估算的25kb很接近啊!看来有些时候计算无力的情况下估算还点靠谱的。。。

机智的我经过一系列估算后,得出了一个拙劣但相当有意义的答案!意义在于,我终于知道什么大小的图片叫做小图片啦!!!不知道这个历史性难题难倒了多少重构GG!


好吧,别打我,我知道我的计算有点暴力。。。。

Anyway!我在文章副标题里就说了,

本文只提供推理方式和分析方法,不保证样本及计算的精准性,慎读!!!

讲真,我的切入点和分析方法应该是没有问题的对吧各位?只是这其中需要计算的数值实在涉及到太多不确定性,我表示暂时受到那么一丢丢困扰,所以就先估算之,感兴趣的同学可以按照此方法重新计算哈。

做这些蛋疼的研究,终归还是要回归到业务上的,那么我们文章开始的疑问是不是已经解决了呢?经过我们一步步的推演和深入浅出,问题基本解决了。

下面简单归纳一下不同场景所应该使用的图片引入方式:(正经脸 -_- !!!)

  • 全局通用的,非特定页面或模块独有的图片,采用单图或base64方式引入,二者区别如下:
    • 若该图片在多处使用或图片本身较大(这类图总体积大于20kb),则使用单图模式
    • 若该图片只有少数地方使用且图片本身较小(这类图总体积小于20kb),则使用base64模式
  • 公共模块/组件里的图片(假设该模块名为mod-prd)
    • 模块内有N(N>=3)个图片,则全部放入**slice/mod/prd**里,使用雪碧图模式,否则参考全局通用图片处理方式
  • 页面自身独有的图片,全部合并成一张雪碧图

装逼结束,轻喷~


关注公众号 “沐洒”(ID:musama2018), 带你学点有用!

编辑于 2023-02-25 15:49・IP 属地广东