- 解析Html (不包含 js css外部文件)
- readstatechange
(第一个)
此时状态为interactive,表示文档已加载和解析但资源仍在加载,该状态通常紧接着会触发DOMContentLoaded
- DOMContentLoaded
Html文档被加载和解析成功,DOM树构建完成时会触发
- Recalculate Style (cssom 构建完成)
通过添加和删除元素,更改属性、类或通过动画更改DOM,全都会导致浏览器重新计算元素样式,在很多情况下还会对页面和页面的一部分进行布局(layout )。 重新计算样式可以分为两步:
- 浏览器计算出给指定元素应用哪些类、伪选择器和ID
- 从匹配选择器中获取所有样式规则,并计算出此元素的最终样式
- readystatechange
(第二个)
(文档已加载和解析,且资源也加载完成)
此时状态为complete,表示文档和资源都已加载完成,该状态通常紧接着触发load
- load事件
文档和资源都已加载完成时会触发
- layout
布局几乎总是在作用整个文档,但还是主要看影响的节点个数
-
解析Html (包含js css外部文件)
- Evaluate Script (执行js)
- Layout 变为不在ParseHtml中执行 可能是因为CSS文件或JS文件的加载阻塞了整个页面的渲染过程,因为js和css都可能对标签进行样式的设置。如果不存在文件,就不会存在等待加载的问题。
-
改变背景色 (重绘)
- 改变高度 (回流,重排)
- 关于阻塞
- css不会阻塞DOM树的解析
- css加载会阻塞js,从而阻塞了DOM树的解析,页面渲染(内联样式性能较高,使用与第一屏)
- js会阻塞DOM树的解析(因为js会改变DOM树的内容)
- css引入的字体文件加载,也会阻塞js
-
关于与页面渲染过程的对应
- js执行时:这时应该只是构建了前面部分的DOM树和CSSOM树,因为js需要通过dom api和CSSOM api操作前面部分的标签内容和样式
- DOM树构建完成:DomContentLoaded事件
- CSSOM构建完成、Render Tree构建完成: Recalculate Style
- Layout:Layout事件
- paint:Paint(图片层绘制)和Composite Layers(图片层合并),除了transform或opacity属性之外,更改任何属性始终都会触发绘制paint
- reflow回流: 3 4 5走一遍
- repaint重绘:3 5 步走一遍
- 更改一个既不要布局也不要绘制的属性: 3 步 + Composite Layers,此行为在 678重新渲染步骤中开销最小,适合动画或滚动,具体比如transform opacity
-
关于chrome浏览器的一些行为
- 渲染队列: 浏览器存在一个渲染队列,用于将多次连续的重拍和重绘操作变成一次。当你进行DOM的读操作时,如果队列不为空,chrome会清空队列,立即进行重排或者重绘。
- 布局:布局或重排中浏览器需要计算元素要占据的空间大小及其在屏幕的位置,网页的布局模式意味着一个元素可能影响其他元素,例如
<body>
元素的宽度一般会影响其子元素的宽度以及树中各处节点。 - 绘制与合成:绘制一般是在多个表面(通常称为层)上完成的,因此浏览器需要将它们按正确顺序绘制到屏幕上,以便正确渲染页面。
- css选择器:对于复杂的css选择器,浏览器需要花更多的时候来确定元素的样式,因此以类为中心的css编写原则。
- js问题 通过script标签的async defer属性
关于css
- 使用简单的样式表。样式表越简单,重排和重绘就越快。具体为
- 减少选择器的复杂性,少用伪类;
- 减少必须计算器样式的元素数量,应当尽可能减少声明为无效的元素的数量。
- 减少DOM元素层级。重绘和重排的DOM元素层级越高,成本就越高。
- 多利用display:none。 display:none的元素没有在渲染树,因而也不会进行重排和重绘
- 使用css动画而不是js动画。(CSS动画优于JS动画,是由于CSS改变的是translate的值,不会引起offsetLeft、offsetTop等位置值的改变。)
- 使用absolute而不是float。position属性为absolute和fixed的元素在重排的开销比float少,因为不用考虑它对其它元素的影响。
- 使用div而不是table。
- 使用classList代替className。 className只要赋值,就一定会重新渲染。
关于JS
- DOM的多个读操作(或多个写操作)应该放在一起,不要穿插进行。因为连续地设置元素样式(写操作),浏览器会一次性执行,即只触发一次重排或重绘,但如果在几个写操作间插入读取样式的操作,浏览器则不得不立即重排或重绘。
- 一次性改变样式。不要一条条地改变样式,而要通过改变class,或者el.style.csstext属性。
- 使用离线DOM来改变元素样式。比如
cloneNode()
克隆节点,然后再替换掉元素节点 或者display:none → 改动 → 显示
。 - 尽量修改层级较低的DOM。
- 不要在循环中重复读取DOM节点属性值。
- 使用 window.requestAnimationFrame()、window.requestIdleCallback() 这两个方法控制重新渲染。
提高fps(frame per second)
网页动画的每一帧(frame)都是一次重新渲染,将一帧送到屏幕会采用如下顺序:
手动控制重新渲染 window.requestAnimationFrame() 方法可以将某些代码统一放到下一次重新渲染时执行。具体是将js代码放在下一帧开始时执行。如果使用setTimeout 或 setInterval 来执行动画之类的视觉变化,其回调可能在帧的某个时间点执行,可能在末尾,这会使我们丢失帧,导致卡顿。
- 处理“布局抖动” 反复读写属性会导致布局抖动,导致长帧。
function doubleHeight(element) {
var currentHeight = element.clientWidth;
element.style.width = (currentHeight / 2) + 'px';
element.style.height = '80px';
}
var elements = document.getElementsByTagName('tr');
for (var i = 0; i < elements.length; i++) {
doubleHeight(elements[i]);
}
将doubleHeight函数改成下面这样:
function doubleHeight(element) {
var currentHeight = element.clientHeight;
window.requestAnimationFrame(function () {
element.style.height = (currentHeight * 2) + 'px';
});
}
- 页面滚动事件
$(window).on('scroll', function() {
window.requestAnimationFrame(scrollHandler);
});
- 现在项目中,页面(以“任务”页面为例)在加载时都会请求一些ajax数据,比如datagrid,tree数据等等,还有些ajax数据只是预加载。如果这些ajax在页面渲染前完成请求,则会阻塞页面渲染。所以同一个页面不同网速下会有两种渲染顺序:
解决办法
- 在所有资源加载完后进行ajax请求。将datagrid等控件的数据加载放在$(window).load()事件中。
- 延迟初始化modal中的内容
- 问:只要执行js,都会重排吗? 答:执行js的发生情况如下:
- 载入页面时script标签:不管有无改变dom,都会重排
- 异步ajax回调:需要判断有无改变dom
- setTimeout:需要判断有无改变dom
-
请求js文件时,请求和执行顺序是什么? 请求会一起发出;执行顺序按引入的顺序,不会因为后一个先返回数据而先执行。
-
页面加载时提前ajax请求一些数据,会不会影响性能? 答:可能会,可能不会。ajax请求时不影响性能,请求后执行回调函数会影响。ajax回调函数会在请求完成且js主程序运行完后执行,等到我们能看到页面,需要经历页面解析和渲染这两个过程,如果页面渲染前ajax回调执行了,那将阻塞渲染过程。
-
问:js是单线程的,浏览器也是单线程的吗? 答:不,浏览器是多线程的,但我觉得本质上也是单线程。网上的资料显示浏览器分为GUI渲染线程(解析渲染布局绘制)、JS引擎线程和事件队列线程(事件、定时器、ajax)