阅读 4980

【得物技术】web端动效方案对比

前言

为了满足业务增长的需求,我们需要创意h5作为承载,增强app趣味、增加用户留存。而如果把创意h5归纳一下,会发现可以分为游戏类和主交互类,本篇文章会对主交互类创意H5的技术选型进行对比,抛砖引玉。

技术选型 - 我们有哪些选择?

web端动效的方式

  • 设计提供静图(jpg/jpeg/png(静态)/svg),通过设计口述或者作出动图,通过 create-keyframe-animation 或者手写,通过 css/canvas 来实现。

    • 优势:灵活,所有动效都尽在开发的掌握。
    • 劣势:开发时间长,沟通成本大,设计同学很难空口描述出想要的动效,也受开发的水平限制比较大。
  • 设计提供 gif,开发用图片的形式嵌入。

    • 优势:开发成本低,沟通成本小。
    • 劣势:gif 一般都比较大,比较小的噪点又过于明显,性能的性价比不高;不支持透明度;只能循环播放,较为死板。
  • 设计提供 apng,开发用图片的形式嵌入。

    • 优势:同 gif,体积较 gif 小,可以与 webp 的大小相对比,性能性价比相对高
    • 劣势:只能循环播放,较为死板。
  • 雪碧图序列帧,设计提供一个合成的雪碧图(静图),开发通过序列帧的方式进行动效操作。

    • 优势:开发成本中等,沟通成本小。
    • 劣势:合成的雪碧图文件大,且在不同屏幕分辨率下可能会失真。
  • 视频,设计提供不等数量的视频文件,通过播放视频来达成动效的效果

    • 优势:可以便捷的操作序列帧,开发成本小,相较 gif 体积较小
    • 劣势:移动端视频在不同 app、不同机型、不同系统的播放体验不大一样,尤其是 app 内,需要端侧做一些处理。
  • 使用 lottie,设计同学给出 json 文件和图片文件夹,开发同学引入 lottie 插件,对 json 进行解析。

    • 优势:开发成本中等,效果不受开发同学水平限制,只要设计画的出,开发就能实现出来;灵活,基点元素可以作为一个普通的 dom 节点进行定位,整个动画可以任意播放停止甚至倒放以及从某一帧开始播放(具体能实现的参见 api 文档),灵活度非常高。
    • 劣势:在开发层面和设计层面看到的帧节点以及播放速度不同,需要持续进行沟通联调,沟通成本大;lottie插件打包前 400+kb,打包后也有 200+kb,会显著增加项目的大小。
  • 使用 svga,设计同学给出 .svga文件,开发引入 svga 插件,对其进行解析。

    • 优势:理论上来说同 lottie
    • 劣势:实际引入中,存在“无故清除canvas画布”的问题,不稳定性极高,建议只使用在单纯播放的场景。

    目前,我们的动效基本通过 create-keyframe-animation+css、lottie、css+apng,这几种方案混合实现。下文我们简单描述一下这三种方案。

    方案简述

    create-keyframe-animation+css

    create-keyframe-animation 是一个很简单的动画库,主要简化了 css 动态插入的代码复杂度。

    举个例子:

2021419-143633.gif

在这个动效中,用户选择的卡片不定,也就是起点位置不定。给 dom 绑定动态生成的 css 是个繁琐的过程,尤其是牵扯到多少毫秒会有什么变化,如果使用 create-keyframe-animation,只需要写一份描述代码↓,再进行注册即可。

const animation = {
  0: {
    width: `${startWidth}px`,
    height: `${startHeight}px`,
    transform: `translate(${startLeft-targetLeft}px,${startTop - targetTop}px) rotateY(-20deg) rotateX(20deg) rotateZ(12deg)`
  },
  17: {
    width: `${startWidth * 1.5}px`,
    height: `${startHeight * 1.5}px`,
  },
  100: {
    width: `${targetWidth}px`,
    height: `${targetHeight}px`,
    transform: `translate(0,0) rotateY(360deg) rotateX(0) rotateZ(0)`
  }
}
复制代码

lottie

lottie是什么?

  • 是 airbnb 出产的一款动效插件,现在支持开发侧的 web/RN/Android/ios/Mac os,以及设计侧的AE。
  • web 端的文档地址:Web - Lottie Docs

lottie怎么用?

npm install lottie-web --save-dev
复制代码
// 此处以vue为例
import lottie from 'lottie-web'
Vue.prototype.$lottie = lottie
// 加载动画(遵从vue声明周期,这个调用需在dom节点加载出来,也即是mount之后)
let machineNormal = document.querySelector('#machineNormal')
this.normalAni = this.$lottie.loadAnimation({
    container: machineNormal, // the dom element
    renderer: 'canvas', // canvas / svg / html
    loop: true, // 自循环
    autoplay: true, // 自动播放
    path: 'static/machine_normal.json', // the animation data
})
复制代码

使用lottie的过程中,需要注意什么?

引入过程中

  1. lottie 动画加载需要一个 dom 节点,这个 dom 节点可被任意操纵位置,动画加载完毕后整个 dom 的宽高会被填充为动画的宽高,也即是设计给出的动画过程中最宽、最高的那个数值。
  2. 同样因为 lottie 动画加载需要一个 dom节点,所以加载动画的方法需在 dom 加载完成的声明周期或者之后调用。
  3. 如果使用 path 参数引入 json 文件:

文件放在本地:

  • 图片也需放在本地,通过修改.json文件的 assets 里面每一项的 u 属性,可以自定义图片文件夹的名字。

例如:

{
"v": "5.5.8",
"fr": 24,
"ip": 1,
"op": 53,
"w": 662,
"h": 827,
"nm": "",
"ddd": 0,
"assets": [
		{
		"id": "image_0",
		"w": 662,
		"h": 827,
		"u": "machine_img/", /* 这个就是文件夹的名字 */
		"p": "img_0.png",
		"e": 0
		},
 ]
}
复制代码
  • 如果是 vue-cli2 建议使用 static 文件夹存放,不受到打包的影响;如果使用的是vue-cli 3,那么放在public文件夹下,并且path直接写想要的文件名,例如:
this.lottie = this.$lottie.loadAnimation({
    container: document.querySelector('#BindDom'), // the dom element that will contain the animation
    renderer: 'svg',
    loop: true,
    autoplay: true,
    path: 'data.json' // the path to the animation json
})
复制代码

文件放在cdn

注意图片的文件夹应该与 json 文件放在同一目录下,例如:

./btn_gain.json
./btn_gain_img/img_0.png
复制代码

动画运行过程中

  1. 在动画初始化的时候,lottie 提供生命周期以感知动画的加载状态,主要提供以下几种:
  • complete
  • loopComplete
  • enterFrame
  • segmentStart
  • config_ready (when initial config is done)
  • data_ready (when all parts of the animation have been loaded)
  • DOMLoaded (when elements have been added to the DOM)
  • destroy

其中 DOMLoaded 理论上是最晚能感知到的周期,也即是“动画加载完成”。但实际使用下来,只有使用 canvas形式加载动效的时候,这个事件才是所有图片全部加载完成;其余情况(svg,html),都只是“开始拉取图片”,还是会展示给用户图片加载的整个过程。

并且,即使使用 canvas 加载,如果 json 文件与对应的图片文件夹都是云端也就是 cdn 的形式拉取的话,依旧无法感知到所有图片加载的过程。所以如果有强需求,需要用户完全不感知图片加载,建议将文件放在本地并以canvas 形式加载。

  • lottie 提供 destroy 的 api 以清空画布,但是在 destroy 旧动画 -> load 新动画这个过程中,会有非常明显的全屏闪动,这是由于画布重新生成需要时间造成的,建议使用dom的显隐切换而不是画布的 destroy 来切换同一块动效的不同状态。
  • 可任意使用 onComplete 的事件监听,不会发生事件的重新绑定。
  • lottie 不提供原生的缓停事件,但是可以通过提供的 setSpeed 方法进行多段调整速度,以达到缓停的效果。

APNG

APNG 是什么

以下来自wikipedia

Animated Portable Network Graphics (APNG) is a file format which extends the Portable Network Graphics (PNG) specification to permit animated images that work similarly to animated GIF files, while supporting 24-bit images and 8-bit transparency not available for GIFs. It also retains backward compatibility with non-animated PNG files.

The first frame of an APNG file is stored as a normal PNG stream, so most standard PNG decoders are able to display the first frame of an APNG file. The frame speed data and extra animation frames are stored in extra chunks (as provided for by the original PNG specification). APNG competes with Multiple-image Network Graphics (MNG), a comprehensive format for bitmapped animations created by the same team as PNG. APNG's advantage is the smaller library size and compatibility with older PNG implementations.

In a comparison made between GIF, APNG and WebP, it was shown that APNG kept lower file size while keeping at least equal quality.

动画便携式网络图形(APNG)是一种继承自便携式网络图形(PNG)的文件格式,他允许像 gif 文件一样播放动图,还拥有 gif 不支持的24位图像和8位透明性。 它还保留了与非动画 PNG 文件的向后兼容性。

APNG 文件的第一帧存储为普通 PNG 流,因此大多数标准 PNG 解码器都能够显示 APNG 文件的第一帧。 帧速度数据和额外的动画帧存储在额外的 chunks 中(如原始的 PNG 规范所规定)。 APNG 的竞争者是由 PNG 团队创建的位图动画的全面格式——多图像网络图形(MNG)。与其相比,APNG 的优势是更小的存储大小以及对旧的 PNG 完全兼容。

通过对比 GIF、APNG和WebP,可以看出 APNG 在质量相同的时候有着更小的体积。

  • APNG 是动的 PNG。
  • APNG的扩展名为 .png 或者 .apng。是的,.png 当然有可能是 APNG 文件。

apng 的兼容性

在2021年4月的现在,APNG 的兼容性如下:

截屏2021-04-16 下午6.20.36.png 可以看到99%的浏览器都兼容了,所以基本可以放心大胆的使用了。

对于不兼容的浏览器,APNG会显示为动画的第一帧。

apng 的优势

正如上文的介绍中附上的链接,apng 比 webp 更小,对比数据来源来自:littlesvr.ca/apng/gif_ap…

GIF = 43 920 bytesAPNG = 34 210 byte
WebP = 41 064 bytesLossy WebP = 73 774 bytes
GIF = 43 132 bytesAPNG = 30 823 bytes
WebP = 55 968 bytesLossy WebP = 114 518 bytes
GIF = 200 700 bytesAPNG = 168 411 bytes
WebP = 424 752 bytesLossy WebP = 394 118 bytes

在页面上的基础使用

正如简单的png一样,一个img标签足以,但是坑多多,详见下文。

在页面上的动画化使用

img 标签虽简单,但却有以下几种问题:

  1. apng 在页面上只能播放一次,所以如果一个动画需要重复播放,需要每次给动画连接添加时间戳,让浏览器认为是一个新的连接
  2. apng 在安卓和ios上表现存在差距,例如安卓播放一次,ios 会播放两次
  3. apng 的动画时间无法控制,很难实现中途暂停,衔接等操作

如上所述,我们可以借助 apng-canvas,将其转成 canvas ,然后以使用 canvas 的方式使用它。 apng-canvas 的原理解析,可以参考网易云前端团队的文章:Web 端 APNG 播放实现原理 我们在 apng-canvas 的基础上,进行了魔改,增加了一些对 canvas 播放的控制能力,例如:控制 apng 的播放速度 和播放次数、监听播放完成的事件等等,使其更加便于使用。 这里放上我们炫酷的抽卡动效,作为 apng 配合 css 的实例:

2021419-144034.gif

参考说明

APNG 那些事: aotu.io/notes/2016/… Web 端 APNG 播放实现原理juejin.cn/post/685767…

文|水程

关注得物技术,携手走向技术的云端

文章分类
前端
文章标签