浏览器的重排和重绘

什么是重排和重绘

浏览器从下载文件到显示页面是一个很复杂的过程。浏览器下载完页面中的所有组件 – HTML标记、JavaScript、CSS、图片 – 之后会解析并生成两个内部数据结构:

  • DOM 树——表示页面结构
  • 渲染树——表示 DOM 节点在页面中如何显示(宽高、位置等)

        DOM 树中的每一个需要显示的节点在渲染树中至少存在一个对应的节点(隐藏的 DOM 元素在渲染树中没有对应的节点)。渲染树中的节点被称为 帧(frames) 或 盒(boxes),符合 CSS 模型的定义,理解页面元素为一个具有内边距(padding)、外边距(margin)、边框(border)和位置(position)的盒子。一旦 DOM 树和渲染树构建完成,浏览器就开始显示(绘制 paint)页面元素。

        当 DOM 的变化影响了元素的几何属性(宽和高) – 比如改变边框宽度或给段落增加文字,导致行数增加 – 浏览器需要重新计算元素的几何属性,同样其他元素的几何属性和位置也会因此受到影响。浏览器会使渲染树中受到影响的部分失效,并重新构建渲染树。这个过程称为 重排(reflow)。完成重排后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为 重绘(repaint)。

        并不是所有的 DOM 变化都会影响几何属性。例如,改变元素的背景色不会影响宽和高,这种情况下,只会发生一次重绘(不需要重排),因为元素的布局并没有改变。

        重排一定会引起浏览器的重绘,而重绘则不一定伴随重排。

        重绘和重排操作都是代价昂贵的操作,它会导致 WEB 应用程序的 UI 反应迟钝。所以,应当尽可能减少这类过程的发生。

触发重排的情况

当页面布局的几何属性改变时就需要 重排。下述情况会导致重排:

  • 添加或删除可见的 DOM 元素
  • 元素位置改变
  • 元素尺寸改变(包括:外边距、内边距、边框厚度、宽度、高度等属性改变)
  • 内容改变。例如:文本改变或图片被另一个不同尺寸的图片代替
  • 页面渲染器初始化
  • 浏览器窗口尺寸改变
  • 获取会导致渲染队列刷新的属性(详细介绍如下)

        根据改变的范围和程度,渲染树中相应部分也需要重新计算。当滚动条出现时,会触发整个页面的重排。

渲染树变化的排队与刷新

  由于每次重排都会产生计算消耗,大多数浏览器通过队列化修改并批量执行来优化重排过程。
然而,你可能会(经常不知不觉)强制刷新队列并要求计划任务立刻执行。获取布局信息的操作会导致队列刷新,比如使用以下属性:

  • offsetTop , offsetLeft , offsetWidth , offsetHeight
  • scrollTop , scrollLeft , scrollWidth , scrollHeight
  • clientTop , clientLeft , clientWidth , clientHeight
  • getComputedStyle() ( currentStyle in IE )

        当获取以上的属性和方法时,浏览器为了获取最新的布局信息,不得不执行渲染队列中的 “待处理变化” ,并触发重排以返回正确的值。

最小化重绘和重排

        重绘和重排的代价非常昂贵,因此一个好的提高程序响应速度的策略就是减少此类操作的发生。

  • 合并多次对样式属性的操作

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

        考虑下面的例子:

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

示例中有三个样式属性被改变,每一个都会影响元素的几何结构。最糟糕的情况下,会导致浏览器触发三次重排。大部分现代浏览器为此做了优化,只会触发一次重排,但是在旧的浏览器中或者有一个分离的异步处理过程时(比如使用计时器),仍然效率低下。如果在上面代码执行时,有其他代码请求布局信息,这会导致三次重排。而且,这段代码四次访问 DOM ,可以被优化。

        上面的代码执行效率更高的方式是:将多次改变样式属性的操作合并为一次操作,这样只会修改 DOM 一次。如:

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

另一个一次性修改样式的办法是修改 CSS 的 class 名称,而不是修改内联样式。这种方法适用于那些不依赖于运行逻辑和计算的情况。

var el = document.getElementById('mydiv');el.className = 'active';
  • 批量修改 DOM

当需要对 DOM 元素进行一系列操作时,可以通过以下步骤来减少重绘和重排的次数:

  1. 使元素脱离文档流
  2. 操作元素
  3. 操作完成后,将元素带回文档中

这样,只有在第一步和第三步会触发两次重排。有三种方式可以实现上面的步骤:

一、隐藏元素(display: none;),操作元素,重新显示

var ul = document.getElementById('mylist');
ul.style.display = 'none';
appendDataToElement(ul, data);
ul.style.display = 'block';

二、使用文档片段(document fragment)在当前 DOM 之外构建一个子树,再把它拷贝回文档

var fragment = document.createDocumentFragment();
appendDataToElement(fragment, data);
document.getElementById('mylist').appendChild(fragment);

三、将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后再替换原始元素

var old = document.getElementById('mylist');
var clone = old.cloneNode(true);
appendDataToElement(clone, data);
old.parentNode.replaceChild(clone, old);

        推荐尽可能的使用文档片段(第二个方案),因为它们所产生的 DOM 遍历和重排次数最少。唯一潜在的问题是文档片段未被充分利用,很多人可能并不熟悉这项技术。

  • 缓存布局信息

浏览器获取元素的 offsetLeft 等属性值时,会导致重排。最好的做法是,将需要获取的布局信息的属性值,赋值给变量,然后再操作变量。

  • 定位

将需要多次重排的元素,position 属性设置为 absolute 或 fixed,这样元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值