1 of 50

Make the Web Fast!

王钰@wy-ei

---- 浅谈 Web 性能优化背景知识与技巧

2 of 50

HTML

CSS

DOM

CSSOM

Render Tree

Layout

Paint

Network

JavaScript

主题脉络

  1. 关键路径渲染
  2. 提升交互性能
  3. 硬件加速 101

3 of 50

关键路径渲染

4 of 50

关键路径

HTML

CSS

DOM

CSSOM

Render Tree

Layout

Paint

Network

JavaScript

如果你不清楚什么是 CSSOM

  • CSSOM: CSS Object Model

p

span

font-weight: blod;

root

display: none;

5 of 50

从一个简单的例子开始

<!doctype html><meta charset=utf-8><title>Performance!</title><link href=styles.css rel=stylesheet />��<p>Hello <span>world!</span></p>

p { font-weight: bold; }

span { display: none; }

Index.html

styles.css

不能再简单了

6 of 50

HTML 正在传输......

<!doctype html><meta charset=utf-8><title>Performance!</title><link href=styles.css rel=stylesheet />��<p>Hello <span>world!</span></p>

p { font-weight: bold; }

span { display: none; }

index.html

styles.css

HTML

CSS

DOM

CSSOM

Render Tree

Network

  • 部分字节已经到达,开始解析 HTML
  • 开始构建 DOM 树

7 of 50

DOM 树构建过程

图片来源于:developer.google.com

8 of 50

继续解析 HTML …...

<!doctype html><meta charset=utf-8><title>Performance!</title><link href=styles.css rel=stylesheet />��<p>Hello <span>world!</span></p>

p { font-weight: bold; }

span { display: none; }

index.html

styles.css

HTML

CSS

DOM

CSSOM

Render Tree

Network

  • 发现 <link> 标签, 发起网络请求
  • DOM 树构建完成
  • 屏幕依然是空白的,等待 CSS 加载

9 of 50

CSS 部分到达,等待 CSS 加载完成......

<!doctype html><meta charset=utf-8><title>Performance!</title><link href=styles.css rel=stylesheet />��<p>Hello <span>world!</span></p>

p { font-weight: bold; }

span { display: none; }

index.html

styles.css

HTML

CSS

DOM

CSSOM

Render Tree

Network

CSS 文档中后面的规则可能覆盖掉之前的,因此需要等待 CSS 全部下载,才会开始构建 CSSOM 树。

  • CSS 部分字节已经到达
  • 尚不能开始解析,需等待全部加载完成

10 of 50

CSS 全部加载完成,可以开始构建 CSSOM 树

<!doctype html><meta charset=utf-8><title>Performance!</title><link href=styles.css rel=stylesheet />��<p>Hello <span>world!</span></p>

p { font-weight: bold; }

span { display: none; }

index.html

styles.css

HTML

CSS

DOM

CSSOM

Render Tree

Network

  • CSS 下载完成
  • 完成构建 CSSOM 树

依然空白,不要着急

11 of 50

Hello

P

world!

span

p

span

font-weight: blod;

root

display: none;

DOM 树

CSSOM 树

DOM + CSSOM = Render Tree (渲染树)

body

  • 将样式匹配至 DOM 节点

12 of 50

Hello

p

font-weight: blod;

渲染树

+

DOM + CSSOM = Render Tree (渲染树)

Hello

p

world!

span

p

span

font-weight: blod;

root

display: none;

CSSOM 树

DOM 树

body

  • <span> 不在渲染树上
    • “display:none;”

body

13 of 50

HTML

CSS

DOM

CSSOM

Render Tree

Layout

Paint

Network

JavaScript

We are here!

14 of 50

<div style="width: 80%">

<h1>Performance</h1>

<p>Hello <span>world!</span></p>

</div>

layout(布局)

Performance

Hello

world!

  • 计算各节点的宽度、高度、位置等信息
    • margin, padding, top, left 等
    • 相对大小(em,50%)

  • 改变了窗口大小会怎样?

15 of 50

  1. 基于图层,填充像素点,得到多个表示图层视觉表现的位图
    1. 调用绘图 API,lineTo, drawPoint, drawLine, etc.
    2. 不同属性的绘制,需要付出的代价不同 color, box-shadow …
  2. 将多个图层推送至 GPU 进行图层合并
    • GPU 能够快速完成图层合并操作

Paint(绘制)

Performance

Hello

world!

Performance

Hello world!

layout:

pixels:

16 of 50

Paint(绘制)

Performance

Hello world!

viewport:

Performance

Hello world!

  • 视口被分为多个小块
    • 每个小块都被渲染并缓存下来
  • 每个元素都可以有自己的图层
    • 图层可以重复利用
    • 图层可以在 GPU 内完成合并

Tools:Chrome Tools > Setting > More tools > Rendering > Layer Borders

17 of 50

GPU 完成图层的合并

GPU

图层合并

渲染树

Hello

多个图层

  1. 渲染树被分为多个图层
  2. 各个图层被绘制到缓冲区并上传至 GPU
  3. GPU 完成图层合并操作

18 of 50

终于!

Hello

HTML

CSS

DOM

CSSOM

Render Tree

Layout

Paint

Network

JavaScript

实际过程远比这复杂,但大体上看,基本就是这样。

19 of 50

等等,好像遗漏了什么?

JavaScript 呢?

20 of 50

引入 JavaScript

<!doctype html><meta charset=utf-8><title>Performance!</title><link href=styles.css rel=stylesheet /><script src=app.js></script>

<p>Hello <span>world!</span></p>

p { font-weight: bold; }

span { display: none; }

index.html

styles.css

Network

  • JavaScript 会读取 DOM 和 CSSOM
  • JavaScript 会修改 DOM 和 CSSOM

HTML

DOM

CSS

CSSOM

JavaScript

elem.style.width = "300px"

21 of 50

JavaScript 会动态改变 CSSOM 树

var width = elem.style.width;�elem.style.width = "300px";

document.write("I am cool !");

app.js

  • JavaScript 在执行阶段可能会读取 CSSOM
  • JavaScript 可以修改 CSSOM
  • CSS 未下载完成,页面不会开始渲染
    • 例外:如果 CSS 出现在 <script> 之后,<script> 之前的页面会渲染出来
  • CSS 未下载完成,即 CSSOM 未构建完成,JavaScript 不能执行
    • 为了保证在 JavaScript 执行的时候能获取到准确的样式信息。
    • 这里的 CSS 是指已经解析出来的,不包含该 script 后面的 CSS。
  • 这就是为什么要将 CSS放在 <head> 内的原因

22 of 50

JavaScript 会动态改变 DOM 树

var width = elem.style.width;�elem.style.width = "300px";

document.write("I am cool !");

app.js

  • JavaScript 可以读取 DOM
  • JavaScript 可以修改 DOM
  • JavaScript 未下载完成,HTML 不能继续进行解析
  • JavaScript 未执行完毕,DOM 树不能继续进行构建

Why比如,document.write 能够向文档中插入内容。

  • 这就是为何要把 JS 放在 </body> 之前的原因

23 of 50

  • 尽可能快地加载加载资源
    • 启用压缩,使用 CDN,内联部分必须前置的脚本,内联关键 CSS
  • 尽可能早地加载 CSS
    • 防止 CSS 阻塞渲染 JS 执行,放在 HEAD 中,只加载首屏需要的 CSS
  • 移除阻塞渲染的 JavaScript 脚本
    • 内联必要脚本,延迟加载其他脚本 <script async></script>
    • JS 放在页面底部。

HTML

CSS

DOM

CSSOM

Render Tree

Layout

Paint

Network

JavaScript

一些准则(Rule)

24 of 50

工具

补充阅读

  • 关键渲染路径

25 of 50

提升交互性能

26 of 50

性能 = 60 FPS.

1000 ms / 60 FPS = 16.6 ms / frame

FPS(frames per second),每秒刷新屏幕的次数。

27 of 50

首屏渲染性能

交互性能

HTML

CSS

DOM

CSSOM

Render Tree

Layout

Paint

Network

JavaScript

若 DOM 树或 CSSOM 树被改变,浏览器需要进行后续多个步骤

28 of 50

<10ms

frame

...

Javascript

更新渲染树

重排

重绘

合并图层

1000 / 60 = 16.6 ms

  • 为了性能要保证每一帧用时小于 16.6 ms
    • JS 执行的时间 < 10ms,在移动端更短
      • 浏览器还要 layout, paint, etc.

一帧又一帧

frame

frame

frame

frame

frame

frame

并非每一帧都要经过以上步骤,某些步骤可以跳过。

29 of 50

frame

超出 16ms 呢?

  • 帧率达不到 60FPS,若低于 60FPS 较多,用户会明显感到卡顿。

Javascript

frame

...

frame

frame

frame

frame

frame

重排

重绘

合并图层

26.5 ms

  • 你的代码必须在 16 ms 内交出控制权
    • 多个帧内完成
    • 使用 Web Worker

哥,等等我

更新渲染树

30 of 50

重排:重新计算 width/height 等涉及尺寸的属性

  • DOM 树上可见节点被修改
  • DOM 节点的尺寸/定位属性被改变
    • 动画,JavaScript 都会导致以上属性改变
  • 浏览器窗口大小改变,旋转屏幕等

何时发生重排:

31 of 50

frame

避免强制性同步布局

  • DOM 树或 CSSOM 树被修改后,得到一个脏渲染树
  • 重排操作是惰性执行的,如无必要不会进行重排
  • 理想情况下,只需要在重绘前进行一次重排

强制性同步布局

frame

...

frame

frame

frame

frame

frame

重排

重绘

合并图层

...

for (n in nodes) {

let offsetLeft = n.offsetLeft;

n.style.left = offsetLeft + 1 + "px";

}

  • 前一次迭代得到一个脏的渲染树
  • 后一次读取 DOM,引发强制布局

惰性重排

32 of 50

强制性同步布局会造成极大的性能问题

一帧内,多次重新计算样式(Recalculate Style)和 布局(Layout)

33 of 50

重绘:重新绘制图层

  • 重排后一定会重绘。
  • 表示视觉的属性(color,background 等)被修改后,会进行重绘。
  • 所在图层中其他元素的视觉属性被修改后,整个层会被重绘。

注意:重绘比较昂贵,尤其是手持设备。另手持设备的计算能力,可能不及 MBP 的 1/20,在调试时,可以模拟降低 CPU 性能的情况。

何时发生重绘:

  • Tools:Chrome Tools > Setting > More tools > Rendering > Paint Flashing

34 of 50

并非每一帧都需要进行以下全部操作

  • 跳过重排
    • 避免修改 DOM 树
    • 避免操作会引起重排的属性,width,height 等
  • 跳过重绘
    • 避免引起重排
    • 避免修改会引起重绘的属性,color,background 等

Javascript

更新渲染树

重排

重绘

合并图层

可跳过

某些属性的改变,GPU 可以经过变换就能得到新的图层,无需重排、重绘。

  • 哪些属性?transform, opacity, filter ...

35 of 50

硬件加速 101

36 of 50

视频来源:http://v.youku.com/v_show/id_XNjY3MTY4NjAw.html

CPU 和 GPU 绘图的对比

CPU

GPU

  • GPU 的并行操作,更擅长绘图。

37 of 50

GPU 完成图层的变换

GPU

skew(30deg)

  1. 原图层已经存在于 GPU 内
  2. CPU 向 GPU 发送指令(move、rotate 等)
  3. GPU 变换图层,得到新的图层
  4. 一帧图像 的参数在 GPU 内完成,不需要重绘重排操作

transform:skew(30deg)

38 of 50

关于图层

  • 某些元素被自动提示值单独的图层
    • Canvas,video,css3 transform animation ...
  • 可以强制提升元素至单独图层,transform: translateZ(0)
    • 每个单独的图层都需要消耗额外的内存,图层太多会导致内存占用过大
    • 图层太多也会导致合并图层的耗时过长
    • 图层太多,导致 CPU 与 GPU 间总线带宽不够用

39 of 50

避免误用 transform: translateZ(0);

40 of 50

fixed 定位元素在滚动时引起整个页面重绘

滚动后定位元素相对于整个文档的位置变了,为获得新的页面,需要重绘整个页面。

41 of 50

解决方案:

使用 transform: translateZ(0); 将 fixed 定位元素提升至单独图层。

注:Chrome 在 dpi 较高的屏幕上会自动将 fixed 定位的元素提升至单独的图层,在 dpi 较低的屏幕上不会提升,另前端工程师常常使用 MacBook Pro。

42 of 50

免费的晚餐

43 of 50

使用 transform 完成动画

  • 在动画过程中没有重排和重绘操作

44 of 50

使用 opacity 来完成动画

  • 穷尽一些办法,使用不会触发重排和重绘的属性来完成动画。

45 of 50

是时候总结一下了

46 of 50

优化关键路径

  • 尽可能快地加载资源
    • 启用压缩,预加载,将关键资源放在同一个 CND/Server 上等
  • 尽可能早地加载 CSS
    • 防止 CSS 阻塞渲染,JavaScript 的执行
  • 移除阻塞渲染的 JavaScript 脚本
    • 内联必要脚本
    • 延迟加载其他脚本 <script async></script>

47 of 50

提升交互性能

  • 高性能 == 60 FPS
    • 保证每帧用时 < 16.6 ms
    • 16.6 ms 包含:JS 运行时间,Layout,Paint,GC,Composite 用时
  • 分析与优化
    • 减少/避免 Layout 和 Paint
    • 保证 Layout 和 Paint 影响范围足够小
    • 使用工具检查是否存在不符合预期的重绘
  • 移动端体验
    • 移动设备性能远远低于 PC / Mac

48 of 50

合理地利用硬件加速

  • 硬件加速的本质
    • 利用 GPU 完成图层的变换
  • 减小 CPU 和 GPU 之间的传输量
    • 避免误用 translateZ(0)
  • 使用不会触发 Layout 和 Paint 的属性完成动画
    • transform,opacity,etc.
    • CSS3 animation 是免费的晚餐(高性能)
    • 避免混用 transform 等与 width 等属性来完成动画(失去晚餐)

49 of 50

唯一永远不改变,是不停的改变

规则会变,不可死守,耳听为虚,眼见为实

50 of 50

Question?