持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情
了解浏览器的渲染机制能让我们更好的做一些性能优化,避免某些操作因为触发没考虑浏览器的渲染规则无效的多次渲染,造成性能的浪费。
浏览器渲染机制
我们知道浏览器采用流式布局模型,浏览器会把html解析成dom,把css解析成cssom,dom和cssom合并就产生了渲染树。有了渲染树我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制在页面上。由于浏览器使用的是流式布局,对渲染树的计算通常只需要一次遍历就可以完成,但是table及其内部元素被排除在外,它们需要进行多次计算,通常要花费3倍同等元素的时间,这也是为什么要尽可能的避免使用table布局的原因之一。为了构建渲染树,浏览器要完成以下任务:对于每个可见的节点,找到cssom树中对应的规则,并使用他们。根据每个可见节点以及其对应的样式,组合生成渲染树,在第一步中,所说的可见节点不包括:script、meta、link等,还有通过css设置的如display:none。注意到由visibility和opacity隐藏的节点,还是会在渲染树上出现。这里我们可以回忆一下vue中v-show和v-if的区别。
回流:
节点的几何属性(大小形状之类的吧)或者布局发生改变被称为回流,一个元素的回流可能会导致了其所有子元素以及DOM中紧随其后的节点、祖先节点元素的随后的回流,所以一个节点的回流会引起页面某个部分甚至整个页面的回流。
重绘:
节点的样式改变且不影响布局的,比如color和visibility等等就是发生重绘。
注意点:重绘不一定会回流,回流一定会重绘。
优化
要知道回流和重绘都是需要消耗一定的浏览器性能的,现在浏览器大多是通过队列机制来批量更新布局的,浏览器会把修改操作放在队列中,至少一个流览器刷新才会清空队列,但是当你获取布局信息的时候,队列中可能有会影响这些属性或方返回值的操作,即使没有,浏览器也是会强制清空队列的,触发回流和重绘,来保证返回值的正确。主要包括:offestTop,clientWidth等等,所以我们要尽可能的避免使用上述属性,它们都会强制刷新渲染队列,所以如果想优化项目的性能我们就要尽可能的避免不必要的回流和重绘。
减少回流和重绘
- 批量的修改dom或者样式:
最核心的思想就是对于会引起回流和重绘的操作不要一次一次的修改,而是应该用一个容器装起来,一次性修改。
const el = document.getElementById('test');
el.style.padding = '5px';
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
//可以优化为使用cssText或者class统一添加
const el = document.getElementById('test');
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';
//或者
const el = document.getElementById('test');
el.className += ' active';
再比如增加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');
appendDataToElement(ul, data);
优化后
const ul = document.getElementById('list');
const fragment = document.createDocumentFragment();
appendDataToElement(fragment, data);
ul.appendChild(fragment);
还有前面说到渲染树回流和重绘都是针对可见元素,所以借助display:none隐藏元素,进行一波增啥改查操作之后再把它显示出来,只会进行一次回流:实例如下:
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';
- 避免触发同步的UI渲染:
就是对前面提到的会触发浏览器强制回流的方法我们不要把它写在循环中,而是转化成为全局对象进行处理。
- 对于复杂的动画效果,使用绝对定位让其脱离文档流:
对于复杂的动画效果,由于会经常的引起重绘回流,因此,我们可以使用绝对定位来使得它脱离文档流,否则就会引起父元素以及后续元素的频繁回流。
- css3硬件加速GPU加速: