重排与重绘

重排与重绘

一个页面渲染完毕后,随着用户的操作,或者数据变化,网页还会进行重新渲染。根据不同的触发条件,重新渲染分为两种情况:重排(reflow)和重绘(repaint)。

所有对元素视觉表现属性的修改,都会导致重绘(repaint)。比如修改了背景颜色、文字颜色等。

所有会触发元素布局发生变化的修改,都会导致重排(reflow)。比如窗口尺寸发生变化,删除、添加 DOM 元素,修改了影响元素盒子大小的 CSS 属性如 width、 height、 padding 等。

通常情况下,重排的影响更大,重排会导致文档局部或全部的重新运算:重新计算元素位置,大小。(改变一个元素的布局,可能会影响很多个元素的布局)

不管是重绘还是重排导致的重新渲染,都会阻塞浏览器。重新渲染的的过程中,JavaScript 会被阻塞,用户的交互行为也会被卡住。复杂的 CSS 动画甚至会拖慢 JavaScript 的运行速度。

注:本文涉及到的 JavaScript 部分,可以先忽略,等以后学习了 JavaScript 再来查看。

导致重排和重绘的场景

CSS 属性改变

网站 CSS trigglers 列出了所有 CSS 属性对 layout (布局)、paint (绘制)的影响。通过这个表,可以查到不同内核下,对 CSS 属性的修改会导致重绘、重排还是两者都会发生。

注:Composite (渲染层合并) 是 chrome 引入 GPU 加速带来的新概念。(相关信息可参看下面的参看资料)

对 CSS 属性进行修改,包括但不限于以下场景:

  • 通过 display: none 隐藏 DOM 节点(导致重绘和重排)
  • 通过 visibility: hidden 隐藏 DOM 节点 (导致重绘,因为它没有影响其它元素位置布局)
  • CSS 动画
  • 通过 JavaScript 添加样式,修改样式

用户交互

  • 对浏览器窗口进行缩放操作会导致重排
  • 对 DOM 节点进行操作,添加、删除、更新 DOM 节点都会导致重排
  • 光标 :hover 、进入文本输入框、修改浏览器的字体都会导致重排

最佳实践

所有的最佳实践都是围绕尽最大可能的降低重绘和重排的频率,来达到减少重新渲染的目的。

CSS 属性的读、写操作分开

浏览对 CSS 属性的连续修改做了优化,比如下面的连续修改两次 style,不会导致两次重新渲染:

div.style.color = 'blue';
div.style.marginTop = '30px';

上面代码只会进行一次重新渲染。但是如果写的不好,则会触发两次重新渲染,如下:

div.style.color = 'blue';
var margin = parseInt(div.style.marginTop);
div.style.marginTop = (margin + 10) + 'px';

上面代码对 div 元素设置背景色以后,第二行要求浏览器给出该元素的位置,所以浏览器不得不重新渲染然后得到该元素的位置。

除此之外,样式的写操作之后,如果有下面这些属性的读操作,都会引发浏览器立即重新渲染:

  • offsetTop/offsetLeft/offsetWidth/offsetHeight
  • scrollTop/scrollLeft/scrollWidth/scrollHeight
  • clientTop/clientLeft/clientWidth/clientHeight
  • getComputedStyle()

通过 class 或者 csstext 来批量更新样式

上面对通过对 style 对 CSS 属性一个一个修改,其实更好的方式应该是通过 class 来批量化。

// bad
var left = 10;
var top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";

// good 
el.className += " theclassname";

// good
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";

其他方法

  • DOM 样式离线更新:尽量使用离线 DOM,而不是真实的网页 DOM 来改变元素样式。比如,操作 Document Fragment对象,完成后再把这个对象加入 DOM。再比如,使用 cloneNode() 方法,在克隆的节点上进行操作,然后再用克隆的节点替换原始节点。
  • 使用 display: none 进行样式批量更新:先将元素设为 display: none(需要1次重排和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要1次重排和重绘)。这样一来,你就用两次重新渲染,取代了可能高达100次的重新渲染。
  • 善用 position:position 属性为 absolute 或 fixed 的元素,重排的开销会比较小,因为不用考虑它对其他元素的影响。
  • 将元素设置为不可见:只在必要的时候,才将元素的 display 属性为可见,因为不可见的元素不影响重排和重绘。另外,visibility : hidden 的元素只对重绘有影响,不影响重排。
  • 减少样式的更新频率:使用虚拟 DOM 脚本库,比如 React 等。
  • 调节 js 运行帧率:使用 window.requestAnimationFrame()、window.requestIdleCallback() 这两个方法调节重新渲染的频率。
  • 慎用 table 布局:table 的单元格具有非常好的自适应特性,但是同时代价也很高,能不用就不用。如果非要使用 table ,给 table 添加 table-layout: fixed 属性,这个属性的目的是让后面单元格的宽度由表头的宽度来决定——减少布局的计算量。

参看资料

  • 网页性能管理详解
  • 10 Ways to Minimize Reflows and Improve Performance
  • 无线性能优化:Composite
  • gpu-accelerated-compositing-in-chrome

你可能感兴趣的:(HTML,CSS)