在日常的开发中,通过使用css属性,做一些动效、动画时,会发现在页面有卡顿;
在Android低端机尤为明显,故需要知道浏览器渲染以及优化手段
浏览器渲染流程
- 构建 DOM 树:浏览器将 HTML 解析成树形结构的 DOM 树,一般来说,这个过程发生在页面初次加载,或页面 JavaScript 修改了节点结构的时候。
- 构建渲染树:浏览器将 CSS 解析成树形结构的 CSSOM 树,再和 DOM 树合并成渲染树。
- 布局(Layout):浏览器根据渲染树所体现的节点、各个节点的CSS定义以及它们的从属关系,计算出每个节点在屏幕中的位置。Web 页面中元素的布局是相对的,在页面元素位置、大小发生变化,往往会导致其他节点联动,需要重新计算布局,这时候的布局过程一般被称为回流/重排(Reflow)。
- 绘制(Paint):遍历渲染树,调用渲染器的 paint() 方法在屏幕上绘制出节点内容,本质上是一个像素填充的过程。这个过程也出现于回流或一些不影响布局的 CSS 修改引起的屏幕局部重画,这时候它被称为重绘(Repaint)。实际上,绘制过程是在多个层上完成的,这些层我们称为渲染层(RenderLayer)。
- 渲染层合成(Composite):多个绘制后的渲染层按照恰当的重叠顺序进行合并,而后生成位图,最终通过显卡展示到屏幕上。
以上是浏览器渲染的基本步骤,简述:
DOM tree + CSS tree == Render tree ==> Layout tree ==> PaintLayer => Composite
PaintLayer => Composite
只要页面上元素有改变,都会重复渲染以上部分步骤,在一帧内完成(16.77ms);
通过设置css 样式 ,做动画,在一帧内无法渲染完,甚至来不及渲染,就会卡顿;
其中部分渲染卡顿,跟渲染层合成(Composite)有关系。
Composite
什么是渲染层合成
在 DOM 树中每个节点都会对应一个渲染对象(RenderObject),当它们的渲染对象处于相同的坐标空间(z 轴空间)时,就会形成一个 RenderLayers,也就是渲染层。渲染层将保证页面元素以正确的顺序堆叠,这时候就会出现层合成(composite),从而正确处理透明元素和重叠元素的显示,这步叫做“层叠上下文”
这个模型类似于 Photoshop 的图层模型,在 Photoshop 中,每个设计元素都是一个独立的图层,多个图层以恰当的顺序在 z 轴空间上叠加,最终构成一个完整的设计图。
对于有位置重叠的元素的页面,这个过程尤其重要,因为一旦图层的合并顺序出错,将会导致元素显示异常。
渲染对象(RenderObject)
一个 DOM 节点对应了一个渲染对象,渲染对象依然维持着 DOM 树的树形结构。
渲染层(RenderLayer)
这是浏览器渲染期间构建的第一个层模型,处于相同坐标空间(z轴空间)的渲染对象,都将归并到同一个渲染层中,因此根据层叠上下文,不同坐标空间的的渲染对象将形成多个渲染层,以体现它们的层叠关系。
因此满足形成层叠上下文条件的 RenderObject 一定会为其创建新的渲染层,当然还有其他的一些特殊情况,为一些特殊的 RenderObject 创建一个新的渲染层,比如 overflow != visible 的元素。根据创建 RenderLayer 的原因不同,可以将其分为常见的 3 类:
- NormalPaintLayer
- 根元素(HTML)
- 有明确的定位属性(relative、fixed、sticky、absolute)
- 透明的(opacity 小于 1)
- 有 CSS 滤镜(fliter)
- 有 CSS mask 属性
- 有 CSS mix-blend-mode 属性(不为 normal)
- 有 CSS transform 属性(不为 none)
- backface-visibility 属性为 hidden
- 有 CSS reflection 属性
- 有 CSS column-count 属性(不为 auto)或者 有 CSS column-width 属性(不为 auto)
- 当前有对于 opacity、transform、fliter、backdrop-filter 应用动画
- OverflowClipPaintLayer
- overflow 不为 visible
- NoPaintLayer
- 不需要 paint 的 PaintLayer,比如一个没有视觉属性(背景、颜色、阴影等)的空 div。
满足以上条件的 RenderObject 会拥有独立的渲染层,而其他的 RenderObject 则和其第一个拥有渲染层的父元素共用一个。
图形层(GraphicsLayer)
渲染层,和其第一个拥有 GraphicsLayer 的父层共用一个。
每个 GraphicsLayer 都有一个 GraphicsContext,GraphicsContext 会负责输出该层的位图,位图是存储在共享内存中,作为纹理上传到 GPU 中,最后由 GPU 将多个位图进行合成,然后 draw 到屏幕上,此时,我们的页面也就展现到了屏幕上。
合成层(CompositingLayer)
满足某些特殊条件的渲染层,会被浏览器自动提升为合成层。合成层拥有单独的 GraphicsLayer,
个渲染层满足哪些特殊条件时,才能被提升为合成层呢?这里列举了一些常见的情况:
- 3D transforms:translate3d、translateZ 等
- video、canvas、iframe 等元素
- 通过 Element.animate() 实现的 opacity 动画转换
- 通过 СSS 动画实现的 opacity 动画转换
- position: fixed
- 具有 will-change 属性
- 对 opacity、transform、fliter、backdropfilter 应用了 animation 或者 transition
常见实用**transforms:translate3d、translateZ **
来形成合成层,这样是对原先的布局没有影响
示例
其他合成情况
隐式合成
除此之外,在浏览器的 Composite 阶段,还存在一种隐式合成,部分渲染层在一些特定场景下,会被默认提升为合成层。其实就是元素div之间相互重叠
上图所示:把position-1
生成合成层,后面的元素会相互重叠,为了层叠上下文展示正确;导致后面的元素直接隐式合成了;
合层压缩
如上图,目前没有遇到
合成爆炸
通过上图研究,很容易就产生一些不在预期范围内的合成层,当这些不符合预期的合成层达到一定量级时,就会变成层爆炸。
需要排查是否有重叠,能否通过z-index
来调节层叠顺序
优化
优点
- 合成层的位图,会交由 GPU 合成,比 CPU 处理要快得多;
- 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层;
- 元素提升为合成层后,transform 和 opacity 才不会触发 repaint,如果不是合成层,则其依然会触发 repaint。(只有css动画,才可以,js去控制还是会重排、重绘的)使用 transform 或者 opacity 来实现动画效果
更新元素几何属性(发生重排)
浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的
引发重排的情况
- 页面首次渲染;
- 浏览器窗口大小发生变化;
- 元素的内容发生变化;
- 元素的尺寸或者位置发生变化;
- 元素的字体大小发生变化;
- 激活CSS伪类;
查询某些属性或者调用某些方法;
- offsetTop、offsetLeft、offsetWidth、offsetHeight
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- clientTop、clientLeft、clientWidth、clientHeight
- width、height
- getComputedStyle()
- getBoundingClientRect()
- 添加或者删除可见的DOM元素。
更新元素绘制属性(发生重绘)
如果修改了元素的背景颜色,那么布局阶段将不会被执行,因为并没有引起几何位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率会比重排操作要高一些。
引发重绘的情况
- 重排
- visibility: visible <=> hidden
- 颜色改变
- 其他几何变化...
直接合成阶段
我们使用了CSS的transform来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。
缺点
- 绘制的图层必须传输到 GPU,这些层的数量和大小达到一定量级后,可能会导致传输非常慢,进而导致一些低端和中端设备上出现闪烁;
隐式合成容易产生过量的合成层,每个合成层都占用额外的内存,而内存是移动设备上的宝贵资源,过多使用内存可能会导致浏览器崩溃,让性能优化适得其s
其他优化
css
- 使用 transform 替代 top
- 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局
- 避免使用table布局,可能很小的一个小改动会造成整个 table 的重新布局。
- 尽可能在DOM树的最末端改变class,回流是不可避免的,但可以减少其影响。尽可能在DOM树的最末端改变class,可以限制了回流的范围,使其影响尽可能少的节点。
- 避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。
- 将动画效果应用到position属性为absolute或fixed的元素上,避免影响其他元素的布局,这样只是一个重绘,而不是回流,同时,控制动画速度可以选择 requestAnimationFrame,详见探讨 requestAnimationFrame。
- 避免使用CSS表达式,可能会引发回流。
将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点,例如will-change、video、iframe等标签,浏览器会自动将该节点变为图层。
js:
- 避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。
- 避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。
- 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
总结
在做动画时尽可能的使用使用csstransforms属性做动画,,它跳过Layout和Paint阶段,直接进入渲染,所以会提升部分性能,同时看看所属空间位置,是否提升了合成层;还有注意元素是否重叠,防止合成层爆炸;这样至少能保证css 方面,尽可能性能没有问题。
剩下的请查看其他优化手段
示例代码
重叠
<style>
.ball-running,.ball-running1 {
width: 100px;
height: 100px;
animation: run-around 4s linear alternate 100;
background: red;
position: absolute;
border-radius: 50%;
}
@keyframes run-around {
0% {
top: 0;
left: 0;
}
25% {
top: 0;
left: 200px;
}
50% {
top: 200px;
left: 200px;
}
75% {
top: 200px;
left: 0;
}
}
.position-1,.position-2,.position-3,.position-4 {
position: absolute;
width: 100px;
height: 100px;
background-color: red;
border: #1b1b1b 1px solid;
}
.position-1 {
transform: translateZ(10px);
}
.position-2 {
top: 90px;
left: 90px;
// transform: translateZ(10px);
}
.position-3 {
top: 180px;
left: 180px;
}
.position-4 {
top: 270px;
left: 270px;
}
.parent-1 {
//width: 100px;
//height: 200px;
background: #5b3a13;
//overflow: auto;
//transform: translateZ(10px);
}
.position-5 {
}
<style/>
<div class="parent-1">
<div class="position-1">
1
</div>
<div class="position-2">
2
</div>
<div class="position-3">
3
</div>
<div class="position-4">
4
</div>
</div>
动画
<style>
.ball-running,.ball-running1 {
width: 100px;
height: 100px;
// animation: run-around2 4s linear alternate 100;
background: red;
position: absolute;
border-radius: 50%;
}
@keyframes run-around {
0% {
top: 0;
left: 0;
}
25% {
top: 0;
left: 200px;
}
50% {
top: 200px;
left: 200px;
}
75% {
top: 200px;
left: 0;
}
}
//.ball-running {
// width: 100px;
// height: 100px;
// animation: run-around 2s linear alternate 100;
// background: red;
// border-radius: 50%;
//}
//@keyframes run-around {
// 0% {
// transform: translate(0, 0);
// }
// 25% {
// transform: translate(200px, 0);
// }
// 50% {
// transform: translate(200px, 200px);
// }
// 75% {
// transform: translate(0, 200px);
// }
//}
</style>
<div class="ball-running"></div>
参考:
transform和left改变位置的性能区别
GPU Accelerated Compositing in Chrome
浏览器层合成与页面渲染优化
浏览器渲染页面过程与页面优化
无线性能优化:Composite
浏览器渲染过程
渲染流程
Stick to Compositor-Only Properties and Manage Layer Count
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。