浏览器底层渲染机制
程序员文章站
2022-04-14 14:02:47
浏览器渲染机制页面之所以能渲染从服务器获取需要渲染的内容(URL解析/DNS/TCP/HTTP…)浏览器基于自己的渲染引擎(例如:webkit/gecko/trident/blink…)开始自上而下加载渲染代码服务器获取的是文件流浏览器首先把16进制的字节流信息编译为“代码字符串”按照w3c规则进行字符解析,生成对应的Tokens,最后转换为浏览器内核可以识别渲染的DOM节点按照节点最后解析为对应的树DOM树,CSSOM树,Render树浏览器在css资源还没有请求回来之前,先生...
浏览器渲染机制
页面之所以能渲染
- 从服务器获取需要渲染的内容(URL解析/DNS/TCP/HTTP…)
- 浏览器基于自己的渲染引擎(例如:webkit/gecko/trident/blink…)开始自上而下加载渲染代码
服务器获取的是文件流
- 浏览器首先把16进制的字节流信息编译为“代码字符串”
- 按照w3c规则进行字符解析,生成对应的Tokens,最后转换为浏览器内核可以识别渲染的DOM节点
- 按照节点最后解析为对应的树DOM树,CSSOM树,Render树
浏览器
- 在css资源还没有请求回来之前,先生成DOM树
- 当所有的css请求回来之后,浏览器按照css的导入顺序,依次进行渲染,最后生成cssom树
- 把DOM树和CSSOM树结合在一起,生成有样式,有结构的render树
- 浏览器按照渲染树,在页面中进行渲染和解析
- 计算元素在设备饰扣的大小和位置布局(layout)或重拍/回流(reflow)
- 根据渲染树以及回流得到的几何信息,得到节点的绝对像素=>绘制/重绘(painting)
link和@import都是导入外部样式(从服务器获取样式文件)
- 遇到link,浏览器会派出一个新的线程(HTTP线程)去加载资源文件,与此同时GUI渲染线程会继续向下渲染代码。不论css是否请求回来,代码都会继续渲染
- 遇到@import,GUI渲染线程会暂时停止渲染,去服务器加载资源文件,资源文件没有返回之前,是不会继续渲染的@import阻碍浏览器的渲染,项目中尽量少用
- 如果是style,GUI直接渲染
正常情况下js也会阻碍GUI的渲染
- js在一般在页面的底部,就是为了确保DOM树生成完才去加载js
- 可能会与defer和async异步管控js的请求
性能优化
- 减少DOM树渲染的时间(HTML层级不要太深,标签语义化…)
- 减少CSSOM树渲染时间(选择器是从右向左解析,所以尽可能减少选择器的层级【less/sass中的层级嵌套虽然好用,但是是一个大坑】)
- 减少HTTP的请求次数和请求大小
- 一般会把CSS放在页面的开始位置(提前请求资源 用link别用@import,对于移动端来讲,如果CSS比较少,尽可能采用内嵌式即可…)
- 为了避免白屏,可以进来第一件事,快速生成一套 loding 的渲染树(前端骨架屏);服务器的SSR骨架屏所提高的渲染是避免了客户端再次单独请求数据,而不是样式和结构上的(首屏处理)
- 把JS放在页面底部以及尽可能使用defer或者async
- CRP性能节点优化
DOM的重绘和回流Repaint和Reflow
- 重绘:元素样式的改变(但宽高、大小、位置不变)如:outline、visibility、color、background-color等
- 回流:元素的大小或者位置发生变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重薪计算布局和渲染
- 如添加或删除可见的DOM元素;元素的位置发生变化;元素的尺寸发生变化(比如文本变化或图片被另一个不同尺寸的图片替代);页面一开始渲染的时候(这个无法避免);因为回流是根据视口大小来计算元素的位置和大小的,所以浏览器的窗口尺寸变化也会引发回流
- 回流一定会触发重绘,但是重绘不一定会回流
性能优化:避免DOM的回流
1、缓存布局信息
div.style.left = div.offsetLeft + 1 + ‘px’;
div.style.top = div.offsetTop + 1 + ‘px’;
修改为=>
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + ‘px’;
div.style.top = curTop + 1 + ‘px’;
2、元素批量修改
文档碎片:createDocumentFragment
模板字符串拼接
3、动画效果应用position属性为absolute或fixed的元素上(脱离文档流)
4、CSS3硬件加速(GPU加速)
比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘;transform/opacity/filters/这些属性会触发硬件加速,不会引发回流和重绘
可能会引发的坑:过多使用会占用大量内存,性能消耗严重,有时候会导致字体模糊等
5、牺牲平滑度换取速度
每次1像素移动一个动画,但是如果此动画使用了100%的CPU,动画就会看起来是跳动的,因为浏览器正在与更新回流做斗争,每次移动3像素可能看起来平滑度低了,但它不会导致cpu在比较慢的机器中抖动
6、避免table布局和使用css的JavaScript表达式
示例代码
/* 点击盒子,让其立即回到0的位置,然后再运动到200的位置(动画1S) */
<style>
.box {
position: absolute;
top: 100px;
left: 100px;
width: 100px;
height: 100px;
background: red;
transition: left 1s linear 0s;
}
</style>
// 现代版浏览器,两行修改操作,只引发一次回流
// 浏览器的渲染队列机制:遇到修改样式的代码,浏览器没有立即渲染,而是先把他放到渲染队列中,继续看下面是否还是修改样式的,
// 是的话继续放进去....(直到遇到获取元素样式的代码或者没有修改样式的代码了,则立即把队列中的样式统一进行渲染,最后只引发一次回流重绘)
box.onclick = function () {
// 立即回到零
box.style.transitionDuration = '0s';
box.style.left = '0px';
// 运动到200的位置
box.style.transitionDuration = '1s';
box.style.left = '200px';
};
// 由于渲染队列机制原因,这样会触发两次,一旦遇到获取样式的代码,需要先把之前队列中的样式进行渲染
box.onclick = function () {
box.style.transitionDuration = '0s';
box.style.left = '0px';
// 获取样式,会立即刷新渲染队列
box.offsetLeft;
box.style.transitionDuration = '1s';
box.style.left = '200px';
};
// 如果把2个操作放到宏任务中,也可以实现效果,但是两个宏任务的时间必须不一样,如果一样,那么还是会一起渲染的
box.onclick = function () {
setTimeout(() => {
box.style.transitionDuration = '0s';
box.style.left = '0px';
}, 1)
setTimeout(() => {
box.style.transitionDuration = '1s';
box.style.left = '200px';
}, 2)
};
// 会同时渲染
box.onclick = function () {
setTimeout(() => {
box.style.transitionDuration = '0s';
box.style.left = '0px';
})
setTimeout(() => {
box.style.transitionDuration = '1s';
box.style.left = '200px';
})
};
// 使用文档碎片
let frg = document.createDocumentFragment();
for (let i = 0; i < 10; i++) {
let span = document.createElement('span');
span.innerHTML = i;
frg.appendChild(span);
}
document.body.appendChild(frg);
本文地址:https://blog.csdn.net/uperficialyu/article/details/107246021