前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >前端:浏览器、GPU 工作原理简要及动画编程启示

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

作者头像
LIYI
发布2019-11-18 21:38:42
1.6K0
发布2019-11-18 21:38:42
举报

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

目录

  • 页面为什么会慢?
  • 一些实用的优化技巧
    • 使用虚拟 DOM
    • 批量绘制
    • 缓存计算属性
    • 使用 transform 实现动画
    • 使用 will-change

01

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

因为页面复杂吗?

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

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

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

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

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

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

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

https://www.google.com

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

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

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

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

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

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

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

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

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() 方案的代表是:

https://daneden.github.io/animate.css/

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

https://www.egret.com/

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 年冬天于北京


往期精选

JS是如何计算 1+1=2 的?

愿我们多几个无需取悦的朋友

篆刻漫谈一二

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 艺述论 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档