CSS 渲染原理一渲染过程

如下代码

浏览器执行了handleResize、handleClick分别干了什么事情
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<style>
ele3 {
// will-change: transform; 和 translateZ(0) 效果雷同
translation: transform 0.2s;
}
<style>
window.addEventListener('resize', handleResize)
window.addEventListener('click', handleClick)
function handleResize() {
ele1.style.width = xxx;
ele1.style.height = yyy;

ele2.style.color = '#fff';
}

function handleClick() {
ele3.style.transform = 'translateX(1000px)';
}

浏览器渲染文档的步骤

https://developers.google.com/web/fundamentals/performance/critical-rendering-path/render-tree-construction?hl=zh-cn

  1. 处理HTML标记并构建DOM树
  2. 处理CSS标记并构建CSSOM树
  3. 合并DOM&CSSOM 为一个渲染树RenderTree, display: none 的元素并不会出现在RenderTree
  4. 根据RenderTree, 布局(计算节点在页面上的大小和位置)
  5. 将节点绘制到屏幕
  6. 渲染层合并(见下篇文章)

回到上述问题, resize时浏览器干了啥?

  1. 浏览器窗口resize时, 执行上述步骤 1 -> 4
  2. 确定哪些元素需要Reflow, 或者Repaint: ele1元素需要Reflow, ele2需要Repaint
  3. 浏览器执行Reflow -> Repaint, 这个过程浏览器会使用批处理优化

click的时候浏览器干了啥?

https://developers.google.com/web/fundamentals/design-and-ux/animations/animations-and-performance

  1. css translation动画, 会将transform || opacity 的元素提升一个复合层.
  2. will-change: transform || opacity; 会告诉浏览器在做更新前就使用最合适的优化, 进行高度优化. 也会将元素强行提升至一个复合层.
  3. 使用js做动画时, js必须在动画的每一帧计算元素的状态, 发送给GPU, 不会将元素提升至一个复合层, 想让元素提升至复合层, 必须使用translateZ或者will-change: transform, opacity;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 方式一: CSS动画: 设置以下CSS样式, hover时动画时, 查看Chrome Console 的Layers, 会有两个layer层;
<div class='bg-change'></div>
<style>
.bg-change {
width: 100px;
height: 100px;
background: red;
translation: transform 20s;
}
.bg-change:hover {
transform: translateX(1000px);
}
</style>

// 方式二 和 方式一样都会产生两个layer
<div class="bg-change-2"></div>
<style>
.bg-change-2 {
width: 100px;
height: 100px;
background: red;
/* transition: transform 20s; */
}
</style>
<script>
const dom = document.body.querySelector('.bg-change-2');
dom.onclick = function() {
this.style.transform = 'translateX(1000px)';
}
</script>

// 方式三: 通过js修改样式, requestAnimationFrame 过程中, 如果不加translateZ(0) 只会有一个Layer, translateZ(0) 才能强制的将元素提升一个复合层;
<div class="bg-change"></div>
<style>
.bg-change {
width: 100px;
height: 100px;
background: red;
}
</style>
<script>
var bg = document.querySelector('.bg-change');
var distance = 0;
requestAnimationFrame(function animate(){
bg.style.transform = 'translateX(' + ++distance + 'px) translateZ(0)';
requestAnimationFrame(animate);
})
</script>

先了解下浏览器的回流和重绘

  1. 浏览器使用流式布局模型(Flow Based Layout)
  2. 由于浏览器使用流式布局, 对Render Tree的计算通常遍历一次即可, 遇到table或者内部元素通常要花3倍等同元素, 因此避免使用table布局
  3. 回流一定会导致重绘, 重绘未必会引起回流

回流 Reflow

https://csstriggers.com/

Render Tree 中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程成为回流。
导致回流的操作:

  • 页面首次渲染
  • 浏览器窗口大小改变
  • 元素尺寸或位置发生改变
  • 元素内容变化(文字数量或者图片大小等等)
  • 元素字体大小变化
  • 添加或删除可见的DOM元素
  • 激活CSS伪类(:hover)
  • 查询某些属性或调用某些方法

导致回流的属性和方法

  • clientWidth、clientHeight、clientTop、clientLeft
  • offsetWidth、offsetHeight、offsetTop、offsetLeft
  • scrollWidth、scrollHeight、scrollTop、scrollLeft
  • scrollIntoView()、scrollIntoViewIfNeeded()
  • getComputedStyle()
  • getBoundingClientRect()
  • scrollTo()

重绘 Repaint

当页面中元素样式的改变并不影响它在文档流中的位置时 (例如: colorbackground-colorvisibility …), 浏览器会将新样式赋予给元素并重新绘制。

浏览器的优化会维护一个队列, 引起回流和重绘的操作放在队列中, 达到一定的阀值会清空队列, 进行一次批处理, 多次合成一次.

如果访问以下属性或方法, 浏览器会立刻清空队列, 确保你拿到的值是最精确的

  • clientWidth、clientHeight、clientTop、clientLeft
  • offsetWidth、offsetHeight、offsetTop、offsetLeft
  • scrollWidth、scrollHeight、scrollTop、scrollLeft
  • width、height
  • getComputedStyle()
  • getBoundingClientRect()

避免回流或重绘

CSS

  • 避免使用table布局
  • 尽量在DOM树的最末端改变class
  • 避免设置多层内联样式
  • 将动画应用在position属性位absolute或fixed的元素
  • 避免使用css表达式: calc()

JS

  • 避免频繁操作样式, 一次性重写style, 通过把样式写在class中, 一次性更改class属性
  • 避免频繁操作DOM, 创建一个documentFragment, 在其上应用所有DOM操作, 之后再把dF添加到文档中
  • 先将元素设置display: none, 操作结束后再显示出来: display: none的元素进行DOM操作不会引发回流和重绘
  • 避免频繁读取会引起回流/重绘的属性, 如需多次使用, 使用变量缓存起来
  • 复杂动画的元素使用绝对定位, 使其脱离文档流. 避免引起父元素和后续元素的频繁回流