写点什么

前端页面之“回流重绘”

作者:CoderBin
  • 2022-10-17
    广东
  • 本文字数:2551 字

    阅读完需:约 1 分钟

前端页面之“回流重绘”

大家好,我是 CoderBin

“回流重绘”是什么?

HTML中,每个元素都可以理解成一个盒子,在浏览器解析过程中,会涉及到回流与重绘:


  • 回流:布局引擎会根据各种样式计算每个盒子在页面上的大小与位置

  • 重绘:当计算好盒模型的位置、大小及其他属性后,浏览器根据每个盒子特性进行绘制


具体的浏览器解析渲染机制如下所示:



  • 解析 HTML,生成 DOM 树,解析 CSS,生成 CSSOM 树

  • 将 DOM 树和 CSSOM 树结合,生成渲染树(Render Tree)

  • Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)

  • Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素

  • Display:将像素发送给 GPU,展示在页面上


在页面初始渲染阶段,回流不可避免的触发,可以理解成页面一开始是空白的元素,后面添加了新的元素使页面布局发生改变


当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性,然后再将计算的结果绘制出来


当我们对 DOM 的修改导致了样式的变化(colorbackground-color),却并未影响其几何属性时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式,这里就仅仅触发了回流

如何触发

要想减少回流和重绘的次数,首先要了解回流和重绘是如何触发的

回流触发时机

回流这一阶段主要是计算节点的位置和几何信息,那么当页面布局和几何信息发生变化的时候,就需要回流,如下面情况:


  • 添加或删除可见的 DOM 元素

  • 元素的位置发生变化

  • 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)

  • 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代

  • 页面一开始渲染的时候(这避免不了)

  • 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)


还有一些容易被忽略的操作:获取一些特定属性的值


offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight


这些属性有一个共性,就是需要通过即时计算得到。因此浏览器为了获取这些值,也会进行回流


除此还包括getComputedStyle 方法,原理是一样的

重绘触发时机

触发回流一定会触发重绘


可以把页面理解为一个黑板,黑板上有一朵画好的小花。现在我们要把这朵从左边移到了右边,那我们要先确定好右边的具体位置,画好形状(回流),再画上它原有的颜色(重绘)


除此之外还有一些其他引起重绘行为:


  • 颜色的修改

  • 文本方向的修改

  • 阴影的修改

浏览器优化机制

由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列


当你获取布局信息的操作的时候,会强制队列刷新,包括前面讲到的offsetTop等方法都会返回最新的数据


因此浏览器不得不清空队列,触发回流重绘来返回正确的值

三、如何减少

我们了解了如何触发回流和重绘的场景,下面给出避免回流的经验:


  • 如果想设定元素的样式,通过改变元素的 class 类名 (尽可能在 DOM 树的最里层)

  • 避免设置多项内联样式

  • 应用元素的动画,使用 position 属性的 fixed 值或 absolute 值(如前文示例所提)

  • 避免使用 table 布局,table 中每个元素的大小以及内容的改动,都会导致整个 table 的重新计算

  • 对于那些复杂的动画,对其设置 position: fixed/absolute,尽可能地使元素脱离文档流,从而减少对其他元素的影响

  • 使用 css3 硬件加速,可以让transformopacityfilters这些动画不会引起回流重绘

  • 避免使用 CSS 的 JavaScript 表达式


在使用 JavaScript 动态插入多个节点时, 可以使用DocumentFragment. 创建后一次插入. 就能避免多次的渲染性能


但有时候,我们会无可避免地进行回流或者重绘,我们可以更好使用它们


例如,多次修改一个把元素布局的时候,我们很可能会如下操作


const el = document.getElementById('el')for(let i=0;i<10;i++) {    el.style.top  = el.offsetTop  + 10 + "px";    el.style.left = el.offsetLeft + 10 + "px";}
复制代码


每次循环都需要获取多次offset属性,比较糟糕,可以使用变量的形式缓存起来,待计算完毕再提交给浏览器发出重计算请求


// 缓存offsetLeft与offsetTop的值const el = document.getElementById('el') let offLeft = el.offsetLeft, offTop = el.offsetTop
// 在JS层面进行计算for(let i=0;i<10;i++) { offLeft += 10 offTop += 10}
// 一次性将计算结果应用到DOM上el.style.left = offLeft + "px"el.style.top = offTop + "px"
复制代码


我们还可避免改变样式,使用类名去合并样式


const container = document.getElementById('container')container.style.width = '100px'container.style.height = '200px'container.style.border = '10px solid red'container.style.color = 'red'
复制代码


使用类名去合并样式


<style>  .basic_style {      width: 100px;      height: 200px;      border: 10px solid red;      color: red;  }</style><script>  const container = document.getElementById('container')  container.classList.add('basic_style')</script>
复制代码


前者每次单独操作,都去触发一次渲染树更改(新浏览器不会),


都去触发一次渲染树更改,从而导致相应的回流与重绘过程


合并之后,等于我们将所有的更改一次性发出


我们还可以通过通过设置元素属性display: none,将其从页面上去掉,然后再进行后续操作,这些后续操作也不会触发回流与重绘,这个过程称为离线操作

往期推荐 💐 🌸 🌹 🌻 🌺 🍁

高阅读好文


【1】关于Vue的一些高频面试题总结


【2】75道CSS高频面试题总结,请注意查收!🔥


【3】Vue内置指令大全


【4】面试官:你说说 js 中实现继承有哪几种方法?


【5】一篇文章带你搞懂 this 的四个绑定规则 ✍


----- 🌻 查看全部 🍁 -----




每文一句:学习是把知识能力思维方法等转化为你的私有产权的重要手段,是“公有转私”的重要途径。你的一生,无法离开学习,学习是你最忠实的朋友,它会听你的召唤,它会帮助你走向一个又一个成功。


本次的分享就到这里,如果本章内容对你有所帮助的话欢迎点赞+收藏。文章有不对的地方欢迎指出,有任何疑问都可以在评论区留言。希望大家都能够有所收获,大家一起探讨、进步!

发布于: 刚刚阅读数: 3
用户头像

CoderBin

关注

始于前端,终于..... 2020-10-14 加入

普通人只考虑他们将如何度过时间,而有智慧的人则试图利用时间 — CoderBin

评论

发布
暂无评论
前端页面之“回流重绘”_CSS_CoderBin_InfoQ写作社区