0

    前端:浏览器、GPU 工作原理简要及动画编程启示

    2023.05.13 | admin | 168次围观

    “最近作者在 VIPKID 企业内部做了一次关于‘动画增强技术方案’的分享,在原分享的基础之上,加入了对浏览器工作原理的考察,并补充动画编码启示若干,烩成此篇,欢迎讨论雅正。本文大约 3300 字。老外 2011 年写的浏览器原理,内容很丰富,单击原文可查看。”

    目录

    01

    页面为什么会慢,动画为什么卡顿?

    因为页面复杂吗?

    很多页面元素多、结构复杂,动画炫酷的网站,同时也很流畅。

    是用户的机器性能差、网络环境差吗?

    同样的终端,为什么竞争对手的产品可以脱颖而出。

    同样的市场,条件是相似的、机会是均等的,在有限的资源和受限的条件之下,谁能深研软硬件原理、浏览器页面解析与渲染原理,谁才能将技术能力的边界拓展到最大。

    那么,HTML 页面为什么会慢,动画有时候为什么会卡顿?

    这要从浏览器的工作原理(甚至包括 GPU 的工作原理)讲起。

    当我们在浏览器的地址栏输入:

    到眼睛看到页面,浏览器与电脑系统做了哪些事情呢?

    以 Chrome 浏览器为例(以下同):

    1)从谷歌 CND 节点加载 HTML 页面内容,这是一堆 HTML 标签,可以还包括一些样式表、Script 标签等 URL 资源

    2)浏览器进行词法解析,将一个很长的字符串(页面内容),分割为一个个有意义的标识符,这一步英文称之为 LexicalAnalysis

    3)浏览器进行语法解析,将上一步解析出来的词法内容,依据语法定义,解析为 DOM 树,这一步英文称之为SyntaxAnalysis

    4)与 2、3 同步进行的还有样式解析,将 CSS 内容解析为一个样式规则树。因为样式一般都有继续关系,所以样式规则(Style Rules)像一颗树,如下所示:

    但需要注意的是,浏览器对整个 HTML 页面的解析,是单线程的,如果页面引用的样式在 Style Sheet URL 中,页面解析暂停,要等 CSS 加载完毕。所以有些网站,都是直接把 CSS 内嵌在 HEAD 内甚至 HTML 元素中,以此提高解析与渲染速度。

    5)将2、3、4 步解析出来的 DOM 树与 Style Rules 规则树,合并在一起,组成一棵渲染树(Render Tree)

    6)Layout,中文称回流。因为渲染树虽然有了,但是位置和大小信息还没有;我们都知道 HTML 页面支持流式布局,上面的内容会影响下面内容的位置和大小,所以这一步是必要且关键的

    7)开始渲染,将准备渲染的内容(立体的存在于内存中的对象)发送 GPU 处理,CPU 接管后干了6 件事,第一步是顶点着色器,第二步是图元装配,第三步是几何着色器,如下所示:

    在这三步中,GPU 完成了立体的渲染结构的构建,简单的、矢量图形拥有更快的处理速度。更少的顶点,意味着更少的细节,如下所示:

    8)光栅化。这一步是屏幕渲染的关键,如果所示,3D 空间的颜色数据映射到了屏幕上一个个像素点。这个像素点数据量惊人,在 iPhone5 的液晶显示器上有 1,136×640 = 727,040 个像素,在 15 寸 Retina MacBook Pro 上,这一数字达到 15.5 百万以上。

    9)片断着色器。这一步包括横向裁剪,超出屏幕之外的所有元素都将被丢掉。此时使用纹理位置,可以显著提高渲染速度。

    使用位图纹理意味着更少的 GPU 计算和更快的渲染速度,且画面感更真实,但同时内存占用更多;内存占用多预示着资源多,资源多意味着更大的带宽。在满足设计需求的前提下,如果使用矢量图,并且在进行时对矢量图进行 flatten 位图化,这不失为网络应用最好的动画资源组织方式之一。在社游时代,大部分游戏的 UI 都是矢量的 Q 可爱风。

    10)测试与混合。网页里实现的透明度,是在这一步达成的,这里有一个公式:

    R = S + D * (1 - Sa)

    假设有两个像素 S(source) 和 D(destination),S 在 z 轴方向相对靠前(在上面),D 在 z 轴方向相对靠后(在下面),那么最终的颜色值就是 S(上面像素) 的颜色 + D(下面像素) 的颜色 * (1 - S(上面像素) 颜色的透明度)。

    透明效果绘制,在底层其实是颜色的叠加;如果可以的话,要动画中要减少使用 alpha,而直接使用计算合成之后的颜色值,这样底层绘制时就少干了一份活。不要以为多一点少一点没有关系,性能瓶颈都是在一个一个小的点上积累造成的。

    看,人类简单的在屏幕上这么一划,机器噼里啪啦干了一堆脏活累活。现在回到我们最初的问题上来,HTML 页面为什么会慢,动画为什么会卡顿,就是因为上面这个过程中,某些点反应迟钝了,效率低了。

    那么,有没有办法优化,答案肯定是有的。从原理上着手,在一些影响渲染性能的节点上注意一下,就能显著改善渲染效果。动画,是单屏图像的连动效果,渲染效率提升了,动画自然就不会卡顿了。

    02

    具体的前端页面优化技巧有哪些?

    减少一次渲染机器所要做的工作内容,就能显著提高渲染效率。重新审视下页面解析及渲染的流程:

    前端:浏览器、GPU 工作原理简要及动画编程启示

    Layout 与 Paint 是最影响渲染效率的节点,任何页面元素大小及位置的变化、JS document.write 脚本的执行,都会引发 Layout(回流)及 Paint(重绘)。以下技巧可以考虑,减少这些流程:

    1)使用虚拟 DOM。VUE 及 React 都使用了这一技术,UI 元素的变化,改变的是虚拟 DOM,然后框架负责统一将改变批量提交给浏览器。

    2)批量绘制

    对单一组件的样式修改,使用 cssText 达成批量模式,如下所示:

    const el = document.getElementById('test');el.style.margin = '5px';el.style.width = '100px';el.style.borderRight = '2px';

    这些代码,建议使用 cssText,改写为:

    const el = document.getElementById('test');el.style.cssText += 'margin: 5px;width: 100px;border-right: 2px; '

    除了使用 cssText 达到批量效果,还可以使用将元素设置为 display:none,设置为 none 之后,元素就不再参加 Layout 与 Paint 了。等所有属性设置完了之后,再将 display 改回来。

    3)缓存计算属性。例如这段代码:

    for (let i = 0; i < elment.length; i++) { elment[i].style.width = box.offsetWidth + 'px';}

    适合改写为:

    const width = box.offsetWidth;for (let i = 0; i < element.length; i++) { element[i].style.width = width + 'px';}

    offsetWidth 获取的是元素的物理宽度,看下面这个盒模型:

    offsetWidth = width + padding + border;(不包括margin)

    为什么 offsetWidth 是一个只读属性,因为它是根据当前页面的渲染树计算出来的一个数值。浏览器作了优化固然会缓存这个数值,但当页面滚动、变化后,它又要重新计算了。

    与 offsetWidth 类似的所有属性,包括 offsetHeight、offsetLeft、offsetTop等,在使用时都要注意。

    4)使用 transform 与 opacity实现动画

    动画改变的就是元素的位置、可见性等属性,要使用 translate 代替 left、top 等改变位置属性,使用 opacity 代替 visibility。为什么?

    因为这两个属性不会触发 Layout(回流)。浏览器渲染有全量模式与增量模式,这两个属性引发的变化,只会带来增量模式的更新;此外,如果 GPU 参与工作了(事实上今天大部分设备,包括手机都有 GPU),或者说页面是有 WebGL 加速的,可能浏览器还做了近一步的优化,还会跳过 GPU 渲染中的前 4个步骤,而直接是从第 5 片断着色器、第 6 测试与混合开始工作的。

    一下子少干这么多活,效果一点没少,渲染效率能不高吗,动画怎么会卡顿呢?

    再给朋友们看一张图:

    这张图展示了前端页面中实现动画的 5 种方案,其运行效率的对比数据。第 1 和 第 4个方案,因为使用的是序列帧动画方案(或称 Sprite 雪碧图动画),如下所示:

    这种动画方案效果不细腻,想增加光滑度就必然增加资源大小,PASS。第 2 种方案实测帧频数低,PASS。

    留下的只有第 3、5 种方案。为什么这两种方案快一些,因为它们没有 Layout 与 Paint浏览器工作原理是怎样的,直接 Composite 后送给了 GPU 处理。

    那么使用这两种技术方案的优秀框架有没有,需要自己开发吗?

    答案是不需要,有大牛已经造好轮子了。js + transform:translate3d() 方案的代表是:

    js + Canvas 技术方案的代码是 Egret。Egret 由前 Adobe 平台技术经理七月联合打造,是一款工具丰富、能力强悍,且风格兼容 ActionScript 语言的优秀引擎。Egret 提供了 Wing3 代码编辑器、Egret Feathers 粒子动画编辑器、DragonBones 骨骼动画编辑器等工具,在拥有不俗的运行效率的同时,还拥有优秀的生产效率,值得一试。附地址:

    5)使用 will-change

    .moving-element { will-change: transform;}

    will-change 是告诉浏览器,这个元素接下来准备要变了,然后浏览器就可以在一个渲染周期内,将所有标记为 will-change 的元素合并为一个层,这即是上文提到的 Composite 阶段。虽然不是 Canvas,是松散的 HTML 元素,但通过这个属性,让一众组件像在一张 Canvas 之上一样,统一绘制,哪些提高了渲染效率。

    在使用时要注意,如果元素不再变化了,要适时移除 will-change 样式,如下所示:

    let el = document.querySelector('.element');el.addEventListener('mouseenter', hintBrowser);el.addEventListener('animationEnd', removeHint);function hintBrowser() { this.style.willChange = 'transform';}function removeHint() { this.style.willChange = 'auto';}

    大概就以上这些技巧,在使用时不能拘泥不化浏览器工作原理是怎样的,要在明白原理的前提下使用,否则可能带来负面效果。

    参考链接:

    — END —

    2019 年冬天于北京

    往期精选

    ▼ 了解作者▼

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    标签: 前端开发
    发表评论