通过了解渲染过程来提高页面性能

本文概述

最近,Web开发者越来越关注页面性能的提高,通过对渲染过程的学习,我们可以更好地理解这一课题。

本文前半部分介绍渲染过程,后半部分以此内容为基础,介绍如何在JavaScript脚本代码、CSS样式代码中优化页面性能。

渲染的基础知识

什么是渲染引擎?

渲染引擎是一种对HTML文档进行解析并将其显示在页面上的工具,在浏览器中担任重要任务。目前,Chrome、Safari、Opera浏览器中使用WebKit引擎、Firefox浏览器中使用Gecko引擎、IE浏览器中使用Trident引擎。

2013年4月3日,Google宣布在Chrome浏览器中使用新型开源渲染引擎Blink。Opera的Bruce Lawson也在官方博客中表示计划改用Blink引擎。同日,Mozilla也在Github中公布与韩国三星共同开发的Android系统与ARM系统用渲染引擎Servo。

虽然各渲染引擎之间肯定存在着区别,但是当用户在地址栏中输入URL地址,开始加载页面时,各渲染引擎都开始实现如下所示的渲染处理。

渲染处理

  1. 渲染引擎首先解析HTML文档,构建DOM树。同时根据外部CSS文件或页面中的style元素中的内容解析样式信息。
  2. 根据DOM树与样式信息构建渲染树。
  3. 当渲染树被构建完毕时,开始布局过程,根据渲染树中的位置信息在屏幕中布局元素(即将元素安排在指定位置)。
  4. 接下来开始绘制过程,将渲染树中的可视化信息绘制在屏幕中。

为了让用户尽快看见一部分内容,渲染引擎会尽快将一部分内容显示在页面上。因此在所有HTML文档被解析完毕之前先开始执行布局处理与绘制处理,在读入新的页面信息时,再次构件渲染树并开始渲染处理与绘制处理。

也就是说在加载过程中,在页面还未加载完毕,就已经开始实现布局处理、渲染处理,即一系列样式信息的更新,由JavaScript脚本代码所触发的动态处理,以及由用户操作所触发的处理等等。这些处理都对页面加载速度产生较大影响。

布局与绘制的区别

  • 绘制:当color、background-color、visiblity、outline等与视觉相关的样式属性值被更新时触发绘制过程。在绘制过程中由于要重计算元素的视觉信息,所以会降低页面加载性能。在WebKit引擎中把绘制过程称为Painting过程,在Gecko引擎中把绘制过程称为Repaint过程。
  • 布局:当窗口尺寸被修改(resize),发生滚动操作,或position、display、width、height等与元素位置相关的样式属性值被更新时触发布局过程。在布局过程中由于要计算所有元素的位置信息,更加会降低页面加载性能。在WebKit引擎中把布局过程称为Layout过程,在Gecko引擎中把绘制过程称为Reflow过程。

浏览器将在页面信息发生改变时将把对页面性能的影响度降低为最小程度。

例如在把visiblity样式属性值指定为hidden而隐藏元素时触发绘制过程,但是在把display样式属性值指定为none而隐藏元素时同时触发布局过程与绘制过程。也就是说由于display样式属性值与元素位置信息相关,所以对页面性能产生较大影响。

引起布局过程与绘制过程的原因

  • 元素的追加、修改与删除
  • 动画
  • 样式修改
  • 元素的class属性值修改
  • hover伪类选择器所触发的元素状态改变
  • 由用户在input元素中的输入而引起的文字节点改变
  • 使用offsetWidth、offsetHeight或getComputedStyle取得样式属性值
  • 文字字体的改变
  • 窗口尺寸的改变(resize)
  • 元素透明度的改变
  • 页面或元素内的滚动

根据渲染引擎的不同,页面信息发生改变时所触发的过程也会有所区别。在部分WebKit引擎中不触发布局过程,只触发绘制过程。在部分渲染引擎中更容易触发布局过程,页面上发生任何信息改变都会对页面性能产生较大影响。

页面加载时需要耗费一些时间,在这个过程中所触发的动画操作或页面缩放操作都会引起布局过程或绘制过程,从而影响页面加载性能。

在智能手机中用户对页面上的操作更加频繁,所以以上知识点尤其重要。

由于窗口尺寸的改变,页面滚动或缩放都会引起布局过程或绘制过程,所以虽然不可能完全避免这些操作,但是可以通过减少布局过程或绘制过程的触发次数来降低其对页面性能产生的影响,从而提高页面性能。

优化JavaScript代码

通过DocumentFragment减少DOM操作时引起的布局次数

在DOM中执行元素增加,文字修改、元素属性值或样式属性值修改等修改DOM树的操作时都会触发布局过程。

在同时执行多个DOM操作时,如果代码书写不合理的话,会让每个DOM操作都触发一次布局过程,从而严重影响页面性能。

因此,在这种场合下,我们应该使用DocumentFragment,汇总多个DOM操作,使其只触发一次布局过程。

DocumentFragment是一个专用于构建元素节点的类似于Document的JavaScript对象,通过该对象的使用,可以降低DOM操作对页面性能产生的影响。

var fragment = document.createDocumentFragment();
var elem, contents;
for( var i = 0; i < textlist.length; i++ ) {
    elem = document.createElement('p');
    contents = document.createTextNode(textlist[i]);
    elem.appendChild(contents);
    fragment.appendChild(elem); //这时不触发布局过程
}
document.body.appendChild(fragment); //只触发一次布局过程

通过复制减少DOM操作时引起的布局次数

可以通过复制操作来复制元素,然后对被复制出来的元素进行修改,在修改完毕后使用被修改后的元素替换回原来的被复制元素,从而使得布局过程只被触发一次。

但是如果使用复制的方法,由于并不直接引用被复制的元素,因此在被复制的元素中并不能体现DOM操作后的结果,另外对于被复制的元素的事件处理函数也不能应用到复制出来的元素中,因此不适用于表单元素。

var original = document.getElementById('container');
var clone = original.cloneNode(true);
clone.setAttribute('width','50%');
var elem, contents;
for( var i = 0; i < textlist.length; i++ ) {
    elem = document.createElement('p');
    contents = document.createTextNode(textlist[i]);
    elem.appendChild(contents);
    cloned.appendChild(elem);
}
original.parentNode.replaceChild(cloned,original);

设置动画帧率在60FPS以内

动画帧率是指1秒中内所渲染的动画帧数,其单位为FPS(Frame Per Second)。浏览器中的刷新率一般为60Hz,在部分移动设备用浏览器中的刷新率低于60Hz。

虽然动画帧率(FPS值)越高,动画的视觉效果越流畅,但是也会因此增加内存负载。如果动画帧率超过浏览器的刷新率,动画的视觉效果也会变卡,甚至引起浏览器崩溃。为了防止这种情况,需要将动画帧率设置为低于60FPS,即在16毫秒内安排一个动画帧。

但是由于窗口尺寸的改变,页面滚动与缩放等原因,在使用setTimeout()方法与setInterval()方法执行动画的时候,并不能确保动画帧率低于60FPS。

在这种场合下可以使用Chrome Developer Tool工具的时间轴面板中的动画帧率功能来确认实时动画帧率,在智能手机中可以在Remote Debugging中使用模拟器来调整动画的执行间隔。

使用requestAnimationFrame()方法防止由动画引起的内存泄露

如果使用requestAnimationFrame()方法执行动画,可以防止浏览器中的内存泄露,同时也可以在当前浏览器标签被切换到其他浏览器标签时,控制动画不被执行。

因此,在使用canvas动画、WebGL动画或SVG动画等JavaScript动画时,requestAnimationFrame()方法所使用的内存消耗量要远低于setTimeout()方法或setInterval()方法所使用的内存消耗量。

但是,与setTimeout()方法或setInterval()方法一样,requestAnimationFrame()方法也不能完全确保动画帧率低于60FPS,同样需要使用Chrome Developer Tool工具或智能手机中的模拟器来进行相关调整。

window.requestAnimFrame = (function(){
    return  window.requestAnimationFrame       ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame    ||
            function( callback ){
                window.setTimeout(callback, 1000 / 60);
            };
})();

使用class汇总多个样式修改

在JavaScript脚本代码中,由于修改元素样式时会触发布局过程或绘制过程,所以每次修改元素的style样式属性值时都会触发布局过程或绘制过程。

//不好的示例
element.style.color = 'red'; //触发绘制过程
element.style.height = '100px'; //触发布局过程
element.style.backgroundColor = 'white'; //触发绘制过程

这时,可以使用元素的className属性值将所有样式属性值修改的绘制过程或布局过程的次数降低为1次。

.active {
    color: red;
    height: 100px;
    background-color: white;
}
 
element.className = 'active';

另外,可以使用cssText属性来降低style属性值修改所触发的绘制过程或布局过程的次数。这种方法适用于在动画等场合中动态修改样式属性值,但是可以事先指定修改后的样式属性值的场合。

element.style.cssText +=
    ';' +'color: ' + newColor + ';' +
    'height: ' + newHeight + ';' +
    'background-color: ' + newBack + ';';

在JavaScript中注意不要强制触发布局过程

由于在浏览器中期望在一次布局过程中完成所有位置信息的指定,所以具有缓存多个位置信息,并在所有位置信息都被获取完毕之后批处理执行这些位置信息指定的功能。

但是,如果使用需要计算元素当前位置或样式属性值的属性值或方法时,将强制触发一次布局过程。这些属性或方法如下所示。

offsetTop, offsetLeft, offsetWidth, offsetHeight

scrollTop, scrollLeft, scrollWidth, scrollHeight

clientTop, clientLeft, clientWidth, clientHeight

scrollBy(), scrollTo(), scrollX, scrollY

getComputedStyle()

height, width

例如,在循环处理中获取并修改元素当前位置信息的处理将对页面性能产生一定程度的影响。

这时,我们可以将每次获取的位置信息缓存在本地变量中,从而降低每一次处理所需耗费的时间。

//不好的示例
for(big; loop; here) {
    el.style.left = el.offsetLeft + 10 + 'px';
    el.style.top = el.offsetTop + 10 + 'px';
}
//正确的示例
var left = el.offsetLeft,
    top  = el.offsetTop
    esty = el.style;
for(big; loop; here) {
    left += 10;
    top += 10;
    esty.left = left + 'px';
    esty.top = top + 'px';
}

理解本地变量与全局变量对性能产生的影响

JavaScript内的变量作用范围是指该变量可以被利用的场所。本地变量可以被利用在函数内部,全局变量可以被整个脚本程序中,可能会对页面性能产生一定影响。

为什么说全局变量可能会对页面性能产生一定影响?如果在本地变量作用范围内引用全局变量,脚本程序将在整个脚本程序内部遍历寻找该变量的所有声明。

因此,我们应该尽量避免使用全局变量,多使用本地变量。

//不好的示例
var i, n, sum; //全局变量
function average(players) {
    sum = 0;
    for (i = 0, n = players.length; i < n; i++) {
        sum += score(players[i]);
    }
  return sum / n;
}
//正确的示例
function average(players) {
    var i, n, sum; //本地变量
    sum = 0;
    for (i = 0, n = players.length; i < n; i++) {
        sum += score(players[i]);
    }
    return sum / n;
}

优化CSS样式代码

使用position:fixed固定布局对性能产生负面影响

虽然可以使用position:fixed、background-attachment:fixed来固定页面或元素背景,这些样式属性将引起布局过程的重复触发,从而对页面性能产生负面影响。

对执行动画的元素使用position:absolute可以缩小布局范围

在通过CSS 3样式代码或JavaScript脚本代码执行动画时需要在每个动画帧内计算动画元素位置,然后触发布局过程。

这时如果指定position:absolute,可以将布局范围限定为动画元素,减少对其他元素进行布局时所产生的性能影响。

在CSS标准中,position的样式属性值absolute与fixed将针对父元素来计算元素位置,而relative将针对整个页面来计算元素位置。因此relative将使布局范围扩大为页面全体,从而对页面性能产生较大的负面影响。

overflow:scroll所引起的滚动将对页面性能产生较大影响

如果对页面进行滚动操作,将触发布局过程。从性能的角度上来说最好是使用overflow:visible,如果必须使用overflow:scroll,至少也要使用position:absolute来缩小布局范围。

使得布局过程或绘制过程消耗较多资源成本的样式属性

  • @font-face
  • animation
  • transition
  • box-shadow
  • border-radius
  • gradient
  • opacity
  • background-size
  • text-align
posted @ 2014-01-20 18:23  sexy_girl  阅读(1333)  评论(2编辑  收藏  举报