Web页面的渲染过程
页面的渲染过程
解析
生成DOM树(dom tree),遵循深度优先的解析原则。
DOM树的构建实际上是元素节点解析,它不会管样式、资源,只关注DOM元素。。
加载
加载:在当前节点解析完成后,异步加载。
css树(css tree,样式结构体),它不会和HTML结合在一起,只会关心它们之间的关系,并且会忽略掉浏览器不能识别的样式和无用的样式。
渲染树
在DOM树与CSS树构建完毕后,会构建渲染树(render tree)。
渲染树构建完毕之后,浏览器会根据它绘制页面。
渲染树的特点:
- 渲染树每个节点都有自己的样式;
- 不包含隐藏节点(
display: none
,<head>
之类不需要绘制的节点); visibility: hidden
样式的节点是包含在渲染树上的;- 渲染树上的每一个节点都会被当作一个盒子,具有内容填充、边距、边框、位置、大小、其他样式。
回流与重绘
当JavaScript对页面的节点进行操作时,就会产生回流或者重绘。
回流
因为节点的尺寸、布局、显示这一些改变时,渲染树中的一部分或者全部都需要重新构建,这种现象就叫回流(reflow),也叫重排。
一个页面至少有一次回流(页面渲染初始化);回流一定会引起重绘。
引起回流的因素:
- DOM节点的增加、删除;
- DOM节点位置变化;
- 元素的尺寸、边距、填充、边框、宽高;
- DOM节点
display
显示与否; - 页面渲染初始化;
- 浏览器 窗口尺寸变化;
- 向浏览器请求某些样式信息(offset、scroll、client、width、height、getComputedStyle()、currentStyle)。
重绘
回流时,浏览器会重新构建受影响部分的渲染树,只要渲染树改变,就会引起重绘。
回流完成后,浏览器会根据新的渲染树重新绘制回流影响的部分节点,这个重新绘制的过程,就叫重绘(repaint)。
重绘不一定是回流产生的后续反应。
时间线
浏览器从开始加载页面到整个页面加载完全结束这一过程中按顺序发生的每一件事情的总流程。
- 生成document对象(#document),此时JS就起作用了;
- 解析文档(从文档第一行阅读到最后一行),同时构建DOM树,此时
document.readyState
为loading
(第一阶段,加载中); - 遇到
<link>
开启新线程,异步加载外部css文件;<style>
构建CSS树(与DOM树同时); - 没有设置异步加载的
<script>
,阻塞文档解析,等待JS脚本加载并执行完毕后,继续解析文档; - 异步加载的
<script>
,异步加载JS脚本并执行(设置async的才会执行),不阻塞解析文档(不能使用document.write()
); - 遇到
<img>
,先解析节点,如果有src,创建加载线程,异步加载图片资源,不阻塞解析文档; - 文档解析完成(同时):
document.readyState
为interactive
(第二阶段,解析完成); - 文档解析完成(同时):设置了
defer
的脚本按顺序执行; - 文档解析完成(同时):触发
DOMContentLoaded事件
,程序从同步脚本执行阶段变为事件驱动阶段; - 设置了
async
的脚本加载并执行完成、<img>
等资源加载完毕后,触发onload事件
,document.readyState
为complete
(第三阶段,文档加载完成)。
仿jQuery的ready:
js
function domReady = function (fn) {
if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', function () {
document.removeEventListener('DOMContentLoaded', arguments.callee, false);
fn();
}, false);
} else if (document.attachEvent) {
document.attachEvent('onreadystatechange', function () {
if (this.readyState === 'complete') {
document.attachEvent('onreadystatechange', arguments.callee);
fn();
}
})
}
if (document.documentElement.doScroll && typeof (window.frameElement) === 'undefined') {
try {
document.documentElement.doScroll('left');
} catch (e) {
return setTimeout(arguments.callee.20);
}
fn();
}
}