`
lipeiu
  • 浏览: 6903 次
  • 性别: Icon_minigender_2
  • 来自: 河南
最近访客 更多访客>>
社区版块
存档分类
最新评论

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

阅读更多
  当浏览器下载完所有页面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)
      布局信息由这些属性和方法返回最新的数据,所以浏览器不得不运行渲染队列中待改变的项目并重新排版以返回正确的值。
      在改变风格的过程中,最好不要使用前面列出的那些属性。任何一个访问都将刷新渲染队列,即使你正在获取那些最近未发生改变的或者与最新的改变无关的布局信息。
      考虑下面这个例子,它改变同一个风格属性三次(这也许不是你在真正的代码中所见到的,不过它孤立地展示出一个重要话题):
      // 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 和风格改变合并到一个批次中一次性执行。
      考虑这个例子:
      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 元素进行多次修改时,你可以通过以下步骤减少重绘和重排版的次数:
      从文档流中摘除该元素
      对其应用多重改变
      将元素带回文档中
      此过程引发两次重排版——第一步引发一次,第三步引发一次。如果你忽略了这两个步骤,那么第二步
中每次改变都将引发一次重排版。
      有三种基本方法可以将DOM 从文档中摘除:
      隐藏元素,进行修改,然后再显示它。
      使用一个文档片断在已存DOM 之外创建一个子树,然后将它拷贝到文档中。
      将原始元素拷贝到一个脱离文档的节点中,修改副本,然后覆盖原始元素。
      为演示脱离文档操作,考虑这样一个链接列表,它必须被更多的信息所更新:
      <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();
      }

分享到:
评论
1 楼 shenlm203 2011-11-22  
写得很好啊

相关推荐

    嵌入式浏览器网页排版技术的研究与实现

    3. **渲染性能**:采用高效的渲染引擎,减少重绘和回流,提高页面滚动的流畅性。 4. **触摸交互**:优化触控操作,提供手势识别,以适应嵌入式设备的交互方式。 5. **兼容性**:处理各种浏览器兼容性问题,确保...

    鼠标滚动切换页面.rar

    4. **JavaScript 动画效果**:为了让滚动切换更平滑,通常会使用`requestAnimationFrame`来实现动画效果,确保在浏览器重绘之前更新页面状态,提升视觉体验。 5. **DOM 操作**:根据滚动位置,JavaScript需要动态地...

    css3实现响应式win8 metro风格页面扁平化设计又一次卷土重来

    - CSS3性能考虑:合理使用硬件加速,避免不必要的计算和重绘。 - 代码组织:保持CSS代码整洁,避免冗余,利用预处理器如Sass或Less提高效率。 总之,利用CSS3的特性,我们可以创建出既具有Win8 Metro风格的扁平化...

    仿微信滑动页面图标文字渐变

    例如,使用requestAnimationFrame而不是setTimeout或setInterval,因为它与浏览器的渲染周期同步,能避免不必要的重绘和回流。 通过以上技术的结合,可以实现一个类似微信滑动界面的图标文字渐变效果。在实际开发...

    主流浏览器内核概览.doc

    Presto的动态特性使其在响应脚本事件时可以快速重绘页面。尽管Presto性能优异,但由于其商业性质,除了Opera浏览器,其他采用Presto的软件相对较少,如NDS Browser。 除了这些主流内核,还有其他一些值得注意的内核...

    css设计彻底研究ppt教程

    1. **CSS优化**:合理组织CSS代码,避免选择器的复杂性,减少不必要的重绘和回流。 2. **CSS Sprites**:合并小图标为一个图像,通过CSS背景定位减少HTTP请求,提高页面加载速度。 3. **BEM命名方法**:Block ...

    Webkit介绍

    WebCore采用了增量重绘和回流机制,以提高页面更新的效率。 2. **JavaScript引擎(JavaScriptCore)**:处理JavaScript代码的解析、编译和执行。早期的JavaScriptCore称为“Nitro”,在Safari中首次引入了Just-In-...

    如何提高Dom访问速度

    5. 减少重绘和重排版操作:重绘和重排版是浏览器中最为昂贵的操作之一,因为它们需要重新计算布局并更新屏幕上元素的位置和外观。为了减少这些操作的发生,可以采用一些策略,例如先隐藏受影响的元素,处理完毕后再...

    jd首页html代码编写.zip

    6. **性能优化**:观察代码中是否应用了CSS的最佳实践,如避免使用内联样式,减少重绘和回流,以及有效利用CSS预处理器等。 7. **可访问性**:查看代码是否考虑了无障碍性(accessibility),如合理使用`&lt;alt&gt;`属性...

    WebKit技术内幕

    7. **性能优化**:如何通过WebKit的特性进行性能优化,包括DOM操作、样式计算、布局和重绘等方面的技巧。 8. **WebKit调试工具**:介绍WebKit内置的开发者工具,如Web Inspector,如何利用它们进行问题排查和性能...

    jquery做的类似报纸新闻

    9. **性能优化(Performance Optimization)**:为了保证页面加载速度和流畅性,可能需要对DOM操作进行优化,避免不必要的重绘和回流,或者利用延迟加载(Lazy Loading)技术只加载可视区域内的内容。 在压缩包文件...

    css实现表单Select样式美化

    10. **优化性能**:在大型表单中,大量的CSS选择器和JavaScript操作可能会对页面性能产生影响。因此,在编写样式时,应尽量减少选择器的复杂度,避免不必要的计算和重绘。 通过以上步骤,我们可以实现一个美观且...

    好的css个人设计体验

    10. **优化与性能**:在实现设计的同时,也要考虑CSS的性能,避免使用过于复杂的选择器,减少不必要的重绘和回流,以及利用CSS预处理器如Sass或Less提高代码可维护性。 通过这个项目,设计师不仅锻炼了CSS技术,也...

    C#打印系列---打印窗体中的文字信息.rar

    比如,避免不必要的重绘,合理使用Graphics缓存,以及考虑使用双缓冲技术减少闪烁。 9. **错误处理**: 在实现打印功能时,应考虑错误处理,如打印机未连接、纸张不足等情况。通过异常捕获,我们可以优雅地处理...

    HTML第四章-CSS样式

    10. CSS优化:通过减少重绘和回流,合理组织CSS代码,利用CSS缓存,以及使用CSS Sprites和数据URL等技术,可以提升网页性能。 这个章节的学习,不仅涵盖了CSS的基础知识,还涉及到了一些进阶技巧和现代Web开发的...

    CSS样式框架,非常有用

    5. **性能优化**:Inuit.css关注性能,通过最小化选择器复杂度和减少CSS重绘,提高页面加载速度。 6. **命名约定**:Inuit.css采用BEM(Block Element Modifier)命名方法,帮助开发者编写清晰、可读的CSS选择器,...

    CSS禅意花园-源代码

    - 通过减少CSS重绘和回流,优化选择器性能,合并和压缩CSS文件,以及使用Sprite图等方法提高页面加载速度。 8. **设计原则** - 禅意花园中的设计体现了设计原则,如对比、重复、对齐和接近性,以及色彩理论和排版...

Global site tag (gtag.js) - Google Analytics