罗列了下一些活动中用到动画的地方,以及踩的一些坑
背景动画 gif
这块一开始的思路是:用一个尽可能小的 gif 来保障用户加载的速度。
先尝试了下 12 帧的 gif (压缩完大小为 6mb),卡顿感很明显,体验不佳:
而且,当未完全加载完 gif 时,在加载的过程中还是会有过分明显的卡顿:
于是调整了下思路:
用静态图预先展示,向下保障用户最低体验;然后等高清 gif 完全加载完才展示,向上带给用户最佳体验。
代码实现上,只要用两层叠加的图片,控制上层 gif 的 visibility
属性即可,如下:
1 | const [isGifShow, setGifShow] = useState(false); // 是否展示 gif |
又和视觉尝试了几次,发现不用 24 帧 (压缩完是 14mb),20 帧 (压缩完是 10mb) 即可在视觉上体验完美:
进场视频 video
先抛出一个问题:
部分移动端浏览器,比如 iOS 微信,为了减少用户流量的浪费,只允许让用户交互完之后才可以播放 video,无法自动播放。
整体思路
正因为之前站外场景 video 比较难兼容,故头部视频都采用,站外直接展示静态图的方案,或者就是转化成 canvas 的方案,但体验不佳。
而这一次是全屏视频,好看得很,故尽管时间有限,但还是在临上线前一天,又和视觉商量了下加了个引导点击交互图,整体流程如下:
(其中,因为时间原因,这次加载中进度条没实现,而是同样展示了点击引导图做替换
Video 相关基础知识
video 属性
首先我们的场景是自动播放视频的场景,不需要控件和声音,所以默认的配置如下:
属性 | 值 | 描述 |
---|---|---|
id | 自定义 | 识别视频的唯一标识。这边通过 Math.floor(Math.random()\*100000) 创建。 |
controls | / | 如果出现该属性,则向用户显示控件,比如播放按钮。 |
height | / | 设置视频播放器的高度。 |
width | / | 设置视频播放器的宽度。 |
src | URL | 要播放的视频的 URL。 |
poster | ‘x’ | 规定视频下载时显示的图像,或者在用户点击播放按钮前显示的图像。目前场景下只要黑屏即可,即给 video 设置 background-color: #000; 。但如果不设置该属性,在某华为机型下,会有一个播放按钮一闪而逝,所以得随便设置个字符串。 |
autoplay | true | 如果出现该属性,则视频在就绪后马上播放。 |
preload | / | 如果出现该属性,则视频在页面加载时进行加载,并预备播放。如果使用 “autoplay”,则忽略该属性。 |
loop | false | 如果出现该属性,则当媒介文件完成播放后再次开始播放。 |
muted | true | 规定视频的音频输出应该被静音。在直播间打开的页面,不设置该属性的话,可能会使得主播的声音消失。 |
playsinline | true | 包括 webkit-playsinline 和 x5-playsinline 。设置 iOS 微信浏览器支持行内播放,否则会全屏置顶播放且有控件。目前场景下,虽然同样是全屏播放,但如果不设置会盖住跳过按钮,影响功能 |
顺带一提,video 样式通过 object-fit
来控制视频大小的展示。目前场景因为需要视频全屏播放,在不同设备不同百分比下,用 cover
来保持视频的宽高比,并居中展示,体验最好。
video 方法
首先视频没有一个开始播放的回调,只能用 timeupdate
变相实现。
方法 | 描述 |
---|---|
play | 开始播放。 |
pause | 暂停播放。 |
timeupdate | 视频播放过程中会不停触发,每次 currentTime 都会更新,通过比较时间可以变相地实现开始播放 onStart 或即将结束播放 onNearlyEnd 等事件。 |
ended | 结束播放后触发。 |
canplay | 是否可以开始播放。 在 iOS 可能有兼容问题无法触发 |
canplaythrough | 是否可以开始播放到结束。在 iOS 可能有兼容问题无法触发 |
complete | 当离线音频加载完成时触发。 |
渐入渐出
这次开屏动画是刚启动就播放,所以不必加上渐入,如果是点击触发的全屏动画建议增加渐入,使得过渡更自然。
渐出的思路就是在 timeupdate
事件中计算时间,即将结束时给视频增加一个渐隐类,代码如下:
1 | const [isSlowlyHidden, setSlowlyHidden] = useState(false); // 开始渐隐 |
1 | .headvideo { |
全屏展示
这块主要是通过,让 url 携带客户端全屏参数来支持实现:
?full_screen=true&nm_style=sbt&keep_status_bar=true&status_bar_type=light&bounces=false
这里的
bounces=false
是让客户端禁用边界滚动,毕竟overscroll-behavior: none;
能力有限
然后在配合全屏组件增加后退和分享功能,同时需要向下兼容站外非全屏场景。
代码如下:
1 | const isInApp = Env.isInNEMapp() || Env.isInLOOKapp(); // 是否在 app 内 |
- 全屏组件的高度是写死的,因为目前客户端还没提供比较好的获取实际状态栏高度的方式,目前高度是 2rem,所以全屏时头部按钮还要额外往下移动一段距离
- 全屏下要去除 iOS 底部的
padding-bottom
安全距离的处理。
强调动画
这块一般用 css animation 来实现,一般最常用的特效就是 放大抖动:
1 | @keyframes tada { |
渐入和渐出
这块也用 css transition 配合动态添加 class 即可实现,比如:
1 | .xxx { |
1 | const [show, setShow] = useState(false); // 是否展示 |
上下弹出的动画,也可以用这种方式,动态设置 bottom
来实现。
复杂的渐入渐出效果建议还是用 React Transition Group
元素层级
层级一般不用 z-index
来实现,这会给后期维护带来很大困难。
主要还是靠给父元素和浮于上层的元素都加上 position: relative
或 position: absolute
(即 f-pr 和 f-pa),然后通过声明位置的先后来控制层级。
总结
总结一下技术方案对比:
前端动画工作流方案
- 展示型动效
- apng-canvas
- video
- jsmpeg
- lottie很多效果实现不了, 不适合复杂动画,只适合简单几何动画
- gif
- 非展示型动效:
- css 手写
- pixi.js 手写
- three.js 手写
- 展示型动效
展示型动效实现
- GIF
- 这是在看了花椒头图动画: https://p6.music.126.net/obj/wo3DlcOGw6DClTvDisK1/4494809066/c284/ffa9/e764/a0c34818ac3aecfdd3374f4f174302c8.gif 得出的结论。
- 相较于video动画而言,gif拥有最好的支持率,最好的兼容性。(https://en.wikipedia.org/wiki/Comparison_of_web_browsers#Image_format_support)
- GIF动画格式每个像素有8bit空间,支持引用256个内置调色盘颜色,每个调色盘支持传统24位RGB空间,所以一张GIF如果色彩种类低于256色依然可以展现出很惊艳的动画, 支持透明(视觉反馈会边缘会有锯齿)
- 现在存在的问题是目前我们导出GIF的姿势有点不对,上面的GIF导出的图层中每个图层只包含部分动画图层,而我们导出的GIF文件每个图层是个独立帧,压缩率不够高。
- 动态图片APNG
- 使用apng-canvas库,将其在canvas上进行绘制。
- 优点:
- 兼容性好,使用方便;
- 控制方便;
- 缺点:
- 无npm包,需要引入js文件,使用window.APNG方法;
- 初次解析apng文件,会有卡顿情况, 可以先预解析下;
- 不能使用webgl render,帧率低;
- 视频
- web端/移动端有用户交互
- 使用video标签播放;
- 移动端无用户交互
- 使用jsmpeg库转canvas播放;
- 优点:
- 可以使用webgl render和2d render;
- 缺点:
- 需要先转为MPEG-1,且转码后的大小会明显增大;
- 素材上传中心不支持MPEG-1格式视频,需要自行上传cdn;
- 低端机在播放时会出现页面崩溃(iphone5);
- web端/移动端有用户交互
- 透明视频
- 使用video播放, 读取纹理,支持webgl时,采用webgl render,不支持时采用canvas 2d render
- 优点
- 由于video压缩率高,没有256色限制,所以自由度高。
- 缺点
- 只能在站内播放 微信不支持自动播放。
- 另外对于循环动画: iOS下video循环动画头尾会存在一个gap
- 开启页面后锁屏,打开后视频不会自动播放
- 两个视频无法实现无缝切换,会有微小的空白出现
- ios端半屏的boss动效放一段时间之后就会停了,安卓的可以一直播放boss动效(有待验证)
- 暂时没有好的解决方法,可以考虑改写jsmsg读MPEG-1视频,然后再进行计算。
- GIF