你应该要知道的重绘与重排

2019-01-14 admin

前言

现代web框架大多都是数据驱动类的,比如 react, vue,所以开发者不需要直接接触 DOM,修改 data 便可以驱动界面更新。但是作为前端工程师,了解浏览器的重绘与重排还是很有必要的,可以帮助我们写出更好性能的 web 应用。

浏览器的渲染

  • CSS Tree: 浏览器将 CSS 解析成 CSSOM 的树形结构
  • DOM Tree:浏览器将 HTML 解析成树形的数据结构
  • Render Tree:将 DOM 与 CSSOM 合并成一个渲染树

有了渲染树(Render Tree),浏览器就知道网页中有哪些节点,以及各个节点与 CSS 的关系,从而知道每个节点的位置和几何属性,然后绘制页面。

重绘与重排

当 DOM 的变化影响了元素的几何属性(比如 width 和 height ),就会导致浏览器重新计算元素的几何属性,同样受到该元素影响的其他元素也会发生重新计算。此时,浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。这个过程被称为重排(也叫“回流”)(reflow),完成重排之后,浏览器会重新绘制受影响的部分到页面上,这个过程就是重绘(repaint)。

所以重排一定会引起重绘,而重绘不一定会引起重排,比如一个元素的改变并没有影响布局的改变(background-color的改变),在这种情况下,只会发生一个重绘(不需要重排)。

引起重排的因素

可以总结出,当元素的几何属性或页面布局发生改变就会引起重排,比如:

  • 对可见 DOM 元素的操作(添加,删除或顺序变化)
  • 元素位置发生改变
  • 元素的几何属性发生改变(比如:外边距、内边距、边框宽度以及内容改变引起的宽高的改变)
  • 页面首次渲染
  • 伪类样式激活(hover等)
  • 浏览器视口尺寸发生改变(滚动或缩放)

如何优化

重绘与重排都是代价昂贵的操作(因为每次重排都会产生计算消耗),它们会导致 web 应用的 UI 反应迟钝,所以开发者在编写应用程序的时候应当尽量减少这类过程的发生。

渲染树队列

因为过多的重绘与重排可能会导致应用的卡顿,所以浏览器会对这个有一个优化的过程。大多数浏览器会通过队列化来批量执行(比如把脚本对 DOM 的修改放入一个队列,在队列所有操作都结束后再进行一次绘制)。但是开发者有时可能不知不觉的强制刷新渲染队列来立即进行重排重绘,比如获取页面布局信息会导致渲染队列的强制刷新,以下属性或方法会立即触发页面绘制:

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

以上属性和方法都是要浏览器返回最新的布局信息,所以浏览器会立刻执行渲染队列中的“待处理变化”, 并触发重排重绘然后返回最新的值。所以在修改样式的过程中,应该尽量避免使用以上属性和方法。

减少重绘与重排

为了减少重绘重排的发生次数,开发者应该合并多次对 DOM 的修改和对样式的修改,然后一次性处理。

合并样式操作

比如:

var el = document.querySelector('div');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';

可以合并成:

var el = document.querySelector('div');
el.style.cssText = 'border-left: 1px; border-right: 1px; padding: 5px;'

批量修改DOM

使元素脱离文档流,再对其进行操作,然后再把元素带回文档中,这种办法可以有效减少重绘重排的次数。有三种基本办法可以使元素脱离文档流:

隐藏元素,应用修改,重新显示
var ul = document.querySelector('ul');
ul.style.display = 'none';
// code... 对ul进行DOM操作
ul.style.display = 'block';
使用文档片段(document fragment),构建一个空白文档进行 DOM 操作,然后再放回原文档中

var fragment = document.createDocumentFragment();
// code... 对fragment进行DOM操作
var ul = document.querySelector('ul');
ul.appendChild(fragment)
拷贝要修改的元素到一个脱离文档流的节点中,修改副本,然后再替换原始元素
var ul = document.querySelector('ul');
var cloneUl = ul.cloneNode(true);
// code... 对clone节点进行DOM操作
ul.parentNode.replaceChild(cloneUl, ul)

缓存布局信息

前面已经知道,获取页面布局信息,会导致浏览器强制刷新渲染队列。所以减少这些操作是非常有必要的,开发者可以将第一次获取到的页面信息缓存到局部变量中,然后再操作局部变量,比如下面的伪代码示例:

// 低效的
element.style.left = 1 + element.offsetLeft + 'px';
element.style.top = 1 + element.offsetTop + 'px';
if (element.offsetTop > 500) {
    stopAnimation();
}
// 高效的
var offsetLeft = element.offsetLeft;
var offsetTop = element.offsetTop;
offsetLeft++;
offsetTop++;
element.style.left = offsetLeft + 'px';
element.style.top = offsetTop + 'px';
if (offsetTop > 500) {
    stopAnimation();
}

总结

为了减少重绘重排带来的性能消耗,可以通过以下几点改善 web 应用:

  1. 批量修改 DOM 和样式
  2. “离线”操作 DOM 树,脱离文档流
  3. 缓存到局部变量,减少页面布局信息的访问次数

参考

高性能JavaScript

原文链接:https://segmentfault.com/a/1190000017882271

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-50950.html

文章标题:你应该要知道的重绘与重排

相关文章
css布局的各种FC简单介绍:BFC,IFC,GFC,FFC
什么是FC? Formatting Context,格式化上下文,指页面中一个渲染区域,拥有一套渲染规则,它决定了其子元素如何定位,以及与其他元素的相互关系和作用。 BFC 什么是BFC Block Formatting Context,块...
2018-05-17
国内外html5游戏引擎排行
一个好的HTML5游戏引擎,能够大大简化游戏的开发实现。 排名列表: Construct 2 ImpactJS EaselJS pixi.js Phaser GameMaker Three.js PlayCanvas Turbulenz ...
2015-11-12
从2014年的发展来展望JS的未来将会如何
<font face="寰�杞�闆呴粦, Arial, sans-serif ">2014骞达紝杞�浠惰�屼笟鍙戝睍杩呴€燂紝鍚勭�嶈��瑷€灞傚嚭涓嶇┓锛屼互婊¤冻鐢ㄦ埛涓嶆柇鍙樺寲鐨勯渶姹傘€傝繖浜涜��...
2015-11-12
12个你未必知道的CSS小知识
虽然CSS并不是一种很复杂的技术,但就算你是一个使用CSS多年的高手,仍然会有很多CSS用法/属性/属性值你从来没使用过,甚至从来没听说过。 1.CSS的color属性并非只能用于文本显示 对于CSS的color属性,相信所有Web开发人员...
2015-11-12
ajax为什么令人惊异?ajax的优缺点
使用Ajax的最大优点,就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变的信息。 Ajax不需要任何浏览器插件,但需要用户允许JavaScript在浏览器上执行。就像DHT...
2015-11-12
HTML5的5个不错的开发工具推荐
HTML5规范终于在今年正式定稿,对于从事多年HTML5开发的人员来说绝对是一个重大新闻。数字天堂董事长,DCloud CEO王安也发表了文章,从开发者和用户两个角度分析了HTML对两个人群的优势。其实,关于HTML5的开发工具,我们以往的...
2015-11-12
JavaScript教程:JS中的原型
Keith Peters 几年前发表的一篇博文,关于学习没有“new”的世界,其中解释了使用原型继承代替构造函数。两者都是纯粹的原型编码。 标准方法(The Standard Way) 一直以来,我们学习的在 JavaScript 里创建对...
2015-11-12
AJAX的浏览器支持
AJAX 的要点是 XMLHttpRequest 对象。 不同的浏览器创建 XMLHttpRequest 对象的方法是有差异的。 IE 浏览器使用 ActiveXObject,而其他的浏览器使用名为 XMLHttpRequest 的 Jav...
2015-11-12
Node.js学习(1)----HTTP服务器与客户端
Node.js 标准库提供了 http 模块,其中封装了一个高效的 HTTP 服务器和一个简易的HTTP 客户端。http.Server 是一个基于事件的 HTTP 服务器,它的核心由 Node.js 下层 C++部分实现,而接口由 Jav...
2015-11-12
typeof、instanceof和contructor的区别
typeof:以字符串的形式返回变量的原始类型,typeof在两种情况下会返回"undefined":一个变量没有被声明的时候,和一个变量的值是undefined的时候,注意,typeof null也会返回object,...
2015-11-12
回到顶部