总结几种 web 中的动效实现方案

最近用 electron 写了一个动效播放程序玩,用于播放一些 mp4 动效或其他封装格式(如 yyeva,svga)的动效。在此记录我关于 web 中动效实现方案的理解。

先贴一软件截图:



何为动效

动效从世俗层面理解,我们通常会联想到以下场景:

  1. 真人电影中的特效(例如指环王/美国大片如变形金刚等里面的一些火焰或大型远景,甚至国产仙侠剧的一些背景或打斗场景)。
  2. 小时候看过的动画片。表现为 2d 平面的物体的移动、变换。
  3. 电子设备中看到的动来动去的元素。例如操作系统层面交互时候的动画—例如桌面 cpu 温度小球的水纹波动;例如网页上交互时或者没有交互时就在转动的粒子。
  4. 直播间别人送礼或者进场时候的动画效果。

故,所谓动效,从本质理解,即但凡是包含视觉上运动的元素的东西,都可以叫动效;完整的说应该叫“动画效果”。因为相比于“不动”的东西,他具有了某些元素的“运动特征”,可能是从 a 点移动到 b 点,也可能是按照某种更复杂的路径在位移变换,也可能是变换尺寸和几何形状。

我来根据自己的理解,给出一个不完全准确的定义:动效,就是一堆元素的集合,且这些元素集合中的任意元素随着时间推移或在特定事件发生后可以变换形状、尺寸,或运动改变自身位置。

应用场景

如上一节所示,动效可以用到以下场景:

  1. 直播间送礼,增强直播间氛围
  2. UI 界面上,给平面的 UI 界面增加更多动感感受。
  3. 电影或电视中的打斗/战争/碰撞/仙侠等夸张场景。
  4. 游戏。你会发现一个游戏应用,就是一场“动效”的盛宴。他本质上就是 n 个“子动效”,随着时间推移,各自元素的按照自身的动效效果进行持续变换的一场“动效变换”。
  5. 动画片。你会发现动画片就是一个“游戏应用”。唯独动画片中的元素全部是靠“时间推移”而变换的,而游戏中的某些元素不仅是依赖“时间推移”,还依赖你的“鼠标或键盘事件”来变换其尺寸形状或位移。

web 中的动效底层技术

在讲述上层方案之前,我们先从底层搞明白,到底在 web 浏览器技术场景下,我们可以利用哪些技术来实现“动效”的概念。

实现静态元素的方法

要想实现“运动和变换尺寸形状”的前提,就是先有一坨“元素集合”。因此,我们首先要讨论的就是如何实现静态的元素本身。

在 web 浏览器中,能绘制出静态 UI 内容元素的技术有:

  • 原生 dom。
    由代码驱动浏览器绘制,矢量的。
  • svg。
    由代码驱动浏览器绘制,是矢量的。可以支持复杂的点线面等绘制指令操作,因此比 dom 要更容易绘制出复杂图形。
  • 图片(gif、png、apng、webp、jpeg)。
    由像素帧组成,直接往显卡绘制像素。并非实时由代码去实时绘制路径,因此单帧绘制性能高且支持更逼真的效果。
  • canvas
    通过 cpu 或 gpu 来靠编程指令来实时绘制 2d 或 3d 的元素。

实现运动和变换

本文咱们先不讨论矢量非矢量问题,我们着重讨论元素如何动起来。 而从最通用的动画实现原理上,我认为可以将动画“动起来”的实现分为“补间帧”和“序列帧”两种。

  • 补间帧。就是你动画数据中只定义关键帧;而在渲染时刻,通过计算机实时计算来得到 A 和 B 两个关键帧之间的补间帧,从而完成所有帧的绘制。
  • 序列帧。就是你的动画数据中已经把所有帧内容全部存储好了,渲染的时候就是拿出这些预先设计好的图像,按照特定的时间点去展示该帧图像。从而形成动的效果。小时候玩的那种“动画书”就是类似原理咯。

结合上面所说的几种静态元素实现方法,我们可以依次说一下他们的动画实现方法:

  • 原生 dom。
    补间:可以通过js 计算或css 的 keyframe/transition实现补间动画渲染。
    序列帧:可以通过js切换某元素背景图这种方式,在 dom 上实现逐帧渲染提前准备好的png/jpg 图,从而实现基于 dom 的序列帧图像变换动画。
  • svg。
    补间:可以通过js 计算或css 的 keyframe/transition 或 svg 支持的其他动画标记 来实现补间动画渲染。
    序列帧:由于 svg 是个矢量描述,其内部无法实现所谓序列帧。除非你把整个 svg 结构看成一个帧,然后提前准备 n 个 svg 数据,然后通过 js 去更换整个 svg,这就类似于上面所说的“原生 dom 通过 js 换背景图”差不多了(这种图片切换方式,完全可以用下面的图片封装 gif/mp4 实现,所以就暂不赘述了)。
  • 图片(gif、png、apng、webp、jpeg)。
    补间:由于是位图逐帧渲染,所以没有所谓实时的补间渲染。(倒是有可能在制作 gif 的时候,可能是通过软件里的”关键帧+补间”给制作的,然后导出成 gif 等格式,但是从 web 渲染层面看,他已经不存在补间概念了)。
    序列帧:图片中的 gif/webp/apng 是可以把多帧图片封装到格式数据中的,从而在播放的时候实现多帧播放的动画效果。另外还有视频技术例如 mp4 其实就等同于 gif/webp,你可以认为原理一样,都是按序播放图片的序列帧动画技术。
  • canvas
    补间:由于 canvas 是一个可编程的画布,他既可以编程绘制内容;又可以编程直接往画布上贴图。因此就像上文所说的 dom 似的,你如果是手工编程绘制内容,且 js 计算并控制 A 到 B 之间的”随时间变换规则”,那你就等于实现了一个补间动画。
    序列帧:canvas 可以支持贴图,所以你 js 控制贴图随着时间来变换,就等于实现了序列帧动画。

动画效果呈现能力

  • 原生 dom。
    一般般,也就是 css 自身有哪些能力你就能实现哪些效果咯。可能也就是形变、位移之类的,也能画出一些复杂效果,但是总体达不到电影级别效果。毕竟就是简单编程点线面绘制。
    至于你用 dom 贴图的话,那就看下面图片格式就行了。
  • svg。
    一般般,比上面 dom 多了一些标签能力和动画能力。能实现的多了很多。但跟电影特效级别肯定还差一些。
  • 图片帧技术(apng/webp/gif/mp4)。
    这种位图 逐帧播放,都可以用相机直接捕获实景,所以理论上啥效果都能实现。
  • canvas
    既可以编程又可以贴图。由于能贴图,那实现的效果就看上面图片类型的效果了。

总体上看,那些复杂的光效粒子等效果,基本上就只能靠位图+逐帧播放的“序列帧形式”才能实现。即底层要走上面所说的“图片帧”技术(apng/webp/gif/mp4)。然后在此格式基础上可以选择用浏览器直接播放,还是说用 canvas 贴图方式来播放。

次之,也可以用 svg 实现一些稍微复杂一点的效果。

透明度问题

在 web 动效领域,涉及到一个关键问题时透明度是否支持的问题。这也决定了上述四种动画技术的应用场景。这里我分析下透明度支持情况:

  • 原生 dom。
    补间:本来就是手工编程绘制的,所以支持透明度。
    序列帧:你填充贴图的话,是否透明就取决于下面要讲的图片是否支持透明了。
  • svg。
    补间:是矢量,且实时编程绘制,所以支持透明。
    序列帧:没有序列帧。
  • 图片帧技术。
    补间:没有补间。
    序列帧:其中 apng,webp 支持透明。gif 不支持透明。视频的话有些格式有透明通道,但已知 mp4 也缺少透明通道。
  • canvas
    补间:由于是编程绘制,所以支持透明。
    序列帧:由于是贴图,所以就看图片是否支持透明(即图片是否有透明通道)。

总结:

  1. 支持透明的动画:svg 技术、图片帧技术中的 webp/apng。 而 dom 和 canvas 中要看情况,贴图就以贴的图为准。
  2. 不支持透明的动画:图片帧技术中的 gif、mp4. 而 dom 和 canvas 要看情况, 贴图就以贴的图为准。

企业实践情况

在实践中,开发者选择技术方案时候通常会考虑 “是否能实现我 设计软件 AE 中的效果” ,以及 “是否能支持透明度”。
所以,我就用两个大场景来区分:

小点缀动画

基于上文讨论,这里可以选择 “原生 dom 技术-补间”、“ svg 技术-补间”、“图片帧技术-如 gif 序列帧”。 都可以。

然后根据透明度要求,再择优按需选择即可。

倒是没太有必要使用 canvas 技术,因为一个小的点缀动画,没必要搞那么复杂。

电影级别特效

要想实现原始设计软件中的效果,基本上只能走位图序列帧形式,即只能直接将 AE 软件将动画设计出来,然后导出成序列帧(例如以 webp/gif/apng/mp4 承载的序列帧)。 然后 web 开发拿到浏览器中进行图片播放或视频播放或 canvas 贴图播放。

于是,这必然只能采用上文我讲的“图片帧技术”或“canvas 技术”。 而其中 canvas 技术其实依旧要选择“canvas 贴图”,所以综合看其实本质上就是采用图片帧技术——gif/webp/apng/mp4。

然而在礼物送礼动效方面,大多是有透明度要求的,于是上面所采用的方案呈现出透明度方面的优缺点:

  • gif。 不支持透明,无法使用。色彩较少,呈现质量不好。
  • webp/apng。支持透明度,可以在某些场景下使用。但是由于他存储动图时候体积大且无声音且渲染采用 cpu 等问题,不太能承载时间长一点的动画。
  • mp4。不支持透明,无法直接使用。色彩丰富,电影级别。

于是,基于上述“透明度+色彩和效果要求+体积和性能要求”,行业内普遍倾向于采用 Mp4 视频格式,并通过一种特殊手段解决了 mp4 没有透明通道的问题。 这个特殊手段就是:

  1. 给 mp4 视频图像中,每一帧图像的右侧额外放一个纯 aplha 透明通道图,作为透明通道数据。
  2. 由于修改了 mp4 图片帧,所以不能直接用播放器来播放视频,而是改成自己手工读取帧之后再 canvas 里面贴图逐帧渲染—恰好就是我上文讲的 canvas 序列帧技术。
  3. 新设计一种格式,里面除了存放 mp4 信息,还放一点可以实时渲染的轨迹或遮罩等描述信息,用于附加动态视觉效果。

上述方案的典型封装实现,如:腾讯的 VAP,以及 YY 的 yyeva。

混合技术

PAG 和 SVGA 应该算是一种混合技术。里面既有实时绘制的矢量描述内容,又有位图内容;里面既有补间计算描述,又有序列帧存储。

看起来 pag 和 svga 要比上一节提到的“vap 和 yyeva 视频播放方案”要更灵活且效果更好? 实则不然,实际上业务中不仅要考虑灵活性和效果实现能力,而且要考虑性能、体积等因素。

总体上看,其实我感觉:

1、svga 和 lottie 和 pag 具有更多的动态能力。 其中 lottie 是纯矢量(svg 和 canvas 渲染),效果能力有限。而 svga 和 pag 是混合技术,矢量实现不了的还可以靠位图和序列帧实现。但可能由于要自己实现对图片帧的播放,会导致性能差一些,以及存储容量大一些。 另外他们由于里面可能是矢量描述为主,所以动态操控能力特别好。
2、vap 和 yyeva.由于借助了视频编码,所以压缩效果好体积小;但是他播放的时候其实依然是用 canvas 来绘制帧序列,在帧序列渲染时候的性能估计跟 svga/pag 也差不多。 这俩格式是以位图为主,所以几乎没有要实时计算的东西,所以渲染性能可能要好一点。

选型

整体看下来,我觉得选型可以采用如下的递进思路:

  1. 最简单的,连 AE 都用不到的。你可以自己直接 svg 或者 dom 实现一下子就完事了。而且能实现动态变更里面的动态内容。
  2. 稍微有点复杂的 AE 动画,但是特别的短小精悍且不需要动态改里面的内容。那就 gif 或 webp 或 apng 搞吧。那如果要动态改里面的内容,就用 lottie 或者 svga。例如红包雨之类的。
  3. 稍微复杂一点的 AE 动画,又要动态改里面的内容,又要里面还有部分特别复杂的光效等位图元素。例如电商活动页倒计时。这种可能里面还有点位图元素。 于是咱们用混合技术:pag(当然了,svga 其实也行,也能混合位图)。
  4. 电影级别特效了。而且几乎不需要编程动态改里面的东西,但要严格还原 ae 效果。那么就直接搞视频播放吧—性能高,少一些矢量渲染计算。那就用 VAP/YYEva 吧。

下面是我询问 GPT 的一些答案:

附录

mp4 视频格式播放原理

视频中元数据会存储一个大概的帧率数据(并不一定完全准确,有的视频是可变帧率)。而帧率是播放器用来显示大概的总长度、分配大概缓冲区、seek 快速定位进度等用处;而播放器播放的时候,是靠读取视频中每一帧图像看该图像的“期望播放时间戳”,从而按照特定时间位置来播放这一帧图像。 最终实现视频的播放。

gif 播放原理

而 gif 是靠每一帧上附加一个延迟标记来告诉播放器“一个帧播放多久”,因为他比较简单,也不需要播放器做什么进度啥的。他的帧率可以大概靠 1000ms/每帧延时时长 算出来。