浏览器渲染机制
- 解析
HTML
,生成DOM
树,解析CSS
,生成CSSOM
树 - 将
DOM
树和CSSOM
树结合,生成渲染树Render Tree
Layout
(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)Paint
(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素Display
:将像素发送给GPU
,展示在页面上。
为了构建渲染树,浏览器主要完成了以下工作:
- 从
DOM
树的根节点开始遍历每个可见节点 - 对于每个可见节点,找到
CSSDOM
树中对应的规则,并应用它们 - 根据每个可见节点以及其对应的样式,组合生成渲染树
第一步中,既然要遍历可见节点,那么我们要先知道,什么节点是不可见的。不可见的节点包括:
- 一些不会渲染输出的节点,比如
script
、meta
、link
等 - 一些通过
css
进行隐藏的节点。比如display:none
。注意利用visibity
和opacity
隐藏的节点。
总结:回流会引起重绘,重绘不一定会引起回流
回流reflow
当我们render tree
中的一些元素的结构或尺寸发生改变,浏览器重新渲染部分或全部文档的过程就叫做回流。
会导致回流的操作:
- 页面首次渲染
- 浏览器窗口大小发生变化(
window.resize
发生变化) - 内容变化
- 添加或删除节点
- 激活
CSS
伪类 clientWidth
(clientWidth
=width
+左右padding
)、clientHeight
(与clientHeight
类似)、clientTop
(border.top
的宽度)、clientLeft
(border.left
宽度)、offsetWidth
(width
+左右padding
+左右border
)、offsetHeight
(与offsetWidth
类似)、offsetTop
(当前元素上边框外边缘到最近的已定位父级(offsetParent
)上边框内缘的距离,如果父级没有定位,则是到body
顶部的距离)、offsetLeft
(与offsetTop
类似)发生改变
重绘repaint
当页面中元素样式的改变不影响它在文档流中的位置,浏览器会将新样式赋值给元素,这个过程叫做重绘
引起重绘的操作:
- 设置background
- 设置visibility
- 设置color等
回流和重绘的性能影响
回流的性能消耗要比重绘大。
浏览器的优化机制
由于每次重排会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列中,知道过了一段时间或者操作达到了一个阈值,才会清空队列。当时,当获取布局信息时,会强制队列刷新,访问如下属性时会强制队列刷新:
offsetTop
、offsetLeft
、offsetWidth
、offsetHeight
scrollTop
、scrollLeft
、scrollWidth
、scrollHeight
clientTop
、clientLeft
、clientWidth
、clientHeight
getComputedStyle(element,pseudoElement)
(用于获取指定元素的CSS
样式,element
:必选,需要获取样式的元素;pseudoElement
:可选,伪类元素,当不查询伪类元素时可以忽略或传入null
)getBoundingClientRect()
(用于获取某个元素相对于视窗的位置集合。集合中有top
,right
,bottom
(top
+元素高度),left
等属性,可以用于实现吸顶操作)
应该避免使用上述属性或方法,如果要使用最好将值缓存起来
避免性能影响
CSS
:
- 避免使用
table
布局 - 避免设置多层内联样式
JS
:
1.合并多次DOM
操作
//使用cssText
const el = element.getElementById('test')
el.style.cssText += 'border-left: 1px; border-right: 2px;padding: 5px'
//修改CSS的class
const el = document.getElementById('test')
el.className += 'active'
2.批量修改DOM
,可以通过如下步骤减少回流重绘次数:
- 使元素脱离文档流
- 对其进行多次修改
- 将元素带回文档中
上述步骤中第一步和第三步可能会引起回流,但是进过第一步之后,对DOM
的所有修改都不会引起回流重绘。
有三种方式可以让DOM
脱离文档流:
- 隐藏元素,应用修改,重新显示
- 使用文档锁片(
documentFragment
)在当前DOM
之外构建一个子树,在把它拷贝回文档 - 将元素元素拷贝到一个脱离文档的节点中,修改节点后,再替换元素的元素
隐藏元素,应用修改,重新显示
在展示和隐藏节点时会产生两次回流:
function appendDataToElement(appendToElement,data) {
let li
for(let i = 0; i < data.length; i++) {
li = document.createElement('li')
li.textContent = 'text'
appendToElement.appendChild(li)
}
}
const ul = document.getElementById('list')
ul.style.display = 'none'
appendDataToElement(ul,data)
ul.style.display = 'block'
使用文档碎片(documentFragment
)在当前DOM之外构建一个子树,再把它拷贝回文档
const ul = document.getElementById('list')
const fragment = document.createDocumentFragment()
appendDataToElement(fragment,data)
ul.appendChild(fragment)
将原始元素拷贝到一个脱离文档的节点中,修改节点后,再替换原始元素
const ul = document.getElementById('list')
const clone = ul.cloneNode(true)
appendDataToElement(clone,data)
ul.parentNode.replaceChild(clone,ul)
上述三种方法优化效果一般,不用优先考虑
避免触发同步布局事件
例如:
function initP() {
for(let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = box.offsetWidth + 'px'
}
}
上述代码每次循环的时候都读取box
的offsetWidth
属性值,每次循环都会强制浏览器刷新队列。可以优化为:
const width = box.offsetWidth
function initP() {
for(let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = width + 'px'
}
}
对于复杂动画效果,使用绝对定位让其脱离文档流
对于复杂动画效果,由于经常引起回流重绘,我们可以使用绝对定位,让其脱离文档流。
CSS3
硬件加速(GPU
加速)
- 使用
CSS3
硬件加速,可以让transform、opacity、filters
这些动画不会引起回流重绘 - 对于动画的其他属性,比如
background-color
这些,还是会引起回流重绘,不过它还是可以提升这些动画的性能
常见触发硬件加速的css
属性:
transform
opacity
filter
Will-change
CSS3硬件加速的坑
:
- 如果太多元素使用
css3
硬件加速,会导致内存占用较大,会有性能问题 - 在
GPU
渲染字体弧导致抗锯齿无效,这是因为GPU
和CPU
的算法不同,如果不在动画结束时关闭硬件加速,会产生字体模糊
性:
transform
opacity
filter
Will-change
CSS3硬件加速的坑
:
- 如果太多元素使用
css3
硬件加速,会导致内存占用较大,会有性能问题 - 在
GPU
渲染字体弧导致抗锯齿无效,这是因为GPU
和CPU
的算法不同,如果不在动画结束时关闭硬件加速,会产生字体模糊