本文由 IMWeb 首发于 IMWeb 社区网站 imweb.io。点击阅读原文查看 IMWeb 社区更多精彩文章。


在企鹅辅导品牌页中,我们需要实现一个动画如下:

复杂帧动画之移动端video采坑实现_java

页面滚动到动画区域,播放动画, 对应动画部分如下:

复杂帧动画之移动端video采坑实现_java_02


帧动画当前的实现有以下几种方式:

  1. GIF 动画

    大家比较熟悉的图片格式

  2. lottie(http://airbnb.io/lottie/)

    Airbnb 开源项目,通过解析 AE 动画为 json 数据,支持跨平台的动画效果解决方案;lottie 在辅导中已经有实际应用,使用过的同学都表示对其实现效果和开发速度表示称赞和推荐,如果你还不了解 lottie, 推荐 lottie 系列学习文章(https://imweb.io/topic/5b23a745d4c96b9b1b4c4efc)

  3. APNG (Animated Portable Network Graphics)

    基于 PNG 格式扩展的一种动画格式,增加了对动画图像的支持,其诞生是为了替代老旧的 GIF 格式,但部分浏览器不支持,需要考虑兼容;

  4. HTML video 元素

GIF 动画适用于处理色彩简单、动效简单的动画,如 logo 、 icon 图这样的小图动画,在上面需要实现的动画中明显细节比较多,区域也比较大,考虑到质量 GIF 排除在外

在使用哪种方式实现该动画上,结合同事 @ajaxchen 的调 研结论:

  1. lottie 在设计师通过 AE 制作了动画之后,通过 AE 插件 bodymovin(https://aescripts.com/bodymovin/) 将动画导出 json 给到我们前端开发,在使用这段 json 数据中,我们引入了 lottie-web 脚本来解析这段 json 数据渲染成为SVG / canvas 动画,效果如下图, 左图为用 lottie 实现,右图为我我们的目标实现效果


    可以看到实现还是存在着差异,颜色、数字倾斜度、虚线的透视都没有达到预期,于是放弃lottie 的使用,但这并不否定 lottie 在实现其它动画的优秀效果

    复杂帧动画之移动端video采坑实现_java_03

  2. APNG

    在对设计师给到的分段的动画帧图片压缩之后,其实现结果 apng 大小高达 29M,webp 格式 17M, 如此庞大的体积,且实现清晰度达不到预期,也只能放弃该方式;由于 APNG 在一些浏览器上不支持,在实现时需引入 apng-canvas(https://github.com/davidmz/apng-canvas)将 apng 转化为 canvas;

  3. createJS

    在我将 ISUX 上的文章《你离高效制作动画只差一篇文章的距离》(https://isux.tencent.com/articles/efficient-animation.html)发送给我们的设计小哥

    之后,他如此回复我

复杂帧动画之移动端video采坑实现_java_04

HTML video

在上面尝试无果之后,我的同事@zzbozheng 向我展示了一个 lol 的页面(https://lol.qq.com/),神奇,居然是用video来实现!我怎么就没想到!


复杂帧动画之移动端video采坑实现_java_05

查看 video 标签的兼容性,无论是我们品牌页的 PC 版还是移动 web 端,兼容性都可以满足我们的需求

复杂帧动画之移动端video采坑实现_java_06


设计小哥哥给到的动画 MP4 视频大小是 350k, 350k对比几十兆简直就是轻量,查了一番 video 的自动播放实现,有一些坑,跟设计师小哥哥也沟通了一番综合考虑之后毅然踩上了 video 的坑

video 标签有对应的事件方法, 可查阅文档: https://developer.mozilla.org/zh-CN/docs/Web/Guide/Events/Media_events

下面是在移动端 web 使用 video 过程中的采坑总结:

  1. video 在 safari 和桌面端 chrome 中可能无法自动播放

    这里的自动播放,无论是 video 标签的 autoplay 属性还是通过 js 自动调用 video 的 play 方法都是自动播放

    桌面端 chrome 自动播放主要受制于 autoplay policy (https://developers.google.com/web/updates/2017/09/autoplay-policy-changes) ,遵循对应的策略则可以自动播放,这主要考量于用户的体验;因为使用 muted (静音)属性可以允许自动播放, 我们的动画本来就是没有声音的,所以在 video 标签中加上 muted 属性

<video muted />
  1. 隐藏视频控制条

    在 video 标签中,只要不加 controls 属性,一般是不会显示控制条的,这样就看不出来是一个视频了,当然有些安卓机器的浏览器的确处于一种失控状态,后面会提到 ○| ̄|_

  2. IOS 视频自动全屏播放

    查阅资料,video 标签添加两个属性即可小屏播放

<video 
muted
playsInline
webkit-playsinline="true"
/>

  1. 微信不允许自动播放视频,必须通过用户交互才能播放

    开始的时候就有过来人的同事提醒过要我注意下微信的视屏自动播放,经过别人的反馈,其实不止是微信不允许,有些机器浏览器也是不允许,这个时候该怎么办?结合 touch 事件一起实现。视频播放是监听 scroll 事件,等到可视范围内调用 video.play() 自动播放,既然有些浏览器需要用户交互,那可以选择 touch 事件,当用户 touch 到这块展示播放区域,触发 touch 事件调用 play, 这里我们的动画区域足够大,不担心用户 touch 不到。这里使用变量来表示视频是否已经播放,如果已经播放就不再执行 touch 事件,避免频繁调用 play

  2. 有些安卓浏览器无法自动播放,touch 事件也无法触发播放

    video 标签的 play 方法返回一个 promise,可通过 promise 来检测到 video 是否可自动播放

video.play()
 .then(() => {
   // play success
 })
 .catch( err => {
   // auto play fail
 })

当 catch 到 error 时,只能启用兼容方案,设计小哥哥给了我几张帧图片,让我渐隐渐现实现图片播放。

复杂帧动画之移动端video采坑实现_java_07

然而! 在华为荣耀 8 的微信里面,我发现了个诡异的问题,视频没有播放,同时 video.play 没有 catch 到 error,而是正常的执行到了 then 方法,也就是说 play 方法返回成功,然而视频实际没有播放,这,这, 这, 是在欺骗我的感情!

复杂帧动画之移动端video采坑实现_java_08

无奈之下, 针对安卓的微信端,视频全部启用兼容模式(几张图片渐隐渐现)

  1. 论安卓浏览器的各种诡异表现

    我:"设计小哥哥,这我无能为力

    设计:"找出所有对应的机型和浏览器,对这些不支持的浏览器使用兼容模式播放动画

    我:"这所有的机型实在难以控制和全部覆盖到...

    设计:"那就先对所有的安卓都使用兼容模式吧,后面对此优化

    于是就这样干掉了所有的安卓 video

    1. oppo 机视频播放自动悬浮置顶

复杂帧动画之移动端video采坑实现_java_09

video 控制条无法隐藏


视频无法控制地自动全屏播放

...

ios QQ 浏览器视频播放完毕,展示推荐视频

复杂帧动画之移动端video采坑实现_java_10

这个 video 我是设置了循环播放的,硬生生 QQ 浏览器就在视频播放完毕后展示推荐视频,并且停止了我的循环播放,这让我的页面显的有点 low, 这明显是不仁道的,尝试无果之后,于是我咨询 QQ 浏览器的同事帮忙这个问题, 他让我在 video 标签上加上这个属性,即可使用系统播放器,而拒绝被拦截植入推荐视屏, 感谢@eddiecmchen 提供的意见

mtt-playsinline=true
  1. 设计师导出的视频背景色与提供的色彩有色差 这在不同 PC 设备中存在差异,例如 MAC 与 windows , 在移动端暂时还没发现,但是可以发现视频在移动端展示与 PC 上展示的色彩差异

复杂帧动画之移动端video采坑实现_java_11



至此附上实现的部分代码块,项目使用 react 技术栈

<video
  muted
  src="***"
  preload="auto"
  playsInline
  webkit-playsinline="true"
  mtt-playsinline="true"
  loop
  ref={this.videoRef}
/>
playVideo = () => {
   const { isVideoCanAutoPlay, isPlayedVideo } = this.state;
   // 播放视频
   const videoDom = this.videoRef.current;
   if (videoDom && !isPlayedVideo && isVideoCanAutoPlay) {
     const playPromise = videoDom.play();
     if (playPromise) {
       playPromise
         .then(() => {
           this.setState({
             isPlayedVideo: true,
           });
         })
         .catch((err) => {
           badjs.info(`[品牌页][AI VIDEO 动画]: 自动播放出错 ${err}`);

           ++this.catchVideoErrorCount;

           // 通过点击和scroll后都无法播放视屏,使用兼容性方案
           if (this.catchVideoErrorCount >= 2) {
             this.setState({
               isVideoCanAutoPlay: false,
             });
           }
         });
     }
   }
 };

最后总结:

  1. 移动端 web 对于 video 自动播放的兼容性是在太差,尤其安卓,一些浏览器对 video 标签进行拦截,并以自己的方式实现,或是悬浮置顶播放,或是两个视屏播放冲突,或是控制条无法隐藏,或是播放默认全屏,如果用其它方式可以实现动画尽量还是用其它方式

  2. 对于 video 的自动播放,考虑一些浏览器限制必须通过用户交互才能使用,如果视屏是在第一屏则有点难度,还是需要用户通过点击才能播放,如果不是第一屏则可通过 touch 事件来触发,毕竟用户下拉滚动还是会触发 touch 事件

  3. video 的 play 方法返回的 promise 存在华为荣耀8 微信里返回 play 成功,但是却没有播放视频

参考文档和网站:

  1. https://developer.mozilla.org/zh-CN/docs/Web/Guide/Events/Media_events

  2. https://lol.qq.com/

感谢在此次采坑过程中给予帮助的同事~