J49 浏览器渲染的流程:DOM回流和重绘

271 阅读4分钟

1.浏览器渲染流程

浏览器渲染页面的步骤:

  • 1.生成DOM树(渲染了所有的HTML标签)

  • 转换

  • 令牌

  • 词法分析

  • DOM构建

  • 2.生成CSSOM树(请求回来CSS后,渲染完CSS)

  • 3.DOM树+CSSOM树=>Render-tree(渲染树)

  • 4.按照render-tree在设备的视口中进行结构和位置的相关计算:布局(Layout)或重排/回流(reflow)

  • 5.根据渲染树以及回流得到的几何信息,得到节点的绝对像素:绘制(painting)或栅格化(rasterizing)

    2.进程process和线程thread

    • 1.电脑端安装很多的应用软件,每当运行一个应用程序,都相当于开辟了一个进程(而对于浏览器来说,每新建一个页卡访问一个页面,都是新开辟一个进程)
    • 2.每一个进程中可能还会同时做多件事情,如果同时做多件事情,则会开辟多个线程
    • 3.一个进程中,包含零到多个线程
    • 4.浏览器是“多线程”的,但是JS渲染或者页面渲染是“单线程的”
      • GUI渲染线程(渲染和绘制页面)
      • JS引擎线程(运行和渲染JS代码)
      • 事件管控和触发线程
      • 定时器管控和触发线程
      • 异步HTTP请求线程

    3.同步编程和异步编程

    • 1.同步编程:一次只能处理一件事情,当前这件事情处理完,才能继续处理下一件事情
  • 2.异步编程:同时可以进行好几件事情(一般是基于多线程并发完成,JS中的异步编程,有自己一些特殊的处理方式=>队列Queue和事件循环EventLoop)

    4.优化方案

  • 标签语义化和避免深层次嵌套

  • CSS选择器渲染是从右到左

  • 尽早尽快地把CSS下载到客户端(充分利用HTTP多请求并发机制)

  • style

  • link

  • @import

  • 放到顶部

  • 避免阻塞的JS加载

  • 放到底部

  • 减少DOM的回流和重绘

    5.重绘

    元素样式的改变(但宽高、大小、位置等不变) 如:outline,visibility,color、background-color等

    6.回流

    元素的大小或者位置发生了变化(当前页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重新计算布局和渲染

    • 如添加或删除可见的DOM元素:元素的位置发生变化;元素的尺寸发生变化,内容发生变化(比如文本变化或图片被另一个不同尺寸的图片所替代);页面一开始渲染的时候(这个无法避免);因为回流是根据视口的大小来计算的位置和大小的,所以浏览器的窗口尺寸变化也会引发回流...
    • 注意:回流一定会触发重绘,而重绘比一定会回流

7.避免DOM回流

  • 1.以下代码引发几次回流?

    • 在老版本的浏览器中,我们分别改变了三次样式(都涉及了位置或者大小的改变),所以触发三次回流和重绘
    • 现代浏览器中默认增加了“渲染队列的机制”,以此来减少DOM的回流和重绘;遇到一行修改样式的代码,先放到渲染队列中,继续看下面一行代码是否还为修改样式的,如果是继续增加到渲染队列中...直到下面的代码不再是修改样式的,而是获取样式的代码!此时不再向渲染队列中增加,把之前渲染队列中要修改的样式一次性渲染到页面中,引发一次DOM的回流和重绘
    DOM的回流和重绘 * { margin: 0; padding: 0; } .box { width: 100px; height: 100px; background: red; } </style>

    box.style.width = '200px'; box.style.height = '200px'; box.style.margin = '20px';

  • 2.解决方案

1.分离读写:一次回流和重绘

box.style.width = '200px';
box.style.height = '200px';
box.style.margin = '20px';
console.log(box.style.width);
console.log(box.offsetHeight);

2.三次回流和重绘

box.style.width = '200px';
console.log(box.style.width);
//=>中断渲染队列,立即渲染一次,引发一次DOM回流和重绘  200px
box.style.height = '200px'; 
console.log(box.offsetHeight);
box.style.margin = '20px'; 

3.手动不分离读写的需求:先设置动画,渲染后,在去改变样式,让其有动画效果

box.style.transition = '.3s';
//下面代码加上后 0.3后才会产生动画
let AA = box.offsetHeight;

box.style.width = '200px';
box.style.height = '200px';

4.集中改变样式

box.className = 'active';
box.style.cssText = 'width:200px;height:200px;';

5.在动态操作DOM结构中的优化(例如:数据绑定)

for (let i = 1; i <= 5; i++) {
let liBox = document.createElement('li');
liBox.innerText = `我是第${i}个LI`;
item.appendChild(liBox); 
//=>每一次向页面中增加,都会触发一次DOM的回流和重绘(5次)
}

6.文档碎片:临时创建的一个存放文档的容器,我们可以把新创建的LI,存放到容器中,当所有的LI都存储完,我们统一把容器中的内容增加到页面中(只触发一次回流)

let frag = document.createDocumentFragment();
for (let i = 1; i <= 5; i++) {
	let liBox = document.createElement('li');
	liBox.innerText = `我是第${i}个LI`;
	frag.appendChild(liBox);
}
item.appendChild(frag);

7.真实项目中,有一个文档碎片类似的方式,也是把要创建的LI事先存储好,最后统一放到页面中渲染(字符串拼接)

let str = ``;
for (let i = 1; i <= 5; i++) {
	str += `<li>我是第${i}个LI</li>`;
}
item.innerHTML = str;