页面重绘与重排版的性能影响

DOM树和渲染树

  当浏览器下载完所有页面HTML 标记,JavaScript,CSS,图片之后,它解析文件并创建两个内部数据结构:一棵DOM树表示页面结构,一棵渲染树表示DOM节点如何显示。
      渲染树中为每个需要显示的DOM 树节点存放至少一个节点(隐藏DOM 元素在渲染树中没有对应节点)。渲染树上的节点称为“框”或者“盒”,符合CSS 模型的定义,将页面元素看作一个具有填充、边距、边框和位置的盒。一旦DOM 树和渲染树构造完毕,浏览器就可以显示(绘制)页面上的元素了。

重排版

当DOM 改变影响到元素的几何属性(宽和高)——例如改变了边框宽度或在段落中添加文字,将发生一系列后续动作——浏览器需要重新计算元素的几何属性,而且其他元素的几何属性和位置也会因此改变受到影响。浏览器使渲染树上受到影响的部分失效,然后重构渲染树。这个过程被称作重排版。重排版完成时,浏览器在一个重绘进程中重新绘制屏幕上受影响的部分。
      不是所有的DOM 改变都会影响几何属性。例如,改变一个元素的背景颜色不会影响它的宽度或高度。在这种情况下,只需要重绘(不需要重排版),因为元素的布局没有改变。
      重绘和重排版是负担很重的操作,可能导致网页应用的用户界面失去相应。所以,十分有必要尽可能减少这类事情的发生。

发生重排版情况

正如前面所提到的,当布局和几何改变时需要重排版。在下述情况中会发生重排版:
      (1)添加或删除可见的DOM 元素
      (2)元素位置改变
      (3)元素尺寸改变(因为边距,填充,边框宽度,宽度,高度等属性改变)
      (4)内容改变,例如,文本改变或图片被另一个不同尺寸的所替代最初的页面渲染
      (5)浏览器窗口改变尺寸
      根据改变的性质,渲染树上或大或小的一部分需要重新计算。某些改变可导致重排版整个页面:例如,当一个滚动条出现时。因为计算量与每次重排版有关,大多数浏览器通过队列化修改和批量显示优化重排版过程。然而,你可能(经常不由自主地)强迫队列刷新并要求所有计划改变的部分立刻应用。获取布局信息的操作将导致刷新队列动作,这意味着使用了下面这些方法:
      offsetTop, offsetLeft, offsetWidth, offsetHeight
      scrollTop, scrollLeft, scrollWidth, scrollHeight
      clientTop, clientLeft, clientWidth, clientHeight
      getComputedStyle() (currentStyle in IE)(在IE 中此函数称为currentStyle)
      布局信息由这些属性和方法返回最新的数据,所以浏览器不得不运行渲染队列中待改变的项目并重新排版以返回正确的值。
      在改变风格的过程中,最好不要使用前面列出的那些属性。任何一个访问都将刷新渲染队列,即使你正在获取那些最近未发生改变的或者与最新的改变无关的布局信息。

重拍版实例分析

1)考虑下面这个例子,它改变同一个风格属性三次(这也许不是你在真正的代码中所见到的,不过它孤立地展示出一个重要话题):

     // setting and retrieving styles in succession
      var computed,
      tmp = ‘‘,
      bodystyle = document.body.style;
      if (document.body.currentStyle) { // IE, Opera
            computed = document.body.currentStyle;
      } else { // W3C
            computed = document.defaultView.getComputedStyle(document.body, ‘‘);
      }
      // inefficient way of modifying the same property
      // and retrieving style information right after
      bodystyle.color = ‘red‘;
      tmp = computed.backgroundColor;
      bodystyle.color = ‘white‘;
      tmp = computed.backgroundImage;
      bodystyle.color = ‘green‘;
      tmp = computed.backgroundAttachment;

在这个例子中,body 元素的前景色被改变了三次,每次改变之后,都导入computed 的风格。导入的属性backgroundColor, backgroundImage, 和backgroundAttachment 与颜色改变无关。然而,浏览器需要刷新渲染队列并重排版,因为computed 的风格被查询而引发。

比这个不讲效率的例子更好的方法是不要在布局信息改变时查询它。如果将查询computed 风格的代码搬到末尾,代码看起来将是这个样子:

      bodystyle.color = ‘red‘;
      bodystyle.color = ‘white‘;
      bodystyle.color = ‘green‘;
      tmp = computed.backgroundColor;
      tmp = computed.backgroundImage;
      tmp = computed.backgroundAttachment; 

在所有浏览器上,第二个例子将更快,重排版和重绘代价昂贵,所以,提高程序响应速度一个好策略是减少此类操作发生的机会。为减少发生次数,你应该将多个DOM 和风格改变合并到一个批次中一次性执行。

2)考虑这个例子:

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

这里改变了三个风格属性,每次改变都影响到元素的几何属性。在这个糟糕的例子中,它导致浏览器重排版了三次。大多数现代浏览器优化了这种情况只进行一次重排版,但是在老式浏览器中,或者同时有一个分离的同步进程(例如使用了一个定时器),效率将十分低下。如果其他代码在这段代码运行时查询布局信息,将导致三次重布局发生。而且,此代码访问DOM 四次,可以被优化。

一个达到同样效果而效率更高的方法是:将所有改变合并在一起执行,只修改DOM 一次。可通过使用
      cssText 属性实现:
      var el = document.getElementById(‘mydiv‘);
      el.style.cssText = ‘border-left: 1px; border-right: 2px; padding: 5px;‘;
      这个例子中的代码修改cssText 属性,覆盖已存在的风格信息。如果你打算保持当前的风格,你可以将它附加在cssText 字符串的后面。
      el.style.cssText += ‘; border-left: 1px;‘;
另一个一次性改变风格的办法是修改CSS 的类名称,而不是修改内联风格代码。这种方法适用于那些风格不依赖于运行逻辑,不需要计算的情况。改变CSS 类名称更清晰,更易于维护;它有助于保持脚本免除显示代码,虽然它可能带来轻微的性能冲击,因为改变类时需要检查级联表。
      var el = document.getElementById(‘mydiv‘);
      el.className = ‘active‘;

批量修改DOM如何减少重排版次数

当你需要对DOM 元素进行多次修改时,你可以通过以下步骤减少重绘和重排版的次数:
      1.从文档流中摘除该元素
      2.对其应用多重改变
      3.将元素带回文档中
      此过程引发两次重排版——第一步引发一次,第三步引发一次。如果你忽略了这两个步骤,那么第二步
中每次改变都将引发一次重排版。
      有三种基本方法可以将DOM 从文档中摘除:
      1) 隐藏元素,进行修改,然后再显示它。
      2) 使用一个文档片断在已存DOM 之外创建一个子树,然后将它拷贝到文档中。
      3) 将原始元素拷贝到一个脱离文档的节点中,修改副本,然后覆盖原始元素。
      为演示脱离文档操作,考虑这样一个链接列表,它必须被更多的信息所更新:

 <ul id="mylist">
       <li><a href="http://phpied.com">Stoyan</a></li>
       <li><a href="http://julienlecomte.com">Julien</a></li>
 </ul>

假设附加数据已经存储在一个对象中了,需要插入到这个列表中。这些数据定义如下:

var data = [
            {
                  "name": "Nicholas",
                  "url": "http://nczonline.net"
            },
            {
                  "name": "Ross",
                  "url": "http://techfoolery.com"
            }
      ];

下面是一个通用的函数,用于将新数据更新到指定节点中:

  function appendDataToElement(appendToElement, data) {
            var a, li;
            for (var i = 0, max = data.length; i < max; i++) {
                  a = document.createElement(‘a‘);
                  a.href = data[i].url;
                  a.appendChild(document.createTextNode(data[i].name));
                  li = document.createElement(‘li‘);
                  li.appendChild(a);
                  appendToElement.appendChild(li);
            }
      };

将数据更新到列表而不管重排版问题,最明显的方法如下:
      var ul = document.getElementById(‘mylist‘);
      appendDataToElement(ul, data);
      使用这个方法,然而,data 队列上的每个新条目追加到DOM 树都会导致重排版。

  如前面所讨论过的,减少重排版的一个方法是通过改变display 属性,临时从文档上移除<ul>元素然后再恢复它。

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

      另一种减少重排版次数的方法是:在文档之外创建并更新一个文档片断,然后将它附加在原始列表上。文档片断是一个轻量级的document 对象,它被设计专用于更新、移动节点之类的任务。文档片断一个便利的语法特性是当你向节点附加一个片断时,实际添加的是文档片断的子节点群,而不是片断自己。下面的例子减少一行代码,只引发一次重排版,只触发“存在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 操作和重排版。唯一潜在的缺点是,当前文档片断还没有得到充分利用,开发者可能不熟悉此技术。

浏览器通过队列化修改和批量运行的方法,尽量减少重排版次数。当你查询布局信息如偏移量、滚动条位置,或风格属性时,浏览器刷队列并执行所有修改操作,以返回最新的数值。最好是尽量减少对布局信息的查询次数,查询时将它赋给局部变量,并用局部变量参与计算。
      考虑一个例子,将元素myElement 向右下方向平移,每次一个像素,起始于100×100 位置,结束于500×500位置,在timeout 循环体中你可以使用:

      // inefficient
      myElement.style.left = 1 + myElement.offsetLeft + ‘px‘;
      myElement.style.top = 1 + myElement.offsetTop + ‘px‘;
      if (myElement.offsetLeft >= 500) {
            stopAnimation();
      }

这样做很没效率,因为每次元素移动,代码查询偏移量,导致浏览器刷新渲染队列,并没有从优化中获益。另一个办法只需要获得起始位置值一次,将它存入局部变量中var current = myElement.offsetLeft;。然后,在动画循环中,使用current 变量而不再查询偏移量:

      current++
      myElement.style.left = current + ‘px‘;
      myElement.style.top = current + ‘px‘;
      if (current >= 500) {
            stopAnimation();
      }

转载自:http://exception.thinksaas.cn/0/16/16591.html

时间: 2024-08-25 12:55:50

页面重绘与重排版的性能影响的相关文章

Repaints and Reflows 重绘和重排版

当浏览器下载完所有页面HTML标记,JavaScript,CSS,图片之后,它解析文件并创建两个内部数据 一棵DOM树 表示页面结构 Normal 0 7.8 磅 0 2 false false false EN-US ZH-CN X-NONE /* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-st

浏览器渲染页面的过程,以及重绘与重排

浏览器的渲染过程 1,浏览器解析html源码,然后创建一个 DOM树.在DOM树中,每一个HTML标签都有一个对应的节点,并且每一个文本也都会有一个对应的文本节点.DOM树的根节点就是 documentElement,对应的是html标签. 2,浏览器解析CSS代码,计算出最终的样式数据.对CSS代码中非法的语法她会直接忽略掉.解析CSS的时候会按照如下顺序来定义优先级:浏览器默认设置,用户设置,外链样式,内联样式,html中的style. 3,构建出DOM树,并且计算出样式数据后,下一步就是构

闲聊前端性能----防抖、节流、重绘与回流。

在最近,小米9卖的特别火,在官方抢购的时候基本都是一点既空.这不禁让我想到了,官网是怎样控制顾客不停点击发起请求而不导致官网崩溃的呢?这由此引出了前端性能的优化中的----防抖和节流.在闲聊完后你就会发现有些时候在抢购商品的时候,你用鼠标在几秒钟不停的按了数十次,或许它仅仅是发送了你第一次点击抢购的那个请求.所以说 抢购时间内的第一次点击尤为关键! 下面来介绍一下什么是防抖!        防抖:任务频繁触发的情况下,只有任务触发的间隔超过制定的时间间隔的时候,任务才会被执行. 下面引用一下知乎

Web前端性能优化-重绘与回流

1.什么是重绘与回流 Render tree 的重新构建就叫回流.当布局和几何属性改变时就需要回流,鼠标移动到图片 图片变大 也会触发回流.回流 能避免就避免 Render tree 改变外观.风格 而不影响布局的时候,就叫重绘 重绘与回流的关系:回流会引起重绘 重绘不一定会引起回流 2.避免重绘回流的两种方法 什么会引起回流和重绘 触发页面布局从而触发重绘的: (1) 盒子模型属性 (2) 定位属性和浮动 (3) 改变节点内部文字结构 如下图: 只触发重绘不触发回流的属性: 优化方法: (1)

页面重绘重排

1.重绘(Repaint) 重绘是一个元素外观的改变所触发的浏览器行为,例如改变outline.背景色等属性.浏览器会根据元素的新属性重新绘制, 使元素呈现新的外观.重绘不会带来重新布局,所以并不一定伴随重排. 2.重排(Reflow) 渲染对象在创建完成并添加到渲染树时,并不包含位置和大小信息.计算这些值的过程称为布局或重排 "重绘"不一定需要"重排",比如改变某个网页元素的颜色,就只会触发"重绘",不会触发"重排",因为布

浏览器的重绘与重排

在项目的交互或视觉评审中,前端同学常常会对一些交互效果质疑,提出这样做不好那样做不好.主要原因是这些效果通常会产生一系列的浏览器重绘(redraw)和重排(reflow),需要付出高昂的性能代价.那么,什么是浏览器的重绘和重排呢?二者何时发生以及如何权衡?如何在具体的开发过程中将重绘和重排引发的性能问题考虑进去?本文期待可以部分解释以上三个问题. 浏览器从下载文档到显示页面的过程是个复杂的过程,这里包含了重绘和重排.各家浏览器引擎的工作原理略有差别,但也有一定规则.简单讲,通常在文档初次加载时,

浏览器的重绘repaints与重排reflows深入分析

重绘是一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观,接下来将详细介绍,需要了解的朋友可以参考下: 在项目的交互或视觉评审中,前端同学常常会对一些交互效果质疑,提出这样做不好那样做不好.主要原因是这些效果通常会产生一系列的浏览器重绘和重排,需要付出高昂的性能代价.那么,什么是浏览器的重绘和重排呢?二者何时发生以及如何权衡?如何在具体的开发过程中将重绘和重排引发的性能问题考虑进去?本文期待可以部分解释以上三个问题. 浏览器从下载文档到显示页面的过程是个复

浏览器的重绘(repaints)与重排(reflows)

转:http://www.css88.com/archives/4991#more-4991 在项目的交互或视觉评审中,前端同学常常会对一些交互效果质疑,提出这样做不好那样做不好.主要原因是这些效果通常会产生一系列的浏览器重绘和重排,需要 付出高昂的性能代价.那么,什么是浏览器的重绘和重排呢?二者何时发生以及如何权衡?如何在具体的开发过程中将重绘和重排引发的性能问题考虑进去?本文期 待可以部分解释以上三个问题. 浏览器从下载文档到显示页面的过程是个复杂的过程,这里包含了重绘和重排.各家浏览器引擎

【REACT NATIVE 系列教程之六】重写SHOULDCOMPONENTUPDATE指定组件是否进行重绘

本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/react-native/2252.html 前几天,Himi 写练手项目时,遇到子更新父state中的某一个属性值,且对父进行重绘时,父包含的所有子组件都进行重绘 – -- 非常尴尬. 查阅了RN文档,终于在生命周期篇看到了想要的答案. 仔细看过RN关于生命周期篇的童鞋应该知道,就是它:shouldComponentUpdate 官方解释此函