su37josephxia/frontend-interview

Day29 - 回流和重绘

su37josephxia opened this issue · 20 comments

什么是回流

每个页面第一次加载的时候,因为要构建render tree(渲染树),都会发生回流。当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,这一过程称为回流,之后浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。

什么是重绘

当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如颜色的更改。则称为重绘。

总结

当页面布局和几何属性改变时就需要回流,回流必将引起重绘,而重绘则不一定会引起回流

优化

一般为了减少页面回流和重绘,可以在操作页面时尽可能少的操作DOM,比如将DOM插入操作放进队列,在一定时间后或者队列中操作打到一定数量时再进行统一操作,VUE和React的虚拟DOM就是用JavaScript模拟DOM树,并操作模拟的DOM树对象,最后对处理后的模拟DOM树进行渲染

  • 回流
    • 当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候是一定会发生回流的,因为要构建render tree。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘
  • 重绘
    • 当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘
  • 回流必将引起重绘,而重绘不一定会引起回流

回流又叫重排,是指页面中某些元素的尺寸或者位置发生变化,浏览器需要重新计算元素的几何属性,将其放在页面中正确的位置,这个过程叫做重排。
重绘是指元素布局不变,只是外观发生变化,比如背景颜色或者边框颜色发生变化。

重排和重绘都会增加浏览器的开销,因为重排是整个元素重新渲染,所以要比重绘开销更大。
可以通过集中进行元素位置和样式的改变以及将元素脱离文档流进行重排、重绘的性能优化。

定义
回流(reflow):当页面中元素的宽高、布局和显示隐藏等改变需要重新构建页面,这就是回流。
重绘(repaint):页面中的元素只是外观样式发生变化,比如背景颜色、内部文字颜色、边框颜色等,不会改变元素结构的,这就是重绘。
注意
回流一定会触发重绘,重绘不一定触发回流。
回流和重绘对性能的影响
当浏览器渲染页面时会开启两条线程:一条渲染JS脚本,一条渲染UI。两条线程互斥,当JS脚本运行时UI线程中止暂停,反之也是如此。当页面样式频繁改动时,会导致UI线程持续渲染,导致页面卡顿,所以要尽量减少重绘和回流。
如何减少回流和重绘

  • 利用 transform 代替 top,left,margin-top,margin-left 等位移属性
  • 用 opacity 替换 visibility,同时结合 transform3d 或者 translateZ 才启作用
  • 减少 JS 控制 dom 元素,利用 className 替代

回流(reflow):页面或者中元素的宽高、布局发生变换,这些变化需要页面布局叫回流
重绘(repaint):页面或者页面元素仅仅样式、显示隐藏等外观样式发生变化,不需要页面重新布局这叫重绘。

回流一定重绘,重绘不一定会回流

回流、重绘比较消耗性能,所以开发过程中要避免或者减少不必要的回流、重绘

回流

Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。
会导致回流的操作:

  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 元素尺寸或位置发生改变
  • 元素内容变化(文字数量或图片大小等等)
  • 元素字体大小变化
  • 添加或者删除可见的DOM元素
  • 激活CSS伪类(例如::hover

查询某些属性或调用某些方法

一些常用且会导致回流的属性和方法:

  • clientWidthclientHeightclientTopclientLeft
  • offsetWidthoffsetHeightoffsetTop、offsetLeft`
  • scrollWidthscrollHeightscrollTopscrollLeft
  • scrollIntoView()scrollIntoViewIfNeeded()
  • getComputedStyle()
  • getBoundingClientRect()
  • scrollTo()

重绘

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:colorbackground-colorvisibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。

总结

有时即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流。

回流必将引起重绘,重绘不一定会引起回流。

  • 回流:当浏览器第一次加载页面时,会构建render tree,render tree的构建其实就是对已渲染的页面内容进行失效与重绘的过程,当render tree检测到页面内容因为布局,大小,属性等变化时就会使该部分内容失效,这个过程就叫回流。
  • 重绘:在回流之后,浏览器对该部分内容进行重新定义生成新的render tree,最终渲染到页面,这一过程就叫重绘。
  • 所以回流必定会出现重绘,但重绘不一定必须先进行回流。比如我们更改页面上一朵花的颜色,那么浏览器只需要将原来的颜色替换为想要的颜色即可,并不需要使原来的dom元素失效再重新定为布局排列等。
  • 引发回流的因素:
  1. 调整窗口大小
    2.改变字体
    3.增加或者移除样式表
    4.内容变化,比如用户在 input 框中输入文字, CSS3 动画等
    5.激活 CSS 伪类,比如 :hover
    6.操作class属性
    7.脚本操作DOM
    8.计算offsetWidth和offsetHeight属性
    9.设置 style 属性的值

如何触发重排和重绘? 任何改变⽤来构建渲染树的信息都会导致⼀次重排或重绘: 添加、删除、更新DOM节点 通过display: none隐藏⼀个DOM节点-触发重排和重绘 通过visibility: hidden隐藏⼀个DOM节点-只触发重绘,因为没有⼏何变化 移动或者给⻚⾯中的DOM节点添加动画 添加⼀个样式表,调整样式属性 ⽤户⾏为,例如调整窗⼝⼤⼩,改变字号,或者滚动。 如何避免重绘或者重排? 集中改变样式 我们往往通过改变class的⽅式来集中改变样式 // 判断是否是⿊⾊系样式 const theme = isDark ? 'dark' : 'light'
使⽤DocumentFragment 我们可以通过createDocumentFragment创建⼀个游离于DOM树之外的节点,然后在此节点上批量操作,最后插⼊ DOM树中,因此只触发⼀次重排

什么是回流
当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候是一定会发生回流的,因为要构建render tree。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。

什么是重绘
当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。

区别:
回流必将引起重绘,而重绘不一定会引起回流。比如:只有颜色改变的时候就只会发生重绘而不会引起回流
当页面布局和几何属性改变时就需要回流
比如:添加或者删除可见的DOM元素,元素位置改变,元素尺寸改变——边距、填充、边框、宽度和高度,内容改变

回流是计算布局信息,重绘是根据布局信息和样式信息进行页面绘制。当修改窗口,元素的大小,样式,插入删除元素等影响布局的操作都会触发回流。获取元素的大小偏移量等位置信息的时候也会触发回流。当触发了回流的时候一定触发重绘,除此之外就是当颜色改变时只会触发重绘。

频繁的回流会影响性能和页面效果,减少回流的操作有3种,第一统一读取或设置元素,不要交替进行。第二进行分层,让元素脱离文档流或者启用gpu渲染。第三设置成局部的回流。当外层容器的有固定的宽高和位置的时候,容器内部发生变化只会在这个容器内产生回流,减少整个文档的回流。

回流:节点发生改变时,浏览器重新渲染部分节点或者全部文档
重绘:节点上的元素并不导致元素位置发生变化时,比如color,background-color,visibility(注意虽然节点隐藏了,但是元素还在,并且位置也不会发生变化),浏览器会将新的样式赋值给这些节点

有了昨天回答浏览器渲染过程题目的基础,我们解释下什么叫回流(也叫重排)和重绘

  • 什么是重排: 如果我们改变元素的宽度、高度等,那么浏览器会触发重新布局,通过昨天的题目,我们知道,重新布局后还需要分层图层绘制光栅化合成/显示等流程,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的
  • 那么什么是重绘呢: 如果通过JavaScript只更改某些元素的背景颜色字体颜色等,渲染流水线会怎么样变化呢?因为没有发生几何位置的变化所以不需要重新布局/分层,直接进入绘制阶段,所以重绘比重排少了布局和分层两个步骤,结果就是重绘比回流更效率

不管重绘重排都会消耗浏览器资源,那么如何尽可能的去减少重绘/重排呢?
● 触发重排的操作放在在一起, 比如改变DOMmargin和/height, 不写在一起可能造成两次重排
● 通过createDocumentFragment汇总appendChild操作
虚拟DOM, 通过DIFF对比最小差异一起提交给浏览器处理,减少重绘重排

对 DOM 操作导致 DOM 尺寸等属性的变化(比如修改元素的 width、height、top)时,浏览器需要重新计算元素的属性,然后再将计算的结果绘制出来,这个过程叫做回流。
常见的会导致回流的操作:
页面首次加载
浏览器窗口尺寸改变
元素尺寸或位置改变
元素内容变化
元素字体大小变化
增删 DOM 元素
查询或调用某些特定属性方法
重绘:对 DOM 操作简单修改样式(比如修改元素的 visibility、color、background-color 等)、却并未影响页面布局时,浏览器不需重新计算元素的位置尺寸等,直接为该元素绘制新的样式。这个过程叫做重绘。
回流往往代价比重绘大;
回流一定重绘,重绘未必回流。

浏览器的渲染过程是解析html生成dom树,遇link解析生成CSSOM树,然后将dom树和CSSOM数合并为Render Tree,执行layout布局,最终渲染;
回流:
当渲染树中部分或全部元素的尺寸、结构或某些属性发生改变导致浏览器重新渲染部分或全部文档的过程称为回流;
导致回流的操作例如:
页面首次渲染、浏览器窗口大小发生改变、元素尺寸或位置改变、字体大小变化、添加或删除可见dom、激活css伪类(例如::hover)、还有一些导致回流的属性和方法(clientWidth、clientHeight、scrollTop、scrollTo()、getBoundingClientRect()等)

重绘:
当页面中元素样式的改变并不影响它在文档中的布局位置,浏览器将新样式重新绘制出来的这个过程叫重绘;
导致重绘操作:
修改css样式比如color、background-color、visibility等

回流一定会引起重绘,重绘不一定引起回流

回流:元素的尺寸等属性的改变,引起了浏览器对元素位置的重新计算的,并重新绘制的过程
重绘:元素的位置尺寸不变,颜色等样式的改变,并重新绘制的过程
回流一定会引起重绘,重绘不一定引起回流,因此回流要比重绘消耗的性能更高

昨天我们讨论了浏览器渲染的流程。如下:

  1. 构建DOM树
  2. 构建CSSOM树
  3. 合成render树
  4. 布局layout
  5. 绘制painting

那么,第4步布局,初始化第一次的时候我们叫他布局,之后再触发,我们就叫他回流
同理,第5步绘制,初始化绘制,之后再触发,就叫重绘

怎样会触发回流呢?

  • 页面内元素大小或者位置发生变更就会引起回流,如果是根节点变更了,则会引起整体页面的回流

怎样会触发重绘呢?

  • 页面内元素的样式如字体、背景、边框的颜色,阴影,或者哪怕内容变更或者图片替换,但只要大小尺寸不变,都只会引发重绘不会触发回流

回流必然会触发重绘,重绘不一定会发出回流。

有没有什么需要注意的优化的点?如下:

  1. 多次DOM操作可以合并成一次,或者将元素使用display:none隐藏在操作,操作完了再展示出来
  2. 使用document Fragment来在构建另一颗DOM子树来操作DOM,完事再拷贝我们的DOM树种
  3. 将元素脱离文档流再操作,操作完再拼接回来。
  4. 使用css硬件加速,可以让浏览器不对这些transform、opacity、filters这些动画不触发回流重绘,常见的触发硬件加速的css属性:transform、opacity、filters。我们也可以用一些*操作在一些非动画样式是使用这些动画样式,诱导浏览器触发css硬件加速,但是这会增加内存负担。
  5. 当我们读取以下元素的时候会导致浏览器触发回流,所以我们在使用的时候可以缓存这些值,不要重复读取。
 offsetTop、offsetLeft、offsetWidth、offsetHeight
 scrollTop、scrollLeft、scrollWidth、scrollHeight
 clientTop、clientLeft、clientWidth、clientHeight
 getComputedStyle
 getBoundingClientRect

以上操作虽然有用,但现代浏览器对回流重绘一般都有做优化,比如会对多次DOM操作合并处理,并不会引起多次回流,所以以上操作如果不是特别复杂,体验上几乎感觉不到差别。

回流

回流也称重排,是指对页面的元素位置重新排列,回导致页面大量元素重新渲染,一般发生在元素的大小和位置发生改变,或者元素的显示和隐藏

重绘

重绘是指对当前元素进行重新渲染,一般发生在当前元素的颜色改变等不会影响到其他元素的位置改变的操作

两者的关系

回流一定伴随着重绘,重绘不一定触发回流

工作中的优化

  1. 尽量用重绘代替重排
  • 例如:加载图片时需要时间,可以在一开始用一个和图片大小相等的元素代替图片,当图片加载完成,直接替换这个元素,这样不会导致其他元素的重排
  1. 使用 js 改变元素的宽高时,尽量批量获取属性之后再批量修改
  2. 使用 DocumentFragment 存储元素,然后再将元素批量插入到页面
  3. 尽量使用 css3 动画开启 GPU 加速,减少重排

回流,由于元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树
重绘,由于元素发生的改变只是影响了元素的一些外观之类的时候

HTML 规范中对于界面的展现是基于盒模型的机制。

一个盒子,它的大小和尺寸会导致会影响他内部盒子的位置,会影响他兄弟盒子,甚至能影响到他父亲盒子的尺寸和位置,因此,在绘制界面之前,就需要计算出所有这些的位置布局信息,然后再结合样式去做界面的展现,所以这个过程中就出现了回流和重绘的概念。

所谓的回流,就是对所有的节点计算他们的布局信息。

所谓的重绘就是根据布局信息以及样式信息对界面做展示,做渲染。