Skip to content

详解浏览器回流机制

首先,我们需要了解一下重绘和回流的基本定义。

  • 回流:是指浏览器需要重新计算元素的几何属性(如位置、尺寸),导致渲染树(Render Tree)的部分或全部重新布局(Layout)的过程
  • 重绘:是指当元素的外观样式改变但不影响布局时(如颜色、背景、阴影等),浏览器仅重新绘制(Paint)受影响区域到屏幕的过程

那什么情况下会触发回流?

触发回流操作

  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 改变元素几何信息
  • 增加、删除可见DOM元素

有了基本了解后,我们来看看以下几个例子

案例1

以下代码会触发几次回流?

js
dom.style.width = '100px'
dom.style.backgroundColor = 'red'

答案:1次。

解读:width = '100px'属于修改元素几何信息,属于回流操作。而backgroundColor = 'red'不会影响元素几何大小和布局位置,所以不会触发回流,只会触发重绘。

案例2

以下代码会触发几次回流?

js
dom.style.width = '100px'
dom.style.marginLeft = '100px'
dom.style.height = '100px'

答案:还是1次

有人会说,widthmarginLeftheight不都影响页面布局吗?按理说不是应该触发3次回流吗?

这里涉及到一个浏览器的优化机制:当我们操作dom的样式时,浏览器并不是立即放入主线程中执行,而是先放入到渲染队列中,等主线程空闲时再一起执行,这样可以减少不必要的回流。

比如上面代码,浏览器会分别创建3个渲染任务(dom.style.width = '100px'、dom.style.marginLeft = '100px'、dom.style.height = '100px')依次推入到渲染队列,然后浏览器空闲时,一次性执行,所以回流只触发一次

案例3

以下代码会触发几次回流?

js
dom.style.marginLeft = '100px'
dom.style.width = '100px'
dom.style.height = dom.offsetWidth + 10 + 'px'
dom.style.padding = dom.offsetWidth + 10 + 'px'

答案:3次

首先,前两行代码执行后,dom.style.marginLeft = '100px'dom.style.width = '100px'会依次放在渲染队列中, 当第三行代码读取dom.offsetWidth值时,会触发渲染队列刷新,此时浏览器会强制执行渲染队列中的所有任务,并清空队列,这是第一次回流。 接着dom.style.height = dom.offsetWidth + 10 + 'px'这段代码会被放入渲染队列中,当第四行代码再次读取dom.offsetWidth时,又会触发渲染队列刷新,执行dom.style.height = dom.offsetWidth + 10 + 'px'代码,并触发第二次回流。 最后dom.style.padding = dom.offsetWidth + 10 + 'px'放入渲染队列,等浏览器空闲时,自动执行,并触发第三次回流。

这里涉及一个新的知识,什么时候获取布局信息,会导致渲染队列刷新?

回答:访问以下方法时:

  • offsetTop、offsetLeft、offsetWidth、offsetHeight
  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • clientTop、clientLeft、clientWidth、clientHeight
  • getComputedStyle()
  • getBoundingClientRect()
  • 获取窗口尺寸、Range尺寸等。更多方法见文档

案例4

以下代码,当点击按钮后,会触发几次回流?

html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .root {
            width: 50px;
            height: 50px;
            background-color: red;
        }
    </style>
</head>

<body>
    <div class="root">哈哈</div>
    <button>Click me</button>
    <script>
        const btn = document.querySelector('button')
        function handler() {
            const dom = document.querySelector('.root')
            dom.style.top = '10px'
            dom.style.width = dom.offsetWidth + 10 + 'px'
            dom.style.width = 20 + 'px'
        }
        btn.onclick = handler
    </script>
</body>

</html>

答案:1次

那如果再上面代码基础上,加上一行样式呢?比如

css
.root {
    width: 50px;
    height: 50px;
    background-color: red;
    position: absolute; // 新增样式
}

重新点击按钮后,会触发几次回流?

答案:2次

为什么js代码没变,只改样式,反而会多触发一次回流呢?

我们来分析下流程: 首先dom.style.top = '10px'先放入渲染队列,然后第二行代码会先读取dom.offsetWidth属性,强制刷新渲染队列。在这里需要特别注意,在position: absolute;样式没有时,修改top是不影响页面布局的,所以不会触发回流,但是当我们加上position: absolute;时,修改top会影响页面布局,所以会触发回流。 接着dom.style.width = dom.offsetWidth + 10 + 'px'放入渲染队列, 然后dom.style.width = 20 + 'px'也放入渲染队列, 最后浏览器空闲时,自动执行渲染队列中的所有任务,并清空队列,触发一次回流。

所以,当我们加上position: absolute;时,总共触发2次回流。没有position: absolute;时,只会触发1次回流。

苏ICP备20040768号