当前位置: 首页 > 科技观察

看完浏览器框架的原理

时间:2023-03-19 22:29:05 科技观察

本文转载自微信公众号“天天向上”,作者天天向上。转载本文请联系天天向上公众号。前言本文将介绍浏览器中Frame的概念及其过程。至于写这篇文章的出发点,我很好奇浏览器中的像素工作流程,什么时候开始的,最后的结果是什么。基于这些好奇心,查阅了一些外文资料,本文提供了一些参考资料,参考链接在文末。原因是在frame这个概念之前,我们要从后台说起,也就是页面渲染过程中有哪些关键路径。五个关键的渲染路径像素输出到页面,肯定是经过了很多过程。作为一名前端工程师,我们在工作中应该注意哪些点呢?这里有一个参考:渲染关键路径的五个主要部分应该是我们最值得关注的部分,因为我们对这部分的控制力最大。至于每个流程是怎样的,不清楚的可以参考下图:每个阶段的任务所以在这样一个像素流水线中,每个部分都可能造成卡顿,所以需要格外注意这些,毕竟那部分不合适,会开不必要的性能开销。三种输出方式我当时的问题是:是不是每一帧总是要经过pipeline各个部分的处理?事实上,情况并非如此。从视觉上看,流水线运行指定帧通常有三种方式。:通常有3种方式来指定帧的操作。如果我们用第三种方式更新视图,即改变一个既没有layout也没有draw的属性,浏览器会跳转到只进行合成。运行一个demo为了更具体的验证上面的过程,可以运行一个demo来验证一下。Demo地址:https://googlechrome.github.io/devtools-samples/jank/主线程我们添加多个dom元素做动画,效果更好更明显,然后我们打开Performance和Record的过程,我们需要注意Main选项卡,也就是主线程,当我们放大里面的Task时,我们有如下图:Task的详细流程也很清楚,UpdateLayerTree-->>Layout-->>绘画-->>复合图层。如果你对Performance中名字的含义不是很清楚,可以参考下面这篇文章,点这里:https://mp.weixin.qq.com/s/iodsGPWgYc97yWLb09Xk6A接下来,我们按下Optimize按钮,跟着之前的过程,录制之后,发现不对,还是这个子步骤,有问题,我很好奇,打开Sources面板,然后发现:unoptimizedanimation,其源码optimizedanimation,使用rAF,了解的人一定不陌生,可以简单理解为:逐帧重绘网页。这里是frame的概念,后面会解释。后面会整理rAF的详细介绍,大家可以持续关注。如何避免回流和重绘回到我们之前想象的点,如何保证可以直接跳转到合成过程,避免Layout和Paint?当然我们需要修改app.js中的update函数,使用transform:translateX(0px);做完动画,完成update函数的处理逻辑后,再记录一下:优化后的动画可以在Task子任务中找到,Layout-->>Paint,跳过了layout和drawing的过程。这就是为什么我们常说回流和重绘需要避免的原因。从主线程的角度来看,完全可以避免这些过程,可以避免大量的计算开销。这就是为什么经常看到这样的建议:坚持动画的变换和不透明度属性更改。使用will-change或translateZ来提升移动的元素。至于使用will-change和translatez来增强图层,这是另外一个知识点,这里就不展开了。至此,我们已经清楚地理解了避免回流和重绘的含义,那么我们提到的frame和rAF与渲染路径有什么关系呢?Frame首先是google了一下,然后维基百科给出了如下定义:在视频、电影、电视、数字视频等领域,可以看成是很多张随时间不断变化的图片,这里一帧指的是每一张图片。好吧,这不是很容易理解。找到这张图才解开了我的疑惑:frame这真是一张图胜过千言万语。从这张图可以理解,成就就是把像素放到屏幕上的完整过程。你一定被里面的一些关键信息弄糊涂了,这里做一些解释。以下内容大部分是翻译的,没有更多的总结,有兴趣的可以阅读原文。PROCESSES(进程)是映入眼帘的进程:RendererProcess:渲染进程。标签的周围容器。它由多个线程组成,这些线程共同负责使您的页面出现在屏幕上的各个方面。这些线程是Compositor、TileWorker和MainThread。GPU进程:GPU进程。这是一个为所有选项卡和周围的浏览器进程提供服务的单一进程。提交帧时,GPU进程会将任何图块和其他数据(例如4D顶点和矩阵)上传到GPU,以便将像素实际推送到屏幕上。一个GPU进程由一个线程组成,称为GPU线程,它实际完成工作。RENDERERPROCESSTHREADS(渲染进程中的线程)现在让我们看一下RendererProcess中的线程。CompositorThread:这是第一个被通知vsync事件的线程(这是操作系统告诉浏览器制作新帧的方式)。它还将接收任何输入事件。合成器线程将尽可能避免进入主线程,并尝试将输入(比如滚动抖动)转换为屏幕上的动作。它将通过更新图层位置并通过GPU线程将帧直接提交给GPU来实现。如果由于输入事件处理程序或其他视觉工作而无法执行此操作,则需要使用主线程。主线程:这是浏览器执行我们都知道和喜爱的任务的地方。JavaScript、样式、布局和绘画。(在未来的Houdini版本中,这会改变,我们将能够在Compositor线程中运行一些代码。)这个线程获得了“最有可能导致卡顿”的奖项,主要是因为这里运行了很多东西.(jank值得页面抖动)CompositorTileWorker(s)(复合图块光栅化线程):一个或多个从合成器线程派生的线程,用于处理光栅化任务。我们稍后再谈。在许多方面,您应该将Compositor线程视为“大老板”。虽然它不运行JavaScript、Layout、Paint或其他任何东西,但它是完全负责启动主线程工作然后将帧传送到屏幕的线程。如果它不需要等待输入事件处理程序,它可以在等待主线程完成其工作时发送帧。您还可以想象ServiceWorkers和WebWorkers生活在流程中,但我没有包括它们,因为这会使事情变得更复杂。事物的流程(主线程进程)让我们从主线程开始。主线程逐步引导我们完成从vsync到像素的过程,并讨论事件的“全脂”版本中的工作原理。值得记住的是,浏览器不需要执行所有这些步骤,这取决于必要的步骤。例如,如果没有要解析的新HTML,则不会开始解析HTML。事实上,很多时候,提高性能的最好方法就是简单地消除部分进程被触发的需要!另外值得注意的是,样式和布局下的红色箭头似乎指向requestAnimationFrame。完全有可能在您的代码中意外触发这两者。这被称为强制同步布局(或样式,视情况而定)并且通常对性能不利。FrameStart(开始新的一帧):触发垂直同步信号开始绘制新的一帧图像。输入事件处理程序(输入事件的处理)。-输入数据从合成器线程传递到主线程上的任何输入事件处理程序。所有输入事件处理程序(触摸移动、滚动、点击)都应该首先触发,每帧一次,但不一定是这种情况。调度程序会尽最大努力尝试,其成功率因操作系统而异。在用户交互和事件进入主线程进行处理之间也存在一些延迟。requestAnimationFrame:这是对屏幕进行视觉更新的理想位置,因为您有新的输入数据,而且它是您最接近垂直同步的位置。其他的视觉任务,比如样式计算,都是在这个之后执行的,所以它的理想位置是变异元素。如果你改变-比如说-100个类,这不会导致100次样式计算;它们将在以后进行批处理和处理。唯一需要注意的是,您不要查询任何计算样式或布局属性(例如el.style.backgroundImage或el.style.offsetWidth)。如果这样做,您将带来重新计算的样式、布局或两者,导致强制同步布局,或更糟的是,布局混乱。解析HTML(解析HTML):将处理任何新添加的HTML并创建DOM元素。您可能会在页面加载期间或在appendChild等操作之后看到更多此类信息。重新计算样式:样式是为任何新添加或变异的东西计算的,这可能是整棵树,也可能是一个范围,具体取决于更改的内容。这可以是整棵树,也可以缩小范围,具体取决于更改的内容。例如,更改主体上的类可能影响深远,但值得注意的是,浏览器已经非常聪明地自动限制样式计算的范围。布局(绘图):计算每个可见元素的几何信息(每个元素的位置和大小)。它通常对整个文档进行计算,通常使计算成本与DOM大小成正比。UpdateLayerTree:创建覆盖上下文和深度排序元素的过程。Paint:这是一个分为两部分的过程的第一部分:Paint是对任何新的或视觉上发生变化的元素(在此处填充矩形,在此处写入文本)的绘制调用的记录。第二部分是光栅化(见下文),其中执行绘制调用并填充纹理。这部分是绘制调用的记录,通常比光栅化快得多,但这两部分通常统称为“绘图”。Composite(合成):计算layer和tile信息,传回compositor线程处理。这将考虑到诸如will-change、重叠元素和任何硬件加速画布等因素。RasterScheduled和Rasterize:现在执行Paint任务中记录的Draw调用。这是在CompositorTileWorkers中完成的,其数量取决于平台和设备功能。例如,在Android上你通常会找到一个Worker,在桌面上你有时会找到四个。栅格化是以层为单位进行的,每一层由瓦片组成。帧结束:当每一层的图块被栅格化时,任何新的图块都会连同输入数据(可能已在事件处理程序中更改)一起提交给GPU线程。FrameShips:最后但并非最不重要的一点是,图块由GPU线程上传到GPU。GPU使用四边形和矩阵(所有常见的GL好东西)将图块绘制到屏幕上。总的来说,整个过程就是上面的。requestIdleCallback要这么说,我们还得拿requestAnimationFrame打个比方。requestAnimationFrame在重新渲染屏幕之前执行。上面说的rAF当时在优化动画,所以很适合做动画。如果你在主线程中通过Task搜索requestIdleCallback,你会发现它是在渲染屏幕之后执行的。查阅文章可以发现,通常是看浏览器是否空闲。【责任编辑:吴晓燕电话:(010)68476606】