js重绘与回流

1.浏览器的渲染过程

  • 解析生成DOM Tree(此时包含所有节点,包括display:none);
  • 根据CSS Object Module(CCSSOM)计算节点的几何属性(坐标和大小)(margin,pading,height,width等),生成Render Tree(不包含display: none的节点);这一过程叫回流或者布局;
  • 在Render Tree进一步渲染其它属性。如:color等。

2.重绘

重绘(repaint):当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要 UI 层面的重新像素绘制,因此损耗较少。
常见的重绘操作有:

  • 改变元素颜色
  • 改变元素背景色
    在这里插入图片描述

3.回流

回流(reflow):又叫重排(layout)。当元素的尺寸、结构或者触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。
常见的回流操作有:

  • DOM的添加和删除;
  • 页面的加载;
  • 元素尺寸改变——边距、填充、边框、宽度和高度;
  • 元素位置的改变;
  • 内容变化,比如用户在input框中输入文字;
  • 浏览器窗口尺寸改变——resize事件发生时;
  • 获取某些属性:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、 clientTop、clientLeft、clientWidth、clientHeight。(浏览器为了返回最精确的值,需要flush队列,因为队列中可能会有影响到这些值的操作)
    重点:回流必定会触发重绘,重绘不一定会触发回流。重绘的开销较小,回流的代价较高。

4.如何减少重绘和回流

  • 使用 transform 替代 top
  • 使用 visibility 替换 display: none ,因为前者只会引起重绘,后者会引发回流(改变了布局)
  • 不要把节点的属性值放在一个循环里当成循环里的变量。
  • 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局
  • 动画实现的速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame
  • CSS 选择符从右往左匹配查找,避免节点层级过多
  • 将频繁重绘或者回流的节点设置为图层,图层能够阻止该节点的渲染行为影响别的节点。比如对于 video 标签来说,浏览器会自动将该节点变为图层。
    浏览器的回流优化机制:浏览器会维护1个队列,把所有会引起重排、重绘的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的重排、重绘变成一次重排重绘。

4.一次渲染多个DOM会出现的问题

  • 1.如果一次性创建加载的DOM数量太多,js执行时间很长,会导致白屏
  • 2.浏览器是单线程的,用户点击、滚动的回调函数会添加到js任务队列尾部,然后等待执行。这些回调都因为等待js引擎创建渲染DOM而被阻塞了。所以二次点击的操作不会马上执行,而是在DOM加载完成之后又得到了执行,给出弹窗。造成卡的情况发生。

5.原生js渲染大量DOM优化

<body>
    <button onclick="alert('hello')">按钮</button>
    <ul>列表</ul>

    <script>
        // 插入100000条数据
        let total = 100000;
        // 设置每次插入的DOM数量
        let flag = 100;
        // 当前已经渲染的DOM数量
        let alreadyRender = 0;
        let ul = document.querySelector('ul');

        function loop() {
            requestAnimationFrame(() => {
                const fragment = document.createDocumentFragment();
                for (let i = 0; i < flag; i++) {
                    // DOM渲染完就退出
                    if (alreadyRender >= total) return;
                    const li = document.createElement('li');
                    li.innerText = Math.floor(Math.random() * total);
                    fragment.appendChild(li);
                    alreadyRender++;
                }
                ul.appendChild(fragment);
                loop();
            })
        }
        loop()
    </script>
</body>

6.vue3封装高速渲染DOM组件


版权声明:本文为m0_51279688原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。