06 | 渲染流程(下):HTML、CSS和JavaScript,是如何变成页面的?
06 | 渲染流程(下):HTML、CSS和JavaScript,是如何变成页面的?
讲述:李兵
时长12:46大小11.68M
分层
图层绘制
栅格化(raster)操作
合成和显示
渲染流水线大总结
相关概念
1. 更新了元素的几何属性(重排)
2. 更新元素的绘制属性(重绘)
3. 直接合成阶段
总结
思考时间
赞 113
提建议
精选留言(136)
- Zkerhcy2020-03-16浏览器工作流程『从输入 URL 到页面展示』学习笔记 导航 用户输入 1. 用户在地址栏按下回车,检查输入(关键字 or 符合 URL 规则),组装完整 URL; 2. 回车前,当前页面执行 onbeforeunload 事件; 3. 浏览器进入加载状态。 URL 请求 1. 浏览器进程通过 IPC 把 URL 请求发送至网络进程; 2. 查找资源缓存(有效期内); 3. DNS 解析(查询 DNS 缓存); 4. 进入 TCP 队列(单个域名 TCP 连接数量限制); 5. 创建 TCP 连接(三次握手); 6. HTTPS 建立 TLS 连接(client hello, server hello, pre-master key 生成『对话密钥』); 7. 发送 HTTP 请求(请求行[方法、URL、协议]、请求头 Cookie 等、请求体 POST); 8. 接受请求(响应行[协议、状态码、状态消息]、响应头、响应体等); - 状态码 301 / 302,根据响应头中的 Location 重定向; - 状态码 200,根据响应头中的 Content-Type 决定如何响应(下载文件、加载资源、渲染 HTML)。 准备渲染进程 1. 根据是否同一站点(相同的协议和根域名),决定是否复用渲染进程。 提交文档 1. 浏览器进程接受到网路进程的响应头数据,向渲染进程发送『提交文档』消息; 2. 渲染进程收到『提交文档』消息后,与网络进程建立传输数据『管道』; 3. 传输完成后,渲染进程返回『确认提交』消息给浏览器进程; 4. 浏览器接受『确认提交』消息后,移除旧文档、更新界面、地址栏,导航历史状态等; 5. 此时标识浏览器加载状态的小圆圈,从此前 URL 网络请求时的逆时针选择,即将变成顺时针旋转(进入渲染阶段)。 渲染 渲染流水线 构建 DOM 树 1. 输入:HTML 文档; 2. 处理:HTML 解析器解析; 3. 输出:DOM 数据解构。 样式计算 1. 输入:CSS 文本; 2. 处理:属性值标准化,每个节点具体样式(继承、层叠); 3. 输出:styleSheets(CSSOM)。 布局(DOM 树中元素的计划位置) 1. DOM & CSSOM 合并成渲染树; 2. 布局树(DOM 树中的可见元素); 3. 布局计算。 分层 1. 特定节点生成专用图层,生成一棵图层树(层叠上下文、Clip,类似 PhotoShop 里的图层); 2. 拥有层叠上下文属性(明确定位属性、透明属性、CSS 滤镜、z-index 等)的元素会创建单独图层; 3. 没有图层的 DOM 节点属于父节点图层; 4. 需要剪裁的地方也会创建图层。 绘制指令 1. 输入:图层树; 2. 渲染引擎对图层树中每个图层进行绘制; 3. 拆分成绘制指令,生成绘制列表,提交到合成线程; 4. 输出:绘制列表。 分块 1. 合成线程会将较大、较长的图层(一屏显示不完,大部分不在视口内)划分为图块(tile, 256*256, 512*512)。 光栅化(栅格化) 1. 在光栅化线程池中,将视口附近的图块优先生成位图(栅格化执行该操作); 2. 快速栅格化:GPU 加速,生成位图(GPU 进程)。 合成绘制 1. 绘制图块命令——DrawQuad,提交给浏览器进程; 2. 浏览器进程的 viz 组件,根据DrawQuad命令,绘制在屏幕上。 相关概念 重排 1. 更新了元素的几何属性(如宽、高、边距); 2. 触发重新布局,解析之后的一系列子阶段; 3. 更新完成的渲染流水线,开销最大。 重绘 1. 更新元素的绘制属性(元素的颜色、背景色、边框等); 2. 布局阶段不会执行(无几何位置变换),直接进入绘制阶段。 合成 1. 直接进入合成阶段(例如CSS 的 transform 动画); 2. 直接执行合成阶段,开销最小。展开共 2 条评论198
- Hurry2019-08-18减少重排重绘, 方法很多: 1. 使用 class 操作样式,而不是频繁操作 style 2. 避免使用 table 布局 3. 批量dom 操作,例如 createDocumentFragment,或者使用框架,例如 React 4. Debounce window resize 事件 5. 对 dom 属性的读写要分离 6. will-change: transform 做优化展开
作者回复: 总结的很好,很有经验👍
共 16 条评论191 - Luke2019-09-27关于浏览器渲染的知识点讲的很细致,我想问下,关于浏览器的渲染细节的知识老师是从哪里学到的?,是通过研究源码学习的吗?有没有一些好的学习资料或者学习方法推荐?能否专门出一篇“授人以渔”的文章,谢谢!
作者回复: 主要几个途径: 1:chromium源码 2:chromium源码里面的一些注释和文档 3:还有油管上blinkon上有一些深入讲解内核的视频 目前基本没有系统介绍浏览器知识的文档,而且网上很多文档还是比较早期的,很多内容都不太适合新版的浏览器了。 这里将浏览器知识和前端系统下结合起来是一件工作量非常大的事。
108 - 朙2019-08-17渲染进程里的帧的概念是什么样子的呢?一个page是一帧吗
作者回复: 可以拿放电影电影来解释,通常,电影的帧速是24,也就是说每秒切换24幅画面,其中的每幅画面就是一帧。 理解什么是帧后,我们在回过头看看我们的页面。由于目前大多数设备的屏幕刷新率为 60 次/秒。因此,如果页面中有一个动画、或一个渐变效果、或者用户正在滚动页面,那么浏览器渲染动画的频率至少要和刷新频率保持一致,也就是每秒需要更新60次,这样我们就能计算出来生成每帧的预算只有(1/60)毫秒,也就是16毫秒多一点(1 秒/ 60 = 16.66 毫秒)。如果超过16毫秒,帧率将下降,并且会出现画面抖动现象,此现象通常被称为卡顿,会对用户体验产生负面影响。 所以,如果想要保证画面的流畅,就需要尽量降低每帧的渲染时间,所以局部更新流水线显得非常重要了,能大大减少处理每帧所消耗的时间。
共 6 条评论56 - mfist2019-08-17减少重排重绘,相当于少了渲染进程的主线程和非主线程的很多计算和操作,能够加快web的展示。 1 触发repaint reflow的操作尽量放在一起,比如改变dom高度和设置margin分开写,可能会出发两次重排 2 通过虚拟dom层计算出操作总得差异,一起提交给浏览器。之前还用过createdocumentfragment来汇总append的dom,来减少触发重排重绘次数。展开
作者回复: 很赞
共 3 条评论39 - Geek_East2019-11-28渲染流程的最后,应该是浏览器进程将Compositor Frame发送到GPU, GPU进行显示吧?
作者回复: 这块我没深入将了,因为结构比较复杂,chromium团队还在重构大的架构,既然你问到了,我就简要介绍下: 1:首先渲染进程里执行图层合成(Layer Compositor),也就是生成图层的操作,具体地讲,渲染进程的合成线程接收到图层的绘制消息时,会通过光栅化线程池将其提交给GPU进程,在GPU进程中执行光栅化操作,执行完成,再将结果返回给渲染进程的合成线程,执行合成图层操作! 2:合成的图层会被提交给浏览器进程,浏览器进程里会执行显示合成(Display Compositor),也就是将所有的图层合成为可以显示的页面图片。 最终显示器显示的就是浏览器进程中合成的页面图片
共 4 条评论25 - 杨陆伟2019-08-17最后的一段话非常经典,赞!大道至简,这真是做软件该秉持的原则,如果实现功能时感受到复杂和无序,那一定是那里错了
作者回复: 说的好,如果感觉到复杂和无序,那一定是哪里错了
25 - ytd2019-08-17请教下老师,canvas的渲染流程是什么样的呢?它不涉及dom,也就不涉及dom树、样式计算、布局、分层,canvas的绘制过程也是在渲染进程中进行的吗?
作者回复: canvas绘制流程很简单,就是调用api直接在画布上绘制,没有DOM,也没有太多套路! 所有的绘制都是自己程序控制的
共 6 条评论17 - tokey2019-08-17老师您好! 我想问以下两个问题: 问题1:手机端开发,body 被内容撑开了,超过一屏,在滑动的过程中会不会触发重排,为什么? 问题2:如果 body 高度设置了100%
作者回复: 现代浏览器做了优化,把滚动操作交给了合成线程来处理,也就是说滚动的内容会被当成一个单独的图层,发生滚动的事件的时候,图层直接由合成线程来生成,也就是说这种情况下没有占用主线程,所以通常情况下不会产生重排和重回操作,只是简单合成就可以了,这样效率是最高的! 为什么说“通常”呢? 这是因为目前渲染流程还是很复杂的,在滚动页面时,有些情况下,如果合成线程搞不定的,那么还要交给主线程去处理,这时候就涉及到重拍了,不过技术是往前发展的,渲染流程会变得越来约简单高效!
共 4 条评论11 - 番茄2019-10-14最后一部分,合成和显示讲的太模糊的,不是很理解。共 1 条评论10
- Luke2019-09-10老师,你好,我有几个问题一直都很很困惑,也没找到答案,希望老师能解惑一下,感谢! 1、图层、图块与BFC有什么区别联系吗?为什么BFC内元素的变动不会对BFC外的元素产生任何影响?是因为BFC会产生一个独立的图层或图块,渲染的时候只用重新渲染这一个图层或图块吗?BFC的原理是什么? 2、在划分图层的时候,每个图层都会生成一系列的绘制指令,而在划分图块的时候,一个图块可能包含多个图层,一个图层也可能分成多个图块,那么在将图块绘制成位图的时候,是如何执行绘制指令的?需要将绘制指令再划分到不同的图块中吗?展开共 2 条评论10
- 不存在的2019-10-20什么叫既不要布局也不要绘制的属性呢?
作者回复: 比如CSS3的实现的一些动画效果
8 - splm2019-10-12在GPU进程完成栅格化,并把结果保存在GPU内存中,此时的结果仍然保存在独立进程中。那么从渲染进程的合成线程发送Drawquad命令到浏览器主线程调用Viz组件,主进程是在什么时候拿到之前存在GPU内存中的位图结果的?是Viz主动去GPU内存获取这部分结果进行合成的吗?这里没太看懂。共 3 条评论8
- Fred 鱼2020-06-20对于使用transform的元素,要事先定义好will-change:transform; ,才能避免layout 和paint。共 1 条评论5
- Warrior2019-09-20重排是否只在当前分层中,会不会影响其他分层的重排?
作者回复: 目前会的,不过未来应该会解决这个问题
5 - 悬炫2019-08-26老师文中说需要剪裁(clip)的地方也会被创建为图层,但是我复制了老师的代码后,发现需要剪裁的地方并没有单独的被创建为图层,难道是最新版本的谷歌浏览器改了渲染规则? 我的浏览器版本是 76.0.3809.100(正式版本) (64 位)共 7 条评论5
- 帅气小熊猫2019-08-19这里的合成线程属于哪个进程?浏览器进程是指主进程吗?前面进程线程那块没有啊
作者回复: 合成线程属于渲染进程,你可以看文中示意图! 浏览器进程是主进程,负责提供一些基础服务和调度其它进程,你可以回顾下第一节和第四节内容。
4 - 海盗船长2020-05-19“栅格化过程都会使用 GPU 来加速生成”,请问下老师,如果用户的电脑没有GPU,栅格化就使用CPU吗3
- 凭实力写bug2019-10-25我比较不明白的是为什么transform 会越过 layout 和 layer . 按照我的理解 transform 位置改变了不越过触发layout吗共 1 条评论3
- 板栗2019-09-18老师,是将所有图块都栅格化,还是刚开始栅格化只可视区的图块,滚动的时候再去动态的栅格化。共 6 条评论3