浏览器渲染原理

浏览器渲染原理

当我们使用浏览器打开一个网页时,此时浏览器会经历 html解析、样式计算、布局、分层、绘制、分块、光栅化、画(呈现) 渲染步骤,这就是本篇所讲的浏览器的渲染原理了,实际上也就是整个页面渲染经历的流程了

渲染前做的事: 实际上浏览器打开新页面时,会校验处理用户输入的内容,对于访问的web服务进行域名解析,再连接我们的 web 端服务,并下载html文档,拿到下载的html文档后,才开始后续渲染的步骤

下面就开始介绍浏览器渲染原理:html解析、样式计算、布局、分层、绘制、分块、光栅化、画(呈现)

html解析

当浏览器开启网络线程下载,并获取到html文档后,会将html渲染任务放到我们渲染进程的消息队列中,会唤醒渲染主线程的事件循环,从消息队列获取渲染任务,便开始我们的html解析过程了

html文档是一堆字符串,操作起来非常方便,html 解析的过程,实际上就是将 html文档 转化为 dom、cssom 对象的过程

dom:document Object Model 也就是文档对象模型,这也是浏览器提供给我们,能够访问与改变html的接口

cssom css Object Model 也就是 css 对象模型,生成的 css 对象模型会放到 stylesheetlist 中。其中包含: style 内部样式、link外部样式、标签行内style、浏览器样式 style 的信息

为什么我们常说 dom树、cssom树

学过树结构的人都明白,他们实际上是一个倒立的树,有一个根节点很多叶子节点,就像一棵树有树根、树杈一样

html的解析过程

html 解析工作主要在渲染主线程中运行,拿到 html 文档后,会开始一步一步解析成dom,遇到外部css不会等待下载,而是继续向后解析,直到遇到外部script,则必须等待其下载完毕(已下载完毕则会直接取出解析执行),因为其可能会修改当前dom树,所以需要阻塞html的解析

与此同时,html 解析的过程为了提高效率,会开启一个预解析线程,其会快速浏览html文档,找到里面的 css 和 js 内容下载,其中下载的css会在预解析线程解析,不会阻塞主线程的解析,遇到外部css样式,则会下载,下载完毕后,将解析任务交给正在html主线程解析

就这样第一步html解析的过程完成了,浏览器获得了dom树、cssom树,其中浏览器样式、内部样式、外部样式表、行内样式都已经在 cssom树 中了

接下来开始 样式计算

样式计算

样式计算则是需要遍历 dom数,结合 cssom树,依次给里面的所有 dom 节点计算出最终样式(中间也包含css属性值计算),称之为 compute style,此过程会将一些颜色转化为rgb,相对单位也变成绝对单位,比如:width:100%;会计算出 px 单位

当然我们之前讲的 css属性值的计算过程 也就是样式计算过程中的一部分,其中包括确定声明值、层叠、继承、默认样式,也就是将重复设置、未设置的样式转化为具体样式的过程,这里不多介绍了

就这样生成了一个带有 css最终样式的 dom 数了

ps:getComputedStyle(dom) 就是获取到样式的计算后的 style 属性样式了,dom.style.width 则获得的是 dom 的初始属性

接下来进入下一步生成布局树

布局

布局的过程,就是将 dom树 生成 layout布局树 的过程,此过程也是用到了视觉格式化模型的知识,需要的话可以进去看看,为了避免篇幅过大,这里不介绍那么多了

浏览器会依次遍历dom元素,会计算出每个节点的几何信息,包含节点的宽高位置等,当然位置是相对于包含块来计算的,当然包含块也是视觉格式化模型的一个概念

计算出几何信息之后,就生成了一个新的布局树(layout树)了,这个布局树一般情况下和 dom树是不一样

  • 当 css 设置了 display:none 时,就意味着dom元素不显示了,因此,布局树中也不会存在此类元素;
  • 除此之外,伪元素节点也不会显示在dom节点中,但布局树中就会存在;
  • 还有我们视觉格式化模型中的行列盒等知识,对于纯文本信息,可能还会根据其行列盒,生成匿名行内盒、匿名块盒等,其包含我们的文本,因此实际布局树也会多出了一些节点

ps:为啥我们的 meta、head、script元素等没有显示,因为浏览器默认样式表中,他们就是display:none,所以布局树中也没有他们;还有我们的什么 div、span 等标签,默认都是没有样式的,这也是浏览器默认样式表中赋予的样式,所以也没必要吐槽一个 div 走天下的人了,也许人家比你懂得多,简单粗暴🤣

我们中间使用的类似于 dom.clientWidth 等,这就是从 布局树 中获取到的内容,因此计算出了实际的内容,当我们读取布局属性时,浏览器为了能够获取到精确的内容,会立即进行回流,重新计算生成新的布局树,这样才能获取到更为精确信息,因此也会影响性能,当仅仅是设置新的布局属性时,则不会影响,也会延迟到下一个时间循环,统一设置

生成 layout 树之后,就到了分层阶段了,这也是浏览器优化的手段

分层

分层这一步则是属于浏览器的优化策略了,也就是将我们的页面分为多个图层各自独立渲染

我们的页面如果内容非常多的话,每次都更新那么多内容,就可能会影响性能,因此浏览器出现了分层策略

  1. 浏览器的渲染主线程中会根据一系列复杂的策略,将页面分成多个部分,也就是多个图层,每个图层独立渲染
  2. 当一个图层的布局发生改变时,则仅仅会重新计算布局当前图层,其他图层则保持不变,这也就优化了渲染效率,毕竟需要渲染的内容少了

滚动条、堆叠(层叠)上下文等或多或少都会影响浏览器的分层策略,当然 will-change 这个大幅度手动影响分层策略,平时推荐使用浏览器的分层策略,默认不太建议使用,当页面出现瓶颈时,可以考虑使用 will-change 分层,来优化性能

js 复制代码
//will-change 例子,告诉浏览器某个及诶单的 transfrom 会发生改变
//可以最大程度将该节点分层出来,以减少渲染图层数量,提升效率
will-change: transform;

这里面的堆叠上下文(其他地方一般叫层叠上下文,一个意思),里面的 transfrom、opacity 等也可能生成层叠上下文,相信已经能理解一部分了,它会影响到分层,但不是全部因素,具体怎么分层还是看浏览器的处理结果

ps:过早的性能优化是万恶之源,不仅仅会浪费开发效率,可能还会造成性能浪费,甚至还可能造成一些后续维护问题(并不是所有人都知道这些知识点,性能评价卡在哪个步骤都还需要浪费大量时间分析)

分层优化后,下一步就到了绘制

绘制

绘制这一步内容就比较少了,实际上就是浏览器为每层缠身绘制执行集,后续会根据绘制指令集画出里面的元素,指令集则是描述了我们这一层是怎么画的,例如:将笔移动到某一个位置,然后开始画具体图形

就好比我们的 canvas,我们一笔一笔画成一个多边形,或者奇奇怪怪的图形

有了指令集就到了分块阶段了

分块

分块 就是将绘制的多个图层信息交给GPU合成线程处理,将每个图层分为更小的区域,也就是块,此过程会开启多线程进行分块,以提升分块效率,毕竟分好的每个块都是独立的,互不干扰

光栅化

合成线程分块后,开启光栅化过程

光栅化则是在GPU合成线程将内容绘制成一块块位图,用于显示到屏幕上,也就是一个一个像素的内容,并且会优先处理视口位置的块,然后会将其处理成大量位图信息(像素点)

ps:现在GPU的情况相信都了解,专门处理独立的并行任务的,并行方面性能要远超cpu,串行方面肯定比不了cpu,两个各有所长,cpu主要处理有序任务,可以处理异步,目前还会提供少量其他核心处理并行任务

当合成线程收到大量的位图信息后,会生成quad指引信息,指引信息包含了位图在屏幕的哪个位置,并且考虑到旋转、缩放等变形,然后就开始

由于变形发生在合成线程,因此效率相对比较高,这也是为何做动画,优先推荐变形的原因(然后如果后续涉及到实际位置等信息,则无需使用变形)

最后合成线程会把 quad指引信息 交个 GPU 进程,然后最后通过硬件传递到显示器显示出新效果

问与答

一段长任务,为什么有的时候会造成卡死?有的时候没有影响?

一段长任务,造成卡死,主要是因为任务执行太长,使cpu长时间在高速运行中,其他操作形成的任务无法得到及时执行,就造成了卡死现象

结合本篇和上篇文章内容,卡死实际上是用户表现上看到的页面点击无响应,动画不执行,实际则可能是因为渲染主线程任务执行过长,导致新的渲染任务延迟执行或者无法执行情况,就形成了视觉中的卡顿、卡死现象

如果执行任务很长,但是不在主线程,则主线程仍然能够响应用户操作,则不会出现卡死现象;如果执行任务很长还在主线程,但是更新的操作已经在在gpu等合成线程执行,那么即使主线程阻塞,那么也不会影响页面的动画效果,因此表象是没有卡顿

为什么我们的样式不生效?

在确保我们样式没有写错的情况,如果使用的是外部样式,有可能外部样式链接错误,加载出错,如果外部样式链接等正常,那可能是我们的样式在css属性值计算过程可能出现了层叠,被优先级更高的选择器或者 !import、行内style 等给抢占了,也可能优先级没问题,但是被别人同优先级后写的覆盖了

当然还有可能是我们对于css理解不够,隐藏了,位置不对等小原因😂

页面上的盒模型怎么变多了?

这是因为在生成布局树的过程,我们的 dom 可能设置了伪元素因此也是需要显示,文本元素外面也可能会添加匿名盒子等,因此增加很正常

ps:盒模型的一些知识,可以参考视觉格式化模型,里面有包含块、匿名盒子等相关知识,会根据css将文档中的元素变成一个个盒子等

transform的效率为什么有时候高?有时候就低?

transform 效率高是因为其在 gpu 的合成线程中执行的,并没有触发渲染线程中的属性计算、布局等流程,因此效率较高,且gpu多线程高并发的特性,导致其效率会偏高(比起正常left之类的偏移动画,又或者使用更差的计时器动画)

有时候效率低,大概是因为合成线程也感受到了压力,页面分层过多(使用transfrom过多也可能会导致页面分层过多,也会生成新的层叠上下文),导致合成线程压力过大,因此效率就低了,当然可能还有 will-change 的滥用,导致过渡分层,非必要可以减少分层因素

will-change是什么

will-change 标记了那个元素将会改变,浏览器会根据该属性对该元素进行分层,在一些场景优化性能,分层后,该层元素发生变化,只会重新渲染该层内容,从而提升效率,当然如果过度使用,或者场景不合适,反而会降低效率

读取style属性会引发回流吗?回流究竟是什么?

如果读取的仅仅是原始dom属性或者属性计算后 dom 的 style 属性不会引发回流,此时在属性计算前后,还未生成布局树,例如:dom.style.width,getComputeStyle(dom).width

但如果读取的是布局树的的的属性则会引发回流,因为此时已经生成布局树,频繁更新更新获取样式,则需要频繁更新布局树,因此浏览器会合并更新操作后统一回流,但是读取过程可能会出现错误

因此浏览区权衡下,更新操作不立即回流,读取过程会立即回流,以保证获取到的布局树信息是准确的

CSSOM究竟是什么?我们能读到它吗?能改变它?

是指的是 css 的 dom树,我们可以通过 dom,通过 dom.style 读取到他,并太改变他

合成线程是什么?它和主线程的关系是什么?

合成线程是分块、光栅化后用到的开始将图层绘制成像用到的其他线程,而主线程是我们渲染进程的主线程,主要在事件循环中处理消息队列任务,他们之间算是协作关系,主线程将分层绘制后的任务交给合成线程执行后续任务

分层和分块是什么

分层是将我们的页面根据滚动块、堆叠(层叠)上下文、will-change等因素分层,某一层发生改变只渲染那一层,从而优化页面渲染性能

分块则是将绘制的绘制指令集交给合成线程分割成更小的块,进行后续的光栅化等操作,以充分利用gpu并行特性

光栅化是什么?

光栅化是将我们图层分块后的内容,通过GPU,处理成位图的过程,也就屏幕的像素

link元素和script元素谁在阻塞页面?

link元素是链接的 css资源,不会阻塞,会在预解析线程解析, script 元素,则是必须在渲染主线程解析,其会阻塞主线程的执行,因为其可能会有更新页面dom的操作,所以需要等待执行

属性值的计算过程

css属性值的计算过程 也就是样式计算过程中的一部分,其中包括确定声明值、层叠、继承、默认样式,也就是将重复设置、未设置的样式转化为具体样式的过程,这里不多介绍了

什么是重绘

按照本篇所讲,重绘就是重新绘制,也就是根据分层信息重新生成指令集

这问题有些抽象,问的人基本上也是一知半解,实际上一些人理解的重绘应该是重新绘制页面,就是页面发生改变后,重新生成新画面的过程,实际上也就是根据我们分层后的信息,重新计算里面的属性、布局,然后合成出最终画面的过程

相关推荐
ElasticPDF-新国产PDF编辑器12 分钟前
React PDF Annotation plugin library online API examples
前端·react.js·pdf
Bruce_Liuxiaowei1 小时前
构建macOS命令速查手册:基于Flask的轻量级Web应用实践
前端·macos·flask
Python私教2 小时前
安装electron项目是为什么要执行postinstall script
前端·javascript·electron
shmily_yyA2 小时前
Nextjs15 实战 - React Notes之SidebarNoteList优化和Suspense的使用
前端·react.js·前端框架
知识分享小能手2 小时前
CSS3学习教程,从入门到精通, 化妆品网站 HTML5 + CSS3 完整项目(26)
前端·javascript·css·学习·css3·html5·媒体
了不起的码农2 小时前
ES6对函数参数的新设计
前端·ecmascript·es6
XH2763 小时前
Android 通知用法详解
前端
陈随易3 小时前
盘点23个Nodejs的替代品Bun的实用功能
前端·后端·程序员
uhakadotcom3 小时前
兄弟们,炸裂了!llama 4发布了!又有哪些创业公司被颠覆了?
前端·算法·面试
uhakadotcom3 小时前
EventEmitter3:高性能事件发射器的使用与优势
前端·javascript·面试