浏览器
浏览器基础结构主要包括如下7部分:
1.用户界面(User Interface):用户所看到及与之交互的功能组件,如地址栏,返回,前进按钮等;
2.浏览器引擎(Browser engine):负责控制和管理下一级的渲染引擎;
3.渲染引擎(Rendering engine):负责解析用户请求的内容(如HTML或XML,渲染引擎会解析HTML或XML,以及相关CSS,然后返回解析后的内容);
4.网络(Networking):负责处理网络相关的事务,如HTTP请求等;
5.UI后端(UI backend):负责绘制提示框等浏览器组件,其底层使用的是操作系统的用户接口;
6.JavaScript解释器(JavaScript interpreter):负责解析和执行JavaScript代码;
7.数据存储(Data storage):负责持久存储诸如cookie和缓存等应用数据。
浏览器内核
浏览器内核主要指的是浏览器的渲染引擎,各大主要浏览器使用内核也是有差别的,大致可以分为以下几类:
- Trident内核: IE
- Webkit内核:Chrome,Safari
- Gecko内核:FireFox
关于移动端
移动端的浏览器内核主要说的是系统内置浏览器的内核。
目前移动设备浏览器上常用的内核有 Webkit,Blink,Trident,Gecko 等,其中 iPhone 和 iPad 等苹果 iOS 平台主要是 WebKit,Android 4.4 之前的 Android 系统浏览器内核是 WebKit,Android4.4 系统浏览器切换到了Chromium,内核是 Webkit 的分支 Blink,Windows Phone 8 系统浏览器内核是 Trident。
浏览器渲染过程
从图可知:浏览器从上到下解析HTML文档,碰到css样式(如果为外联,则加载css样式表),生成DOM节点树和解析样式生成CSSOM树是同时进行的。
图解释:
1.构建DOM树(DOM tree):从上到下解析HTML文档生成DOM节点树(DOM tree),也叫内容树(content tree);
2.构建CSSOM(CSS Object Model)树:加载解析样式生成CSSOM树;
3.执行JavaScript:加载并执行JavaScript代码(包括内联代码或外联JavaScript文件);
4.构建渲染树(render tree):根据DOM树和CSSOM树,生成渲染树(render tree);渲染树:按顺序展示在屏幕上的一系列矩形,这些矩形带有字体,颜色和尺寸等视觉属性。
5.布局(layout):根据渲染树将节点树的每一个节点布局在屏幕上的正确位置;
6.绘制(painting):遍历渲染树绘制所有节点,为每一个节点适用对应的样式,这一过程是通过UI后端模块完成;
7、合成(composite):在layout和painting之后,浏览器会将多个复合层传入GPU,进行合成工作
(5,6,7点深入探讨:一篇文章说清浏览器解析和CSS(GPU)动画优化)
为了更友好的用户体验,浏览器会尽可能快的展现内容,而不会等到文档所有内容到达才开始解析和构建/布局渲染树,而是每次处理一部分,并展现在屏幕上,这也是为什么我们经常可以看到页面加载的时候内容是从上到下一点一点展现的。
5,6两点过程中,如果 DOM 或 CSSOM 被修改,以上过程需要重复执行,这样才能计算出哪些像素需要在屏幕上进行重新渲染(导致重绘和回流)。
阻塞渲染:CSS 与 JavaScript
css如何阻塞渲染的?
碰到 CSS 资源,浏览器会优先CSSOM 构建,JavaScript 执行将暂停,直至 CSSOM 就绪。所以引入顺序上,CSS 资源先于 JavaScript 资源。
渲染树的构建要求同时具有 DOM 和 CSSOM。即HTML 和 CSS 都是阻塞渲染的资源。HTML 显然是必需的,因为包括我们希望显示的文本在内的内容,都在 DOM 中存放,那么可以从 CSS 上想办法。
最容易想到的当然是精简 CSS 并尽快提供它。
js如何阻塞渲染的?
当浏览器遇到一个 script 标记时,DOM 构建将暂停,直至脚本完成执行。
主流浏览器如Chrome和FireFox等都有一些优化,总是并行加载资源。比如在执行脚本时,开启另一个进程解析剩余的文档以找出并加载其他的待下载外部资源(不改变主进程的DOM树,仅优化加载外部资源)
如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。这显然是很不好的体验,所以浏览器允许脚本异步加载,下面就是两种异步加载的语法。
<script src="path/to/myModule.js" defer></script> <script src="path/to/myModule.js" async></script>
defer(下载完成延迟执行)与async(下载完成异步执行)
的区别是:
defer
要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;
async
一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。
另外,如果有多个defer
脚本,会按照它们在页面出现的顺序加载,而多个async
脚本是不能保证加载顺序的。
重绘和回流
重绘Repaint
当页面元素样式的改变不影响元素在文档流中的位置时(例如background-color, border-color,visibility),浏览器只会将新样式赋予元素并进行重绘操作在,注意改变字体大小也会触发重绘。
回流Reflow
当改变影响文档内容或者结构,或者元素位置时,回流操作就会被触发,本质还是一个布局的过程,一般有以下几种情况:
- DOM操作(对元素的增删改,顺序变化等);
- 内容变化,包括表单区域内的文本改变;
- CSS属性的更改或重新计算;
- 增删样式表内容;
- 修改class属性;
- 浏览器窗口变化(滚动或缩放);
- 伪类样式激活(:hover等)。
对于重回和回流,浏览器自身的渲染优化策略
浏览器本身会尽可能地减少其重绘或回流的次数,只更改必要的元素。例如一个position设置为absolute/fixed的元素的更改只会影响其本身和其子元素,而static的元素变化则会影响其之后的所有页面元素。
另外一项优化的技术则是在JavaScript代码运行时,浏览器会缓存所有的变化,然后只通过一次pass绘制操作来应用这些更改。例如下面这段代码只会触发一次重绘和回流:
然而,根据我们之前提到过的,获取某个元素的属性将会触发强制回流。比如我们在刚才的代码中加上一句读取元素属性的操作:
结果就会有两次回流发生。因此,我们应该尽量合并读取元素属性的操作来优化性能。
强制回流
当获取一些属性时,浏览器为取得正确的值也会触发重排。这样就使得浏览器的优化失效了。
这些属性包括:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle() (currentStyle in IE)。
所以,在多次使用这些值时应进行缓存。
当然也有我们不得不触发强制回流的情况。比如说对同一个元素的margin-left属性进行两次操作——开始的时候赋值100px的距离,之后为了实现动画效果,再加上transition属性将距离改变到50px.
我们先定义一个CSS类:
之后再对页面元素进行操作:
但事实上这段代码并不会像注释描述的那样运作,每条语句的操作将被缓存,只有结果会在页面上显示,所以我们就需要手动进行一次强制回流:
浏览器渲染优化
合法地书写HTML和CSS,不要忘了文档编码类型。样式文件应当在 <head> 标签中,脚本文件在 <body> 结束前。
简化并优化你的CSS选择器(有些人可能CSS预处理器用习惯了从来不关注这一点)。将嵌套层减少到最小。CSS选择器根据其优先级具有不同的运行效率(从快到慢):
- ID选择器: #id
- 类选择器: .class
- 标签选择器: div
- 相邻选择器: a + i
- 子元素选择器: ul > li
- 通用选择器: *
- 属性选择器: input[type="text"]
- 伪类选择器: a:hover
浏览器中CSS选择器是从右到左进行匹配的(为什么浏览器要从右到左匹配样式选择器),这也是为什么越短的选择器运行越快的原因(别提通用选择器,它会遍历所有元素):
- 在你的脚本代码中,多次访问的dom缓存下来,再去查询他们的属性。查找器尽量简洁,减少DOM的访问
- 将元素缓存到本地之后再进行操作,最后再添加到DOM当中,减少DOM的操作
- 如果你使用jQuery进行DOM操作的话,最好遵循jQuery最佳实践。
- 修改元素样式时,更改其class属性是性能最高的方法。你的选择器越有针对性越好(这同样也有助于分离页面样式和逻辑)。
- 尽量只对 position 为 absolute/fixed 的元素设置动画。
-
HTML文档结构层次尽量少,最好不深于六层;
-
少量首屏样式内联放在标签内
-
隐藏在屏幕外,或在页面滚动时,尽量停止动画
- 在页面滚动时禁用 :hover 样式效果:
参考文章: