做者:Alexander Skutin , 2014.5.26 . 由Max shirshin与2014年6月30日翻译(俄语 -> 英语)
现今咱们应更加注重网页渲染,及其在web开发中的重要性。虽然不少文章都曾谈到这一主题,但大可能是分散和割裂的。譬如为了对这个主题有更全面的认识须要去搜索不少的信息来源,而这也是笔者决定写这篇文章的缘由。笔者相信本篇文章会有益于初级开发者,固然对但愿可以更新和整理已有知识的中高级开发者一样可以有所裨益。
当页面布局定义完成后,页面渲染的过程与样式和脚本所承担的重要角色同样,也须要在初期就开始进行优化。专业的开发者须要了解这些技巧以免性能的问题。
本篇文章并不会详细介绍浏览器的内部工做机制,但将会提供一些通用的规则。这是因为不一样的浏览器引擎有着不一样的工做方式,太针对与某一个特定浏览器的研究会使这个过程变得过于复杂。
浏览器如何渲染网页?
首先概述浏览器渲染网页的过程:
一、文档对象模型(Document Object Model = DOM)将首先历来自于服务器的HTML中生成。
二、加载并解析样式,造成样式对象模型(CSS Object Mode = CSSOM)。
三、在DOM和CSSOM上层,建立一个渲染树,这是由将被渲染的对象造成的集合(webkit内核称这些对象为“渲染者”或“渲染对象”,在Gecko内核中则称为“帧”)。渲染树会影响DOM结构,但隐藏元素则不在此列(例如<head>标签,或具备display:none属性的元素)。在渲染树中的每一个文本字符串都被表述为一个单独的渲染对象,而每一个渲染对象都将包含其应有的DOM结构(或文本块)以及计算后的样式。从另外一个角度上说,这个渲染树描述了DOM中的可见表征。
四、计算渲染树中元素的坐标,从而成为“层”。浏览器使用一系列方法(这一系列方法仅须要一个节拍(pass))来放置全部的元素(表格须要多个节拍)。
五、最后,页面将在浏览器窗口中显示出来,这个过程称为“绘制”。
当用户与页面交互时,或者脚本改变页面时,前面说起的这些操做都会重复执行,页面布局改变时一样如此。
重绘
当改变了元素样式,而这个变更并不会影响元素在页面中的位置时(例如background-color,border-color,visibility),浏览器会依据新的样式仅重绘这个元素(即重绘和从新应用样式会发生)
从新布局
当改变影响了文档内容、结构、或元素位置时,布局会从新应用和生成。这些改变一般由如下行为触发:
- DOM操做(元素的添加、删除、修改以及调整顺序)
- 内容变动,包括表单域中的文字修改
- CSS属性的计算和修改
- 样式表的添加和移除
- class属性的修改
- 浏览器窗口变更(大小、滚动)
- 伪类激活(:hover)
如何优化
浏览器渲染
浏览器会尽量的将重绘和重排限制在受影响的元素区域内。例如,对一个绝对定位/悬浮定位的元素尺寸大小的变动会仅仅影响这个元素及他的后代,然而一个静态定位元素的尺寸变化会让在其以后的全部元素都触发从新布局。
另外一个优化的技巧是,当运行javascript代码时,浏览器会缓存这些变动,而后在代码运行以后,将变动放到一个单独的节拍中再应用。例如,下面的代码会仅触发一次重绘和从新布局。
var $body = $('body');javascript
$body.css('padding', '1px'); // 重排, 重绘php
$body.css('color', 'red'); // 重绘css
$body.css('margin', '2px'); // 重排,重绘html
// 以上步骤下实际仅发生一次重排和重绘
可是就像上面提到的,从新计算一个元素属性将会触发一次强制的从新布局。当咱们添加额外一行代码来获取元素属性时,将会产生一次强制重排
var $body = $('body');java
$body.css('padding', '1px');jquery
$body.css('color', 'red');
$body.css('margin', '2px');web
最终,咱们将形成两次重排而并不是只是一次。正因如此,你须要将获取元素属性归到一次,来优化性能。(来看示例)
当你不得不触发强制重绘时,会有一些情况发生。例如:咱们将要把一条相同的属性两次应用到一个相同的元素上。最初,这个元素会以无动画的方式设为100px,而后须要带动画的调整为50px。你能够经过这个示例研究,但我会更详细的描述这个状况。
咱们首先建立一个具备transition属性的css类
.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;
}
而后进行如下处理:
// 具备has-transition类的元素
var $targetElem = $('#targetElemId');
//移除has-transition类
$targetElem.removeClass('has-transition');
// 在css类再也不存在后,改变这个属性确认动画已经去除
$targetElem.css('margin-left', 100);
// 增长has-transition类
$targetElem.addClass('has-transition');
// 修改属性
$targetElem.css('margin-left', 50);
这个实现并不会像指望的方式工做。这些改变被缓存起来,并会在最后的代码块中应用。由于咱们须要一个强制的重排,经过如下方式能够实现:
//移除has-transition类
$(this).removeClass('has-transition');
//修改属性
$(this).css('margin-left', 100);
// 触发强制重排,从而在class和属性中的改变能够马上生效
$(this)[0].offsetHeight;
// 示例,其余的属性也能够实现
// 增长has-transition类
$(this).addClass('has-transition');
// 修改属性
$(this).css('margin-left', 50);
如今将会按设想中的工做了。
实际开发中的优化建议
经过总结一些有用的信息,(做者)提供如下建议:
- 建立有效的html和css,但不要忘记明确文档编码。样式能够添加到<head>中,脚本则添加到<body>的尾部。
- 尝试简化和优化css选择器(使用CSS预处理的开发者一般不会注意这种优化方法),尽可能保持低级的css嵌套。这也是css选择器标识性能的方法(从最快的那个开始)。
-
- 一、标识符 #id
- 二、类 .class
- 三、标签: div
- 四、兄弟选择器:a+i
- 五、邻接父子节点选择器 ul > li
- 六、通用选择器:*
- 七、特性选择器:input[type="text"]
- 八、伪类和伪元素:a:hover。你须要记住,浏览器处理CSS选择器的顺序是从右至左,这就是为何最右边的选择器应当是最快的那种,好比#id或.class
div * {...} 坏
.list li {...} 坏
.list-item {...} 好
#list .list-item {...} 好
一、在你的脚本中,尽量的减小对dom的操做,尽可能缓存那些会须要重复用到类型和对象。在执行复杂的操做时,比较好的方法是:操做一个“离线”的元素,并在结束后从新增长到DOM树中(离线的元素即脱离了DOM后在内存中保存的元素)。
三、为了修改元素的样式,修改元素的"class"属性是一般会采用的方法。执行这种操做的DOM节点的层次越深越好。(同时也是因为深层次定义有利于逻辑解耦)
四、若是能够的话,仅对那些绝对定位或悬浮定位的元素执行动画。
五、滚动时去除复杂的伪元素动画(例如:hover,能够给body增长一个额外的没有hover动画的样式)。
若须要更详细的说明,请参见如下文章:
1. How browsers work
2. Rendering: repaint, reflow/relayout, restyle
但愿本文可以有所启发!
30th June 2014