浏览器渲染原理以及海量数据渲染的方法

656 阅读4分钟

浏览器的渲染原理

浏览器工作流程:构建DOM -> 构建CSSOM -> 构建渲染树 -> 布局 -> 绘制。

  1. 首先解析接收到的文档,根据文档定义构建一棵DOM树,DOM树是DOM元素及属性节点组成的
  2. 然后对CSS进行解析,生成CSSOM规则树
  3. 根据DOM树和CSSOM规则树构建渲染树,渲染树的节点被称为渲染对象,渲染对象是一个包含有颜色和大小等属性的矩形,渲染对象和DOM元素先对应,但这种对应关系不是一一对应的,不可见的DOM元素不会被插入渲染树,还有一些DOM元素对应着几个可见的对象,它们一般是一些具有复杂结构的元素,无法用一个矩形来描述
  4. 当渲染对象被创建并添加到树种,它们并没有位置和大小,所以当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情就是弄清楚各个节点在页面中的确切位置和大小,通常这一行为也被陈伟“自动重排”
  5. 布局阶段结束后是绘制阶段,遍历渲染树并调用渲染对象的paint方法将它们的内容显示到屏幕上,绘制使用UI基础组件

值得注意的是,这个过程是完成的,为了更好的用户体验,渲染引擎将会尽可能早地将内容呈现到屏幕上,并不会等所有的html都解析完之后再去构建和布局render树,它是解析完一部分就显示一部分内容,同时还可能通过网络下载其他内容

详细资料可以参考: 《浏览器渲染原理》 《浏览器的渲染原理简介》 《前端必读:浏览器内部工作原理》 《深入浅出浏览器渲染原理》

为什么操作DOM会慢

因为DOM是属于渲染引擎中的东西,而JS又是JS引擎的东西,当我们通过JS去操作DOM的时候,其实这个操作就涉及了两个线程之间的通信,那么势必会带来一些性能上的损耗。操作DOM次数一多,也就等于一直在进行线程之间的通信,并且操作DOM可能还会带来重绘回流的情况,所以也就导致了性能上的问题。

插入几万个 DOM,如何实现页面不卡顿?

首先我们可能不能一次性将几万个DOM全部插入,这样肯定会造成卡顿,所以解决问题的重点是应该如何分批次部分渲染DOM。

  1. 很多人都会想到的方式是通过requestAnimationFrame循环插入

    **原理:**渲染大数据时,合理使用createDocumentFragment和requestAnimationFrame,将操作切分为一小段一小段执行。

    **documentFragment:**是一个虚拟的Dom列表,可以储存待处理的xml片段(el元素),因为他不在真实的Dom结构中,所以对它所做的操作不会触发浏览器的回流,只会在他插入dom的时候触发一次而已。

    ​ 上面把多个动态生成的div插入到了虚拟节点里,在最后完成之后只做了一次插入,这样就只会触发一次回流。     

    ​ 但是在数量太多的时候,哪怕是一次插入,也会因为浏览器渲染不过来导致失去响应,这时候就需要增加一定的时间间隔,可以使用setTimeout,也可以使用一个api-requestAnimationFrame

    requestAnimationFrame:

    特点:

    • requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率
    • 在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量
    • requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销

    使用:

    ​ requestAnimationFrame的用法与settimeout很相似,只是不需要设置时间间隔而已。requestAnimationFrame使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。它返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame用于取消这个函数的执行

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<ul>
			渲染数据
		</ul>
   
	</body>
  <script>
    setTimeout(() => {
      //插入十万条数据
      const total = 100000;
      //一次插入20条,如果觉得性能不好就减少
      const once = 20;
      //计算渲染数据的次数
      const loopCount = total / once;
      let countOfRender = 0;
      let ul = document.querySelector("ul");
      function add() {
        //使用createDocumentFragment来优化新能,插入不会造成回流
        const fragment = document.createDocumentFragment();
        for (let i = 0; i < once; i++) {
          const li = document.createElement("li");
          li.innerText = Math.floor(Math.random() * total);
          fragment.appendChild(li);
        }
        ul.appendChild(fragment);
        countOfRender += 1;
        loop();
      }
      function loop() {
        if (countOfRender < loopCount) {
          window.requestAnimationFrame(add);
        }
      }
      loop();
    }, 0);
  </script>
</html>

  1. 虚拟滚动:virtualized scroller

    这种技术的原理就是渲染可视区域内的内容,非可见区域的就完全不渲染,当用户当用户在滚动的时候就实时去替换渲染的内容。

从上图中我们可以发现,即使列表很长,但是渲染的 DOM 元素永远只有那么几个,当我们滚动页面的时候就会实时去更新 DOM

react-virtualized就是专门解决这个的库