21

本文提供一个优化网页性能的大概思路,具体操作网上资料很多。

缓存优化

性能优化第一步,便是管理好页面的缓存,避免重复下载资源。否则,即增加服务器压力,又折磨用户的钱包。

浏览器缓存机制

  1. 访问页面,请求各种资源,浏览器检查本地是否有缓存。
  2. 如果有,检查资源是否过期。没过期,直接使用缓存。过期了,便向服务器发出请求。
  3. 发出的请求中会带上etag和last-modified首部字段。
  4. 服务器会通过Etag和last-modified来判断浏览器缓存的资源是否已经不可用。
  5. 如果资源仍然有效,便返回304告知浏览器使用缓存。否则返回更新后的资源。

按照这一套逻辑,便可规划好网站的缓存。

如果资源提前过期,如何通知浏览器更新资源?

通常无法做到这一点,因为浏览器发现资源没过期,根本不会发出请求。
但是可以通过修改资源的网址来实现。所以需要给资源文件名加上版本号或者随机标记。例如 style.1234.css。
也就是说,不要让浏览器缓存html文件,否则,过期之前,浏览器都不会请求服务器。

加载时优化

消灭不必要的下载

最好的优化,便是根本不下载资源。所以要尽量减少比不要的资源。

  1. 评估所有依赖是否必要,权衡利弊。
  2. 依赖的下载路径是否可靠,不可用时候是否会阻碍整个页面。
  3. 产品设计时候就需要抛弃浪费带宽的设计。

压缩所有可以压缩的资源

代码自不用说,都是文本,全部压缩。

优化图片

  1. 去掉不必要的图片
  2. 多使用css3来代替图片
  3. 使用压缩率更高的图片。特别是gif动图,一些视频格式(H.264或WebM)的体积比gif小很多。
  4. 用艺术字字体,不要用图片
  5. 仔细权衡图片和文字的关系。要表达一个意思,可能一图胜千言。多了一张图片,反而节省了大量文字。
  6. 使用progressive jpeg。相比随着数据下载从上到下显示的baseline jpeg,progressive jpeg是由模糊到清晰,用户体验好,也不会导致reflow。
  7. 图片分辨率要尽可能小,避免图片分辨率大于显示分辨率。
  8. 为使用更新浏览器的用户提供更现代的图片格式。
  9. 多种分辨率的位图供不同页面大小使用。
  10. 要给标签指明宽高,否则会导致reflow。
  11. 使用HTTP/2。比如,精灵图是由很多小图片组成的一张大图片,可以减少http请求。但是却难以缓存,修改一个小图片,导致所有小图片缓存失效。HTTP/2,一个链接内可以发起多个请求,便无需使用精灵图。

优化字体

  1. @font-face 中unicode-range可以制定字符范围,用来避免下载不需要的语言的字符。
  2. 确保字体都被压缩过。
  3. 用@font-face的display属性和FontFace对象管理好字体加载时的逻辑。

关键渲染路径

浏览器渲染一张网页通过以下步骤。

  1. 处理 HTML 标记并构建 DOM 树。
  2. 处理 CSS 标记并构建 CSSOM 树。
  3. 将 DOM 与 CSSOM 合并成一个渲染树。
  4. 根据渲染树来布局,以计算每个节点的几何信息。
  5. 将各个节点绘制到屏幕上。

说人话:浏览器先解析HTML,发现<link>就去请求CSS文件,发现<script>就去请求js文件。
当CSS文件完全取回,便会开始构建CSSOM树,当CSSOM树和DOM树都构建完成,才会开始渲染页面。
当JS文件完全取回,并不一定立刻执行,需要等待CSSOM树构建完成。同时,如果DOM树尚未构建完成,就会暂停构建,等待JS执行完成。因为浏览器不知道JS文件是否会修改CSS和DOM。
所以,在网页中随意摆放<link>和<script>,极有可能造成各种阻塞,必须精心安排。
优化关键渲染路径,便是指优化这个渲染过程,让网页尽快呈现出来。

css
  • CSS文件会阻塞渲染。浏览器构建好DOM树后,必须等待CSSOM树构建完成。
  • 在文档顶部放置外联CSS的标签,让浏览器尽快请求CSS文件。
  • 避免在css文件中使用@import,因为只有包含import的文件被下载编译后,浏览器才会发现并下载import的css。
  • 可以考虑使用内联CSS,无需额外请求,不会阻塞渲染。
js
  • 在CSSOM构建完成前,js不会开始执行。
  • js也会阻止DOM树构建。除非在<script>标签上标记async。
  • 用Chrome开发者工具的audits检查网页。

动画优化

重绘过程:
rendering

  1. js修改元素样式
  2. 浏览器重新计算CSSOM树
  3. 如果布局发生变化,则计算出新的布局
  4. 按不同的layer来计算出绘制元素所需的每个像素。
  5. 按顺序合并不同的layer

优化动画便是优化以上步骤使动画达到60帧。
CSS选择器要简单,避免浏览器遍历DOM树来寻找元素。
js修改的元素要避免牵连过多导致布局发生变化。
要减少需要重绘的元素。重绘时,同一个layer的元素都会被重绘。重绘也是非常耗费性能的一步。

CSS选择器

  • 选择器越复杂,浏览器计算得越久。最糟情况下,浏览器需要遍历整个DOM-tree,计算量等于元素总个数乘以选择器个数。
  • 尽量不要使选择器太复杂,事先给需要被操作的元素加上类名。

reflow, layout

即布局发生变化。Chrome, Opera, Safari, Internet Explorer中叫layout. 火狐称之为Reflow。
  • reflow, repaint次数越少越好,牵连的元素越少越好。
reflow总是牵涉整个文档流。
  • 千万不能修改元素css后立刻读取css计算值。因为浏览器必须重新计算布局,才能知道所需值。而js中相关API都是同步的,所以这将导致浏览器同步reflow,阻塞js线程。

Paint(重绘)

因为动画一直在运动,所以要避免使用会导致布局发生变化,和导致必须重新计算元素像素(重绘)的CSS属性制作动画。而transform和opacity两个CSS属性正是我们所需的。
因为浏览器渲染网页时,会将网页分层(layer),最后将不同层合并,然后完成渲染。
同一层中,哪怕只有一个小小的元素发生变化,整个层都会被repaint。

开发者工具的Paint Profiler界面中观察到每一层的绘制过程
所以,可以考虑将动画放在单独的layer中。

创建新layer

CSS的will-change和 transform: translateZ(0)可以用来创建新layer;
再搭配transform和opacity,用CSS3制作动画。根据我的实验,可以完全避免reflow和重绘

但是过多layer也消耗内存和性能,用开发者工具中的Performance判断新layer是否带来优化,否则不要创建新layer。
开发者工具的layer界面中可以观察网页有多少个layer,每个layer包含哪些元素。

优化交互

网页时给人用的,所以最恼人的是鼠标或手指操作页面时,页面卡卡的。
而响应用户操作的JS都是异步的。如果JS线程太繁忙,迟迟不能进入下一个事件循环,就会导致响应用户操作不及时。
尽量增加线程空闲时间,让事件循环可以及时取出回调函数。提高响应的优先级,用户交互时,停下其他耗时的JS。
响应不必过于及时,在100ms内得到反馈,人类都不会察觉到卡顿,可以把一些耗时的工作分解成很多步,利用这个时间差去执行它们。
活用requestAnimationFrame方法。

requestAnimationFrame在下一帧开始前调用函数。优于setInterval的地方是每一帧只会调用一次,而setInterval则不一定。

监听函数

交互事件的监听函数的执行时间不能太长,否则会阻塞页面滚动。

监听函数可能会调用preventDefault, 导致浏览器必须等待监听函数执行完成。
不过新扩展的addEventListener方法第三个参数可以是一个对象,对象的passive属性用来事先承诺不会调用preventDefault方法,浏览器则不会等待监听函数。

不要再交互事件的监听函数中修改样式,会导致强制同步reflow,阻塞js执行。

因为监听函数会在requestAnimationFrame之前执行,如果监听函数中修改了样式,又用到requestAnimationFrame来制作动画,便会导致强制同步reflow。

某些交互事件触发极频繁,注意要debounce。

debounce:不要高频率调用函数,事件连续触发时,只调用一次函数。

OLong
450 声望318 粉丝