回流(Reflow)/重排
页面第一次渲染完后,我们后期基于某些操作,修改了某个节点在视口中(自己所在层级中)的位置或者大小,
这样浏览器需要重新计算当前视口(当前层级中)所有节点的布局位置「也就是把Layout操作重新搞了一遍」,
然后重新进行Painting绘制,我们把这个操作称之为"回流(Reflow)或者叫做重排"!回流非常消耗页面性能,
我们常说的操作DOM消耗性能,指的就是这件事!
哪些操作会引发回流?
- 改变节点的位置
- 改变节点的大小
- 新增或者移除节点
- 视口大小改变
- 内容改变引发了节点的大小改变
而且回流一定会引发重绘,而且页面第一次渲染,本身就会有一次Layout和Painting
某些对于DOM操作(例如:改变文字颜色、背景、visibility「修改display可能会引发回流,但是设置visibility原始位置不动,则不会引发回流」...),并不会对节点的位置产生影响,此时只需要"重绘(Repaint)"即可!
在页面运行时的性能优化
前端性能优化的核心操作:减少操作DOM产生的回流(或重排)
@1 读写分离:把设置元素样式代码和获取元素样式代码分离编写,不要穿插混合一起
渲染队列机制:当代浏览器的机制,当前上下文代码执行过程中,遇到修改元素样式(操作DOM)的代码,浏览器并没有立即去处理,而是先存放在渲染队列中;当遇到获取元素样式操作(或者当前上下文执行完毕),才会刷新渲染对列(也就是把对列中对DOM的操作统一进行处理),引发一次回流!
box.style.width='200px';
box.style.height='300px';
console.log(box.offsetWidth);
console.log(box.offsetHeight);
let w=box.offsetWidth;
let h=box.offsetHeight;
box.style.width=w + 10 + 'px';
box.style.height=h + 10 + 'px';
@2 统一修改样式
- 把需要修改的样式先写在样式表中,基于修改元素的样式类名达到修改样式的需求,也只会引发一次回流
box.classList.add('active');
- 基于cssText处理
box.style.cssText='width:200px;height:300px';
@3 新增DOM元素采取"批量统一增加"的方案
- 文档碎片:一个临时存储DOM节点的容器
-
let frag=document.createDocumentFragment(); //创建容器 //fill 变成密集数组 new Array(10).fill(null).forEach(()=>{ let box=document.createElement('div'); box.innerHTML='...'; frag.appendChild(box); //把每一次创建的div先存放在文档碎片中 }); document.body.appendChild(frag); // 最后把容器插入到body末尾
-
模板字符串拼接
@4
修改元素的样式尽可能使用 transform 变形属性 / opacity,因为它的改变不会引发DOM回流「因为浏览器内部对其做了硬件加速,也可以理解为这就是"规定"」
@5
修改样式的元素,尽可能在单独的文档流中,虽然这样不能减少DOM的回流次数,但是可以让本次回流处理的更快,因为只需要把当前的文档流中的节点重新布局即可!渲染绘制的时候也只对当前文档流处理即可!
@6
JS处理动画的规则:
能用CSS3动画解决的,坚决不用JS动画,如果JS也解决不了,那就换需求! 适当情况下,需要牺牲平滑度来换取速度
不要直接自己操作DOM,使用Vue/React框架,我们只操作数据,操作DOM的事情交给框架去做即可!