CRP(Critical [ˈkrɪtɪkl] Rendering [ˈrendərɪŋ] Path,关键渲染路径)
总览
从用户输入 url 到显卡绘制,一共分为下面这几个步骤。
1、URl解析
2、DNS 解析
3、TCP/IP 三次握手
4、发送 HTTP 请求
5、服务器返回结果
6、TCP/IP 四次挥手
7、HTML解析,生成 DOM 树,CSS 生成 CSSOM 树,生成渲染树
8、将绘制任务交给显卡
过程
1 URI解析
地址解析和编码
HSTS (HTTP严格传输安全协议)
缓存检查
。。。
浏览器首先会看是否有缓存,若没有缓存,则浏览器会直接向服务器发送请求,然后服务器返回请求结果以及缓存标识,存入缓存,然后页面加载成功
如果有缓存,则看缓存是否过期(Expires 和 Cache-Control),若缓存没过期,则直接读取缓存,然后返回缓存,最后页面加载成功
如果有缓存,但是缓存过期了,则需要携带标识(if-Modified-Since和if-None-Match),向服务器发送请求,此时服务器会查看资源是否有更新,(1)若没有更新,则返回304 状态码,继续使用缓存->读取缓存->返回缓存->页面加载成功;(2)若资源已经更新,则重新返回资源和缓存标识,返回状态码为200,并存入缓存中,最后页面加载成功
2 DNS解析
3 TCP/IP 三次握手
4 发送 HTTP 请求
参考:HTTP1.0、HTTP1.1和HTTP2.0的区别
5 服务器返回结果
6 TCP/IP 四次挥手
7 HTML解析,生成 DOM 树,CSS 生成 CSSOM 树,生成渲染树
参考:浏览器-页面渲染过程
优化
关键流程一:浏览器渲染流程
1. 构建DOM树、CSSOM树、渲染树、
- DOM树
- 转换
- 令牌
- 词法分析
- DOM构建
CSSOM 树
Render-Tree 渲染树
- 总结步骤:
- 处理 HTML 标记,构建 DOM 树
- 处理 CSS 标记,构建 CSSOM 树
- 将 DOM 树和 CSSOM 树融合成渲染树
- 根据生成的渲染树,计算它们在设备视口(viewport)内的确切位置和大小,这个计算的阶段就是回流 => 布局(Layout)或 重排(reflow)
- 根据渲染树以及回流得到的几何信息,得到节点的绝对像素 => 绘制(painting)或栅格化(rasterizing)
优化方案:
标签语义化和避免深层次嵌套
CSS选择器渲染是从右到左
尽早尽快地把CSS下载到客户端(充分利用HTTP多请求并发机制)
- style
- link
- @import
- 放到顶部
避免阻塞的JS加载
放到底部
减少DOM的回流和重绘
浏览器渲染原理
1. DOM的重绘和回流 Repaint & Reflow
- 重绘:元素样式的改变(但宽高、大小、位置等不变)
- 如:outline, visibility, color, background-color等
- 回流:元素的大小或者位置发生了变化(当页面布局和几何信息发生变化的时候),触发了重新布局,导致渲染树重新计算布局和渲染
- 如添加或删除可见的DOM元素;元素的位置发生变化;元素的尺寸发生变化;内容发生变化(比如文本变化或者图片被另一个不同尺寸的图片所替代);页面一开始渲染的时候(这个无法避免);因为回流事根据视口的大小来计算元素的位置和大小的,所以浏览器的窗口尺寸变化也会引发回流。。。 注意:回流一定会触发重绘,而重绘不一定会回流
2. 前端性能优化之:避免DOM的回流
- 放弃传统操作DOM的时代,基于vue/react开始数据影响视图模式
- mwm / mvc / virtual dom / dom diff …
- 分离读写操作(现代的浏览器都有渲染队列的机制)
- offsetTop, offsetLeft, offsetWidth, offsetHeight, clientTop, clientLeft, clientWidth, clientHeight, scrollTop, scrollLeft, scrollWidth, scrollHeight, getComputedStyle, currentStyle… 会刷新渲染队列
- 样式集中改变
- div.style.cssText = ‘width:20px;height:20px;’
- div.className = ‘box’;
前端性能优化之:避免DOM的回流
- 缓存布局信息
- 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’;
- 元素批量修改
- 文档碎片:createDocumentFragment
- 模版字符串拼接
- 动画效果应用到position 属性为 absolute 或 fixed 的元素上(脱离文档流)
- CSS3 硬件加速(GPU加速)
- 比起考虑如何减少回流重绘,我们更期望的事,根本不要回流重绘;transform, opacity, filters… 这些属性会触发硬件加速,不会引发回流和重绘…
- 可能会引发的坑:过多使用会占用大量内存,性能消耗严重,有时候会导致字体模糊等
- 牺牲平滑度换取速度
- 每次1像素移动一个动画,但是如果此动画使用了100%的CPU,动画就会看上去事跳动的,因为浏览器正在与更新回流做斗争。每次移动3像素可能看起来平滑度低了,但它不会导致CPU 在较慢的机器中抖动
- 避免 table 布局和使用 css 的 javascript 表达式
关键流程二:网络交互层面上的优化
1. DNS方面的优化
每一次DNS解析时间预计在20~120毫秒
- 减少DNS请求次数
- DNS预获取(DNS Prefetch)
<meta http-equiv="x-dns-prefetch-control" content="on">
<link rel="dns-prefetch" href="//static.360buyimg.com"/>
<link rel="dns-prefetch" href="//misc.360buyimg.com"/>
<link rel="dns-prefetch" href="//img10.360buyimg.com"/>
<link rel="dns-prefetch" href="//d.3.cn"/>
<link rel="dns-prefetch" href="//d.jd.com"/>
复制代码
2. 减少HTTP请求次数和请求资源大小
- 资源合并压缩
- 字体图标
- Base64
- GZIP(一般的文件能压缩60%多)
- 图片懒加载
- 数据延迟分批加载
- CDN资源
- …
3. 应用缓存
缓存位置
- Service Worker:浏览器独立线程进行缓存
- Memory Cache : 内存缓存
- Disk Cache:硬盘缓存
- Push Cache:推送缓存(HTTP/2中的)
打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配,如有则使用,如没有则发送网络请求。 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话),其次才是 disk cache。 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache),服务器直接返回 200 和最新内容。
强缓存 Expires / Cache-Control
浏览器对于强缓存的处理:根据第一次请求资源时返回的响应头来确定的
- Expires:缓存过期时间,用来指定资源到期的时间(HTTP/1)
- Cache-Control:cache-control: max-age=2592000第一次拿到资源后的2592000秒内(30天),再次发送请求,读取缓存中的信息(HTTP/1.1)
两者同时存在的话,Cache-Control优先级高于Expires
协商缓存 Last-Modified / ETag
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程
- 协商缓存生效,返回304和Not Modified
- 协商缓存失效,返回200和请求结果
- Last-Modified和If-Modified-Since 第一次访问资源,服务器返回资源的同时,响应头中设置 Last-Modified(服务器上的最后修改时间),浏览器接收后,缓存文件和响应头; 下一次请求这个资源,浏览器检测到有 Last-Modified,于是添加If-Modified-Since请求头,值就是Last-Modified中的值; 服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200; 但是Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源;
- ETag和If-None-Match Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成;下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到请求头If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。
数据缓存:LocalStorage本地存储
参考文章:掘金文章:深度剖析前端性能优化CRP