前端开发者都要的理解网页渲染

今天被人问到我一些关于网页渲染的注意事项,一时竟然不知道怎么回答,下午有时间正好认认真真来整理下。
虽然网上已经有很多文章提到过这个主题了,但大部分信息都是零碎的片段。我相信这篇文章对新手会很有用,并且对想刷新和巩固他们已经了解的东西的高手也同样适用。

渲染应该从最开始当页面布局被定义时就进行优化,样式和脚本在页面渲染中扮演着非常重要的角色。专业人员知道一些技巧以避免一些性能问题。

浏览器是怎样渲染一个页面的?

我们从浏览器渲染页面的大概过程开始说起:

由从服务器接收到的 HTML 形成 DOM(文档对象模型)。
样式被加载和解析,形成 CSSOM(CSS 对象模型)。
紧接着 DOM 和 CSSOM 创建了一个渲染树,这个渲染树是一些被渲染对象的集合( Webkit 分别叫它们”renderer”和”render object”,而在Gecko 引擎中叫”frame”)。除了不可见的元素(比如 head 标签和一些有 display:none 属性的元素),渲染树映射了 DOM 的结构。在渲染树中,每一个文本字符串都被当做一个独立的 renderer。每个渲染对象都包含了与之对应的计算过样式的DOM 对象(或者一个文本块)。换句话说,渲染树描述了 DOM 的直观的表现形式。
对每个渲染元素来说,它的坐标是经过计算的,这被叫做“布局(layout)”。浏览器使用一种只需要一次处理的“流方法”来布局所有元素(tables需要多次处理)。
最后,将布局显示在浏览器窗口中,这个过程叫做“绘制(painting)”。

重绘

当在页面上修改了一些不需要改变定位的样式的时候(比如background-color
,border-color
,visibility
),浏览器只会将新的样式重新绘制给元素(这就叫一次“重绘”或者“重新定义样式”)。

重排

当页面上的改变影响了文档内容、结构或者元素定位时,就会发生重排(或称“重新布局”)。重排通常由以下改变触发:
DOM 操作(如元素增、删、改或者改变元素顺序)。
内容的改变,包括 Form 表单中文字的变化。
计算或改变 CSS 属性。
增加或删除一个样式表。
改变”class”属性。
浏览器窗口的操作(改变大小、滚动窗口)。
激活伪类(如:hover状态)。

浏览器如何优化渲染?

浏览器尽最大努力####限制重排
举个例子,一个 position 为 absolue 或 fixed 的元素的大小变化只影响它自身和子孙元素,而对一个 position 为 static 的元素做同样的操作就会引起所有它后面元素的重排。
另一个优化就是当运行一段Jjavascript 代码的时候,浏览器会将一些修改缓存起来,然后当代码执行的时候,一次性的将这些修改执行。举例来说,这段代码会触发一次重绘和一次重排:

JavaScript

; html-script: false ]var $body = $('body');
$body.css('padding', '1px'); // 重排, 重绘
$body.css('color', 'red'); // 重绘
$body.css('margin', '2px'); // 重排, 重绘
// 实际上只有一次重排和重绘被执行。

如上面所说,访问一个元素的属性会进行一次强制重排。如果我们给上面的代码加上一行读取元素属性的代码,这个情况就会出现:

JavaScript

; html-script: false ]var $body = $('body');
$body.css('padding', '1px');
$body.css('padding'); // 这里读取了一次元素的属性,一次强制重排就会发生。
$body.css('color', 'red');
$body.css('margin', '2px');

上面这段代码的结果就是,进行了两次重排。因此,为了提高性能,你应该讲读取元素属性的代码组织在一起。
有一种情况是必须触发一次强制重排的。例如:给元素改变同一个属性两次(比如margin-left
),一开始设置100px,没有动画,然后通过动画的形式将值改为50px。当然,我在这里会讲更多的细节。
我们从一个有transition的CSS class开始:

; html-script: false ].has-transition {
   -webkit-transition: margin-left 1s ease-out;
      -moz-transition: margin-left 1s ease-out;
        -o-transition: margin-left 1s ease-out;
           transition: margin-left 1s ease-out;
}

然后进行实现:

JavaScript

; html-script: false ]//我们的元素默认有"has-transition"属性
var $targetElem = $('#targetElemId');
 
//删除包含transition的class
$targetElem.removeClass('has-transition');
 
// 当包含transition的class已经没了的时候,改变元素属性
$targetElem.css('margin-left', 100);
 
// 再将包含transition的class添加回来
$targetElem.addClass('has-transition');
 
// 改变元素属性
$targetElem.css('margin-left', 50);

上面的实现没有按照期望的运行。所有的修改都被浏览器缓存了,只在上面这段代码的最后才会执行。我们需要的是一次强制重排,我们可以通过进行以下修改来实现:

JavaScript


; html-script: false ]//删除包含transition的class
$(this).removeClass('has-transition');
 
// 改变元素属性
$(this).css('margin-left', 100);
 
//触发一次强制重排,从而使变化了的class或属性能够立即执行。
$(this)[0].offsetHeight; // offsetHeight仅仅是个例子,其他的属性也可以奏效。
 
// 再将包含transition的class添加回来
$(this).addClass('has-transition');
 
// 改变元素属性
$(this).css('margin-left', 50);

现在这段代码如我们所期望的运行了。

实际的优化建议

汇总了一些有用的信息,我建议以下几点:

创建合法的 HTML 和 CSS ,别忘了制定文件编码,Style 应该写在 head 标签中,script 标签应该加载 body 标签结束的位置。
要改变元素的样式,修改“class”属性是最高效的方式之一。你要改变 DOM 树的层次越深,这一条就越高效(这也有助于将表现和逻辑分开)。
尽可能的只对 position 为 absolute 或 fix 的元素做动画。

试着简化和优化 CSS 选择器(这个优化点被大多数使用 CSS 预处理器的开发者忽略了)。将嵌套层数控制在最小。以下是 CSS 选择器的性能排行(从最快的开始):
ID选择器:#id
class选择器: .class
标签: div
相邻的兄弟元素:a + i
父元素选择器: ul > li
通配符选择器: *
伪类和伪元素: a:hover
你应该记住浏览器处理选择器是从右向左的,这也就是为什么最右面的选择器会更快——#id
或.class。

CSS

; html-script: false ]div * {...} // bad
.list li {...} // bad
.list-item {...} // good
#list .list-item {...} // good

在你的脚本中,尽可能的减少 DOM 的操作。把所有东西都缓存起来,包括属性和对象(如果它可被重复使用)。进行复杂的操作的时候,最好操作一个“离线”的元素(“离线”元素的意思是与 DOM 对象分开、仅存在内存中的元素),然后将这个元素插入到 DOM 中。
如果你使用 jQuery,遵循[jQuery 选择器最佳实践]

当滚动时禁用一些复杂的 :hover
动画是一个很好的主意(例如,给 body 标签加一个 no-hover 的 class)。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容