【DOM】重绘与重排详解及在性能优化中的应用

本文详细解释了DOM树与渲染树的关系,阐述了如何通过减少重排和重绘来提升Web应用的性能,包括合并样式修改、减少DOM查询和使用绝对定位避免大规模重排等内容,特别提到了IE浏览器与hover伪类可能导致的问题。
摘要由CSDN通过智能技术生成

DOM树

表示页面结构

渲染树

表示DOM节点如何展示

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

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

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

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

重排何时发生

正如前文所提到的, 当页面布局和几何属性改变时就需要“重排”。下述情况中会发生重排。

  • 添加或删除可见的DOM元素。
  • 元素位置改变。
  • 元素尺寸改变(包括:夕|、边距、 内边距、 边框厚度、 宽度、 高度等属性改变)。内容改变, 例如: 文本改变或图片被另一个不同尺寸的图片替代。
  • 页面谊染器初始化。
  • 浏览器窗口尺寸改变。

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

渲染树变化的排队与刷新

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

  • 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 = 'lpx'  
el. style.borderRight = '2px '
el. style.padding = '5px'

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

够达到同样效果且效率更高的方式是: 合并所有的改变然后一次处理, 这样只会修改DOM一次。 使用cssText属性可以实现:

el.style.cssText += ' ;border-left: 1px' ;

另一个一次性修改样式的办法是修改 css的class名称, 而不是修改内联样式。 这种方法适合那些不依赖于运行逻辑和计算的情况。 改变 css 的class名称的方也更清晰, 更易于维护。它有助于保持你的脚本与免除显示性代码, 尽管它可能带来轻微的性能影响, 因为 改变类时需要检查级联样式。

var el= document.getElementByld('mydiv'); 
el.className ='active'

批量修改DOM

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

  1. 使元素脱离文档流。
  2. 对其应用多重改变。
  3. 把元素带回文档中。

该过程里会触发两次重排一一一第一步和第三步。 如果你忽略这两个步骤, 那么在第二步所产生的任何修改都会触发一次重排。

有三种基本方法可以使DOM脱离文档:

  • 隐藏元素, 应用修改, 重新显示。
  • 使用文档片断(docuement fragment)在当前DOM之外构建一个子树, 再把它拷贝回文档。
  • 将原始元素拷贝到一个脱离文档的节点中,改副本, 完成后再替换原始元素。

缓存布局信息

如前文所述,浏览器尝试通过队列化修改和批量执行的方式最小化重排次数。当你查询布局信息时,比如获取偏移量(offsets)、滚动位置(scroll values)或计算出的样式值(computedsytle values)时,浏览器为了返回最新值,会刷新队列并应用所有变更。最好的做法是尽量减少布局信息的获取次数,获取后把它赋值给局部变量,然后再操作局部变量。

考虑一个例子,把myElement元素沿对角线移动,每次移动一个像素,从100像素×100像素的位置开始,到500像素x500像素的位置结束。在timeout循环体中你可以使用下面的方法:

//低效的
myElement.style.left = 1 + myElement.offsetLeft +'px'
myElement.style.top = 1 + myElement.offsetTop +'px'
if (myElement.offsetLeft >= 500) { 
	stop()
}

这种方法效率低下,因为元素每次移动时都会查询偏移量,导致浏览器刷新渲染列队而不利于优化。一个更好的方法是,获取一次起始位置的值,然后将其赋值给一个变量,比如var current = myElement.offsetleft。然后,在动画循环中,直接使用current变量而不再用偏移量:

current++ 
myElement.style.left = current+'px'
myElement.style.top = current+'px'
if (current >= 500) { 
	stop()
}

让元素脱离动画流

用展开/折叠的方式来显示和隐藏部分页面是一种常见的交互模式。它通常包括展开区域的几何动画,井将页面其他部分推向下方。

一般来说,重排只影响渲染树中的一小部分,但也可能影响很大的部分,甚至整个渲染树。 浏览器所需要重排的次数越少,应用程序的响应速度就越快。因此当页面顶部的一个动画推移页面整个余下的部分时,会导致一次代价昂贵的大规模重排,让用户感到页面一顿一 顿的。渲染树中需要重新计算的节点越多,情况就会越糟。

使用以下步嘱可以避免页面中的大部分重排:

  1. 使用绝对位置定位页面上的动画元素, 将其脱离文档流。
  2. 让元素动起来。 当它扩大时, 会的时覆盖部分页面。 但只是页面一个区域的重绘过程, 不会产生重排并重绘页面的大部分内容。
  3. 当动画结束时恢复定位, 从而只会下移一次文档的其他元素。

IE和:hover

从IE 7开始,IE允许在任何元素(严格模式下)上使用:hover这个 css 伪选择器。 然而,如果你有大量元素使用了:hover, 那么会降低响应速度。 此问题在IE8中更为明显。

例如,如果你创建一个5列和500~1000行的表格,并使用tr:hover改变背景色来高亮显示鼠标所在的当前行, 当鼠标在表格上移动时, 性能会降低。 高亮过程会变慢, CPU使用率会提高到80%~90%。所以在元素很多时应避免使用这种效果,比如很大的表格或很长的列表。

  • 43
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值