开始上网,输入 url
当我们开始输入 url 的时候,浏览器就已经开始匹配 url ,从历史纪录,书签等,找到已经输入的字符串可能对应的 url ,然后给出智能提示,自动补全等。
服务器返回 HTTP 请求
响应类似请求:
- 状态行
- 响应头
- 响应体
这五步中,三四步比较慢生成布局和绘制合成渲染
当文档加载过程中遇到js文件,html文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中js文件加载完毕,还要等待解析执行完毕,才可以恢复html文档的渲染线程。因为JS有可能会修改DOM,最为经典的document.write,这意味着,在JS执行完成前,后续所有资源的下载可能是没有必要的,这是js阻塞后续资源下载的根本原因。所以我明平时的代码中,js是放在html文档末尾的。
DOM 树
DOM 树中的每一个需要显示的节点再渲染树中至少存在一个对应的节点,隐藏的 DOM 元素则没有对应节点,渲染树中的节点被称为 “帧(frames)”或“盒(boxes)”,
符合 CSS 模型的定义,一旦DOM 树和渲染树构建完成,浏览器就开始显示绘制元素
很多时候,密集的重新渲染是无法避免的,比如scroll事件的回调函数和网页动画。
网页动画的每一帧(frame)都是一次重新渲染。每秒低于24帧的动画,人眼就能感受到停顿。一般的网页动画,需要达到每秒30帧到60帧的频率,才能比较流畅。如果能达到每秒70帧甚至80帧,就会极其流畅。
大多数显示器的刷新频率是60Hz,为了与系统一致,以及节省电力,浏览器会自动按照这个频率,刷新动画(如果可以做到的话)。
所以,如果网页动画能够做到每秒60帧,就会跟显示器同步刷新,达到最佳的视觉效果。这意味着,一秒之内进行60次重新渲染,每次重新渲染的时间不能超过16.66毫秒。
一秒之间能够完成多少次重新渲染,这个指标就被称为"刷新率",英文为FPS(frame per second)。60次重新渲染,就是60FPS。
如果想达到60帧的刷新率,就意味着JavaScript线程每个任务的耗时,必须少于16毫秒。一个解决办法是使用Web Worker,主线程只用于UI渲染,然后跟UI渲染不相干的任务,都放在Worker线程。
DOM 解析
<html>
<html>
<head>
<title>Web page parsing</title>
</head>
<body>
<div>
<h1>Web page parsing</h1>
<p>This is an example Web page.</p>
</div>
</body>
</html>
DOM 解析成这样
CSS 解析
假设有下面这样的 DOM 结构
<doc>
<title>A few quotes</title>
<para>
Franklin said that <quote>"A penny saved is a penny earned."</quote>
</para>
<para>
FDR said <quote>"We have nothing to fear but <span>fear itself.</span>"</quote>
</para>
</doc>
CSS 文档
/* rule 1 */ doc { display: block; text-indent: 1em; }
/* rule 2 */ title { display: block; font-size: 3em; }
/* rule 3 */ para { display: block; }
/* rule 4 */ [class="emph"] { font-style: italic; }
CSS Rule tree 时这样
- 图中的第4条规则出现了两次,一次是独立的,一次是在规则3的子结点。所以,我们可以知道,建立CSS Rule Tree是需要比照着DOM Tree来的。CSS匹配DOM Tree主要是从右到左解析CSS的Selector,好多人以为这个事会比较快,其实并不一定。关键还看我们的CSS的Selector怎么写了。
- CSS匹配HTML元素是一个相当复杂和有性能问题的事情。所以,你就会在N多地方看到很多人都告诉你,DOM树要小,CSS尽量用id和class,千万不要过渡层叠下去
通过这俩个树我们可以得到一个叫 Style Context Tree
所以,Firefox基本上来说是通过CSS 解析 生成 CSS Rule Tree,然后,通过比对DOM生成Style Context Tree,然后Firefox通过把Style Context Tree和其Render Tree(Frame Tree)关联上,就完成了。注意:Render Tree会把一些不可见的结点去除掉。而Firefox中所谓的Frame就是一个DOM结点,不要被其名字所迷惑了。
在 Firefox 中,系统会针对 DOM 更新注册展示层,作为侦听器。展示层将框架创建工作委托给FrameConstructor,由该构造器解析样式(请参阅样式计算)并创建框架。
在 WebKit 中,解析样式和创建呈现器的过程称为“附加”。每个 DOM 节点都有一个“attach”方法。附加是同步进行的,将节点插入 DOM 树需要调用新的节点“attach”方法
渲染
- 计算 CSS 样式
- 构建 render tree
- Layout
- 开始绘制
上图流程中有很多连接线,这表示了Javascript动态修改了DOM属性或是CSS属会导致重新Layout,有些改变不会,就是那些指到天上的箭头,比如,修改后的CSS rule没有被匹配到,等。
Repaint
屏幕的一部分要重画,比如某个元素的 CSS 背景变了,但尺寸没有变
Reflow
当 DOM 的变化影响了元素的几何属性 => 比如改变边框宽度或者给段落增加文字,导致行数增加 => 浏览器需要重新计算元素的几何属性,同样其他元素的几何属性和位置也会收到影响,浏览器会使渲染树中受到影响部分失效,并重新构造渲染树,发生重排,完成重排后,浏览器会重新绘制受影响部分的元素到屏幕中,也会发生重绘。
- 样式表越简单,重排和重绘就越快。
- 重排和重绘的DOM元素层级越高,成本就越高。
- table元素的重排和重绘成本,要高于div元素
发生重绘
- 当你增加、删除、修改DOM结点时,会导致Reflow或Repaint
- 当你移动DOM的位置,或是搞个动画的时候。(元素位置发生改变)
- 当你修改CSS样式的时候。或者页面渲染初始化
- 当你Resize窗口的时候(移动端没有这个问题),或是滚动的时候。
- 当你修改网页的默认字体时。
- 元素尺寸发生改变(边距, 高度等)
- 注:display:none会触发reflow,而visibility:hidden只会触发repaint,因为没有发现位置变化。
渲染树的变化的排队与刷新
由于每次重排都行会产生消耗,大多数浏览器通过队列化修改并批量执行来优化重排过程,然而,你可能会不知不觉的强制刷新对了要求计划立刻执行,获取布局的信息会导致队列刷新
- offsetTop, offsetBottom, offsetWidth, offsetHeight
- scrollTop, scrollBottom, scrollWidth, scrollHeight
- clientTop, clientBottom, clientWidth, clientHeight
- getComputedStyle() =>( currentStyle in IE)
以上属性和方法需要返回最新的布局信息,因此浏览器不得不执行渲染队列中的‘待处理变化’并触发重排以返回正确的值。
"重绘"不一定需要"重排" ---- "重排"必然导致"重绘"
最小化重绘和重排
- 改变样式 => 不要一条条地改变样式,多个改变合并 => 直接更换 CSS 的 Class
- 批量修改 DOM => 使元素脱离文档流(隐藏显示,使用文档片段,拷贝到一个脱离文档的节点中完成后再替换) => 修改 => 带回文档
- 动画使用绝对定位,使用拖放代理
- 缓存布局信息 => 偏移量,滚动位置, 计算出的样式等 获取一次后赋值给局部遍历
- 事件委托 => 减少事件处理器的数量
浏览器请求嵌在 HTML 中的资源(图片,视频音频, CSS, JavaScript 等)
其实这个步骤可以并列在步骤8中,在浏览器显示HTML时,它会注意到需要获取其他地址内容的标签。这时,浏览器会发送一个获取请求来重新获得这些文件。比如我要获取外图片,CSS,JS文件等,类似于下面的链接:
图片:http://or3233yyd.bkt.clouddn.com//17-8-9/67016800.jpg
CSS式样表:https://cdn.bootcss.com/animate.css/3.5.2/animate.css
JavaScript 文件:https://cdn.bootcss.com/jquery/3.2.1/core.js
这些地址都要经历一个和HTML读取类似的过程。所以浏览器会在DNS中查找这些域名,发送请求,重定向等等...
不像动态页面,静态文件会允许浏览器对其进行缓存。有的文件可能会不需要与服务器通讯,而从缓存中直接读取,或者可以放到CDN中
网友评论