浏览器渲染
浏览器渲染引擎
- Webkit: Chrome Safari
- Gecko: firefox
加载页面和渲染过程
tips 加载过程和渲染过程分开说。简明扼要、抓住要点,不拖沓
浏览器从加载到渲染页面的过程
加载过程: 要点
- 浏览器根据DNS服务器得到域名的IP地址
- 向这个IP的机器发送HTTP请求
- 服务器收到、处理并返回HTTP请求
- 浏览器得到返回结果(html)
渲染过程
- 根据HTML结构生成DOM树
- 根据CSS生成CSSOM树
- 将DOM和CSSOM整合形成RenderTree
- 根据RenderTree开始渲染和展示
- 遇到
script时会执行并阻塞渲染
link、img、script的下载是异步的,而执行是阻塞的。
为何要将CSS放在HTML头部?
这样会让浏览器尽早拿到CSS尽早生成CSSOM,然后在解析HTML后之后一次性生成最终的RenderTree,渲染一次即可。如果CSS放在HTML底部,会出现渲染卡顿的情况,影响性能和体验。
为何要将JS放在HTML底部?
JS放在底部可以保证让浏览器优先渲染完现有的HTML内容,让用户先看到内容,体验好。另外,JS执行如果涉及DOM操作,得等待DOM解析完成之后
参考推荐 《从输入 URL 到页面加载完成的过程中都发生了什么事情》
渲染过程
浏览器接收到 HTML 文件并转换为 DOM 树
- 网络传输中的字节数据转为字符串
- 字符串通过词法分析 转换为 标记token - 标记化
- 标记转为Node 根据 Node构建为 DOM树
字节数据 => 字符串 => Token => Node => DOM
将CSS文件转换为CSSOM树
字节数据 => 字符串 => Token => Node => CSSOM
层级扁平, 从右往左, 多用className
生成渲染树
渲染树只包含要显示的节点及其样式信息,若 某节点为 display: none 是不会在渲染树中显示的。
根据渲染树进行 布局Layout(回流),然后调用GPU绘制,合并图层,最终显示在屏幕上。
涉及知识点及思考
为什么DOM操作很慢
- DOM操作是渲染引擎,而JS运行则是在JS引擎中,线程间的通信,有性能损耗
- DOM节点 上属性 方法
- 操作DOM可能有回流、重绘发生,也会有性能问题
插入 n+ 个DOM,如何让页面不卡顿
- requestAnimationFrame
- 虚拟滚动 virtualized scroller 原理:只渲染可视区内容,非可见区域完全不渲染,当滚动时实时去替换渲染的内容
什么情况下阻塞渲染
渲染前要有渲染树,所以尽快生成 render Tree,降低需要渲染的文件大小(SSR),扁平层级,优化选择器
解析script会阻塞,所以script放body后,或者添加 defer或async属性
回流和重绘
- 重绘:外观改变而不需要影响布局的,color background
- 回流:几何属性或布局属性改变,需要进行计算称为回流
性能问题:
- 改变window大小
- 改变字体
- 添加或删除样式
- 文字改变
- 定位或者浮动
- 盒模型
回流和重绘和浏览器的EventLoop也有关:
- 当EventLoop执行完Microtasks,会判断document是否需要更新,因为浏览器是60hz刷新率 每16.67ms更新一次
- 然后判断是否有resize和scroll事件(resize scroll自带16.67ms的节流)
- 判断是否触发了media query
- 更新动画并且发送事件
- 判断是否有全屏操作事件
- 执行requestAnimationFrame回调
- 执行IntersectionObserver回调,该方法用于判断元素是否可见,可以用于懒加载,但兼容性不好
- 更新界面
- 一帧中可能会做的事,若有空闲,还会去执行 requestIdleCallback回调
减少回流重绘
节流
function throttle(fn, delay = 500) {
let lastTime = 0
return function(...args) {
let now = +new Date()
if (now - lastTime > delay) {
lastTime = now
fn.apply(this, args)
}
}
}
setInterval(throttle(
() => console.log(1)
), 1)防抖
function debounce(fn, delay = 500) {
let timer = null
return function(...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(this, args)
}, delay)
}
}在不考虑缓存和网络协议前提下,考虑通过哪些方式来最快渲染页面
- 文件大小考虑
- script标签的使用上考虑
- 从CSS、HTML书写上考虑
- 尽可能减少渲染页 所需要加载的资源
预加载
在开发中可能遇到,有资源不需要马上用到,但希望尽早获取,就可以用预加载
预加载是声明式的fetch,强制浏览器请求资源,且不会阻塞onload事件,可使用以下代码开启预加载
<link rel="preload" href="http://example.com">
预加载可以一定程度上降低首屏的加载时间,因为可以将一些不影响首屏但重要的文件延后加载,唯一缺点就是兼容性不好。
预渲染
可以通过预渲染将下载的文件预先在后台渲染,可以使用以下代码开启预渲染
<link rel="prerender" href="http://example.com">
预渲染虽然可以提高页面的加载速度,但是要确保该页面大概率会被用户在之后打开,否则就是白白浪费资源去渲染。
懒执行
将某些逻辑延迟到使用时再计算。可用于首屏优化,对于某些耗时逻辑并不需要在首屏就使用的,就可以使用懒执行。懒执行需要唤醒,一般可以通过定时器或者事件的调用来唤醒
懒加载
将不关键的资源延后加载
图片,进入或即将进入可视区再请求加载需要的资源。
CDN
静态资源尽量使用CDN加载,由于浏览器对于某个域名有并发请求上限,可考虑使用多个CDN域名。
加载静态资源需要注意CDN域名要与主站不同,否则每次请求都会带上主站的Cookie,平白消耗流量。
