浏览器渲染过程
- 解析HTML,生成DOM树,解析CSS,生成CSSOM树
- 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
- Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
- Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
- Display:将像素发送给GPU,展示在页面上。(比如会在GPU将多个合成层合并为同一个层,并展示在页面中。而css3硬件加速的原理则是新建合成层)
生成渲染树过程
- 从DOM树的根节点开始遍历每个可见节点。
- 对于每个可见的节点,找到CSSOM树中对应的规则,并应用它们。
- 根据每个可见节点以及其对应的样式,组合生成渲染树
不可见的样式是不会进入render tree的
- 一些不会渲染输出的节点,比如script、meta、link等
- 一些通过css进行隐藏的节点。比如display:none。注意,利用visibility和opacity隐藏的节点,还是会显示在渲染树上的。
重绘 当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,将渲染树的每个节点都转换为屏幕上的实际像素,这个过程称为重绘。
回流 (Reflow) 当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器根据视口重新计算位置和大小。
会导致回流的操作:
- 页面首次渲染
- 浏览器窗口大小发生改变
- 元素尺寸或位置发生改变
- 元素内容变化(文字数量或图片大小等等)
- 元素字体大小变化
- 添加或者删除可见的DOM元素
- 激活CSS伪类(例如::hover)
- 查询某些属性或调用某些方法
可能会导致回流的属性或方法:
- clientWidth、clientHeight、clientTop、clientLeft
- offsetWidth、offsetHeight、offsetTop、offsetLeft
- scrollWidth、scrollHeight、scrollTop、scrollLeft
- scrollIntoView()、scrollIntoViewIfNeeded()
- getComputedStyle()
- getBoundingClientRect()
- scrollTo()
现代浏览器对频繁回流或重绘的优化 览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次
当你访问以下属性或方法时,浏览器会立刻清空队列:
- clientWidth、clientHeight、clientTop、clientLeft
- offsetWidth、offsetHeight、offsetTop、offsetLeft
- scrollWidth、scrollHeight、scrollTop、scrollLeft
- width、height
- getComputedStyle()
- getBoundingClientRect()
因为需要确保拿到的值是最精确的
减少重绘和回流的操作
对于css
- 减少table使用:table及其内部元素除外,可能需要多次计算,通常要花3倍于同等元素的时间。
- 尽可能在DOM树的最末端改变class,减小回流的范围。
- 避免设置多层内联样式。
- 将动画效果应用到position属性为absolute或fixed的元素上,使它脱离文档流,相当于新建了一个文档对象,触发的回流是新建文档,否则会引起父元素及后续元素频繁回流
- 避免使用CSS表达式(例如:calc())。
- 开启css3到硬件加速
- 可以让transform、opacity、filters这些动画不会引起回流重绘
- 比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能
- 能出发硬件加速的属性:transform、opacity、filters、Will-change等
对于js
- 创建一个容器,将所有添加内容放到容器内,再添加到页面,只引起一次回流和重构
var fragment=document.createDocumentFragment()
fragment.appendChild(添加对象);
document.body.appendChild(fragment);
el.style.position='absoulte';
...
el.style.position='static';
- 可以先为元素设置display: none,因此元素不会在render tree里面,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘
ul.style.display = 'none';
...
ul.style.display = 'block';
- 将多个el.style.样式名的操作,转变成el.style.cssText=’…;'一次性操作
const el = document.getElementById('test');
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';
const ul = document.getElementById('list');
const clone = ul.cloneNode(true);
...
ul.parentNode.replaceChild(clone, ul);
function initP() {
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = box.offsetWidth + 'px';
}
}
修改为
const width = box.offsetWidth;
function initP() {
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = width + 'px';
}
}
|