场景2,内嵌至js文件中

前端图片引入方式神演算

2017/01/11 · 基础技术 ·
图片

原文出处: 沐洒(@Musa沐洒)   

菲律宾太阳 1

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

先阐述一下背景:

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

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

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

菲律宾太阳 2

这就是我们今天的议题。

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

菲律宾太阳 3

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

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%

菲律宾太阳 4

但是!

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

菲律宾太阳 5

当然样本少是一个问题,但大概的我们还是能看出来一些端倪: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的资源。。。。等等!总感觉哪里不对!

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

菲律宾太阳 6

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

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

菲律宾太阳 7

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

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

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

菲律宾太阳 8

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

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

菲律宾太阳 9

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

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

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

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

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

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

  • 全局通用的,非特定页面或模块独有的图片,采用单图或base64方式引入,二者区别如下:
    • 若该图片在多处使用或图片本身较大(这类图总体积大于20kb),则使用单图模式
    • 若该图片只有少数地方使用且图片本身较小(这类图总体积小于20kb),则使用base64模式

  • 公共模块/组件里的图片(假设该模块名为mod-prd)
    • 模块内有N(N>=3)个图片,则全部放入**slice/mod/prd**里,使用雪碧图模式,否则参考全局通用图片处理方式

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

装逼结束,轻喷~

2 赞 3 收藏
评论

菲律宾太阳 10

菲律宾太阳 11

使用建议

  1. 图片资源的base64编码进css文件会带来一定的性能消耗,需谨慎使用。
  2. 将图片资源编码进js文件中,管理和预加载H5应用的图片资源,合理的合并请求可以大大提高页面体验。

    1 赞 1 收藏
    评论

菲律宾太阳 10

渲染

1 –
减少DOM树的深度,DOM树越深,越吃浏览器的渲染资源。尽量减少不必要的DOM层级。

2 –
优化体积过大的css文件,css体积过大需要更多的时间加载,关键的资源需要进行拆分渲染,保证首屏加载速度,提高用户体验。

3 –
避免在HTML中直接缩放图片,在HTML中直接缩放图片会使页面内容重绘,会影响页面中其他操作的效率,可能会产生卡顿。

4 –
优化js加载,耗时的script应当在html底部渲染,或者使用异步加载,与首屏无关的js也应当延迟加载。

5 – 避免使用<table>与<iframe>:

table是将整个table元素渲染完毕再一次性绘制到页面上,特别吃性能,相比而言可以用其他列表元素代替,譬如ul、dl。

iframe会因为加载源页面(即src=’xxx’),而造成父级进入渲染阻塞的状态,等待iframe的源页面加完完毕才会回到父级继续往下渲染,如果是必要时,可以用异步的方式动态加载iframe。

6.
避免使用css滤镜,与css表达式,太吃性能的玩意儿暂时不要玩儿吧,浏览器hold不住。

4、调整引用次数

50kb大小的图片,base64化后,调整引用图片做背景图的dom的个数

引用次数 10 20 50 100 135
Rendering时间 15ms 19ms 44ms 74ms 83ms

菲律宾太阳 13

避免阻塞

如何统计?

通过Navigation
Timing记录的关键时间点来统计页面完成所用的时间,并通过Chrome开发工具来跟踪细节

JavaScript

var timing = window.performance.timing timing.domLoading
//浏览器开始解析 HTML 文档第一批收到的字节 timing.domInteractive //
浏览器完成解析并且所有 HTML 和 DOM
构建完毕timing.domContentLoadedEventStart //DOM
解析完成后,网页内资源加载开始的时间 timing.domContentLoadedEventEnd
//DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕)
timing.domComplete //网页上所有资源(图片等) 下载完成,且准备就绪的时间

1
2
3
4
5
var timing = window.performance.timing
timing.domLoading //浏览器开始解析 HTML 文档第一批收到的字节
timing.domInteractive // 浏览器完成解析并且所有 HTML 和 DOM 构建完毕timing.domContentLoadedEventStart //DOM 解析完成后,网页内资源加载开始的时间
timing.domContentLoadedEventEnd //DOM 解析完成后,网页内资源加载完成的时间(如 JS 脚本加载执行完毕)
timing.domComplete //网页上所有资源(图片等) 下载完成,且准备就绪的时间

以上定义来自chrome官方文档,在其它环境下也许会有差异,从测试结果看,下面的build时间在android+微信环境中一直是0,对此可能是因为渲染机制差别,此处不做深入测试。除osx+chrome之外环境的数据仅作参考。

JavaScript

build = timing.domComplete – timing.domContentLoadedEventStart
//间隔记录网页内资源加载和呈现时间。 complete = timing.domComplete –
timing.domLoading //页面接收到数据开始到呈现完毕的总时间。

1
2
build = timing.domComplete – timing.domContentLoadedEventStart //间隔记录网页内资源加载和呈现时间。
complete = timing.domComplete – timing.domLoading //页面接收到数据开始到呈现完毕的总时间。

1. 优化HTTP请求

1 – 减少请求次数:

  1. 合并代码。

  2. sprite化图片。

  3. 小于8kb的用base64作为src源。

4.
缓存ajax,对于每次请求返回内容都相同的ajax,可以设置cache属性进行缓存。

  1. 剔除重复性的脚本。

2 – 减少请求体积:

  1. 让后台dalao们使用GZIP。

  2. 压缩代码,减少文件的空白字符。

  3. 优化图片,压缩IMG-PNG8格式,压缩图片。

3 –
减少请求资源自带cookie的体积,也叫cookie隔离,使用CDN就好,有兴趣可以看看Cookie

  • Free Domains技术。

4 –
减少页面中空引用的href与src标签,因为你什么都不写,浏览器还是会对服务器发起一个空的HTTP请求,会对服务器产生一些不必要的压力。

5 –
突破请求限制,同一时刻向一个域名请求下载的并行数有限,可以使用不同域名分别存放静态资源,增大并行下载量。

6 – 使用大杀器,CDN。

7 –
不要滥用post请求,post翻译过来是邮递的意思,获取数据还是用get请求。而且在大部分浏览器中post还是有分步操作,而get是一步到位,虽然效率上差别不会很明显,但这不是混淆语义的理由。

顺便附上一个get与post区别的link: get与post的区别。

8 –
css置顶,script置底,如需在头部放置script,可以使用defer与async异步的script引入。

9 – 避免使用css表达式。

10 – 使用懒加载。

11 – 减少DNS查找。每次DNS查询都会有30-120ms的耗时,可以使用DNS预加载。

12 –
减少页面重定向。每次重定向都会有资源的损耗,尽量减少不必要的重定向。

分析和小结:

在OSX+Chrome环境下,将50kb的图片base64后放入样式中,build过程拉长了约20倍,使用Timeline工具可以看到,计算样式阻塞了整个过程。

菲律宾太阳 14

  1. 比起直接引入图片地址,css文件中引入base64格式的图片对样式渲染的性能消耗明显,如果大量使用,会带来耗电和发热的问题,需谨慎使用。
  2. Rendering消耗的时间同css文件大小、引用次数几乎成正比(未测试其它极限情况),在网络条件优质的4G环境,50~70ms的RTT(往返时延)情况下,通常移动网络的状况会更差,对于首屏优化,合适的使用还是很值得的。
  3. 图片转成base64编码后,文档大小较原文件大了一些,而经过 gzip
    后两者几乎没有区别。

预加载

1 – 使用DNS预解析,像天猫阿里的元信息里面都有用到:

菲律宾太阳 15

使用dns-prefetch的姿势。

发表评论

电子邮件地址不会被公开。 必填项已用*标注