零、前言
动画的种类。一般遇到的动画主要分为两类:展示性动画,交互性动画。展示动画一般是页面局部的动画效果,例如动态logo展示,部分icon的效果展示。交互性动画例如红包雨。
前端实现动画往往要带来比较大的工作量,动画的堆叠,以及调试,使用合理的框架将会大大减少工作量。
主要介绍一下几种(star比较高的)框架
- Animate.css
- Lottie
- Anime.js
一、动画的实现方式
CSS Animation
JS驱动的动画
canvas
SVG
CSS3动画,从直观上来讲,可以说是十分丝滑了,使用简单,深入研究请参考:https://www.w3cplus.com/animation/animation-performance.html 。
JS驱动的动画因为要定时更新DOM来实现动画效果,性能较差(requestAnimationFrame的性能表现要优于setXXX系列,深入研究请访问:https://jinlong.github.io/2013/06/24/better-performance-with-requestanimationframe/ 同时应尽量使用transform,开启硬件加速,避免引起页面重拍)。
SVG,本质是解析XML结构,通过数据修改dom后,浏览器会自动重绘(区别于canvas),性能优于JS驱动,对比canvas还是存在劣势。
canvas可以启用硬件加速,性能较优。与CSS3动画对比,chrome的canvas性能优于css (参考数据来源:https://segmentfault.com/a/1190000000438057)。
二、Animate.css
纯CSS3框架,https://github.com/daneden/animate.css。
提供了初始的css3类库,使用时直接加上类名即可。也可以在样式里层叠掉默认样式进行微调。
优点
- 使用简单粗暴
- 预设效果多
- 性能靠谱
缺点
- 灵活性较低
- 难以实现复杂动画序列
适用场景
- 在原有页面上进行动画增加、优化
- 不涉及复杂动画逻辑
三、Lottie
http://airbnb.io/lottie/
https://github.com/airbnb/lottie-web
https://github.com/chenqingspring/react-lottie
UI通过AE使用bodymovin导出json,前端直接调用,可以选择导出canvas、svg。兼容多端。react项目推荐使用react-lottie
event-node项目 PT-7306_20180926_animate 分支 /animation/index.htm
以React为例:
简单用法
const { json } = this.props.store.toJS();
const options = {
renderer: 'svg',
loop: false,
autoplay: false,
animationData: json.deom1
}
return (
<div>
<Lottie options={options}
ref={ref => { this.singalLottie = ref; }}
height={'100vw'}
width={'100vw'}/>
<div className="p-animation__btn-wrap">
<button className="p-animation__lottie-btn" onClick={() => {
this.singalLottie.stop();
}}>stop</button>
<button className="p-animation__lottie-btn" onClick={() => {
this.singalLottie.play();
}}>play</button>
<button className="p-animation__lottie-btn" onClick={() => {
if(this.state.isPaused) this.singalLottie.play();
if(!this.state.isPaused) this.singalLottie.pause();
this.setState({isPaused: !this.state.isPaused})
}}>pause</button>
</div>
</div>
)
但是不知道为什么pause是失效的= =之后研究下。
官方示例使用的是props里面的isStopped和isPaused,页面存在单一控制动画可以使用。
序列例子:
const { json } = this.props.store.toJS();
const options = {
renderer: 'canvas',
loop: false,
autoplay: false,
animationData: json.deom1
}
const optionFirst = {
renderer: 'canvas',
loop: false,
autoplay: true,
animationData: json.deom1
}
return (
<div>
<Lottie options={optionFirst}
style={{display: 'inline-block'}}
height={'50vw'}
width={'50vw'}
eventListeners={[
{
eventName: 'complete',
callback: () => {
this.moreLottie2.play()
}
}
]}
/>
<Lottie options={options}
ref={ref => { this.moreLottie2 = ref; }}
style={{display: 'inline-block'}}
height={'50vw'}
width={'50vw'}
eventListeners={[
{
eventName: 'complete',
callback: () => {
this.moreLottie3.play()
}
}
]}/>
<Lottie options={options}
ref={ref => { this.moreLottie3 = ref; }}
style={{display: 'inline-block'}}
height={'50vw'}
width={'50vw'}
eventListeners={[
{
eventName: 'complete',
callback: () => {
this.moreLottie4.play()
}
}
]}/>
<Lottie options={options}
ref={ref => { this.moreLottie4 = ref; }}
style={{display: 'inline-block'}}
height={'50vw'}
width={'50vw'}/>
</div>
)
常用方法:
animation.play(); // 播放该动画,从目前停止的帧开始播放
animation.stop(); // 停止播放该动画,回到第0帧
animation.pause(); // 暂停该动画,在当前帧停止并保持
animation.goToAndStop(value, isFrame); // 跳到某个时刻/帧并停止。isFrame(默认false)指示value表示帧还是时间(毫秒)
animation.goToAndPlay(value, isFrame); // 跳到某个时刻/帧并进行播放
animation.goToAndStop(30, true); // 跳转到第30帧并停止
animation.goToAndPlay(300); // 跳转到第300毫秒并播放
animation.playSegments(arr, forceFlag); // arr可以包含两个数字或者两个数字组成的数组,forceFlag表示是否立即强制播放该片段
animation.playSegments([10,20], false); // 播放完之前的片段,播放10-20帧
animation.playSegments([[0,5],[10,18]], true); // 直接播放0-5帧和10-18帧
animation.setSpeed(speed); // 设置播放速度,speed为1表示正常速度
animation.setDirection(direction); // 设置播放方向,1表示正向播放,-1表示反向播放
animation.destroy(); // 删除该动画,移除相应的元素标签等。在unmount的时候,需要调用该方法
事件:
data_ready: 加载完json动画
complete: 播放完成(循环播放下不会触发)
loopComplete: 当前循环下播放(循环播放/非循环播放)结束时触发
enterFrame: 每进入一帧就会触发,播放时每一帧都会触发一次,stop方法也会触发
segmentStart: 播放指定片段时触发,playSegments、resetSegments等方法刚开始播放指定片段时会发出,如果playSegments播放多个片段,多个片段最开始都会触发。
data_ready: 动画json文件加载完毕触发
DOMLoaded: 动画相关的dom已经被添加到html后触发
destroy: 将在动画删除时触发
优点:
1.可以实现较为复杂动画
2.可以导出canvas,性能高。也可以在部分小图标上导出svg,灵活性高。
3.方便控制启停,可以控制回调
4.大大解放前端生产力
缺点:
1.需要UI部门支持 (咨询了下 环境配置较为复杂,配好之后使用起来还是ok的)
2.部分效果导出无法实现
适用场景:
1.展示性动画,动画序列不长,可控
2.UI给力
四、Anime.js
http://animejs.com/documentation
https://github.com/juliangarnier/anime
demo:https://event.qunar.com/hrPropaganda/index.htm
Anime.js是一个JS驱动的动画框架,支持:
- 任何包含数值的DOM属性都可以设置动画(包含input的value)
// 1~1000
anime({
targets: '#domAttributes input',
value: 1000,
round: 1,
easing: 'easeInOutExpo'
});
- kerframes,连接多个动画
anime({
targets: '#keyframes .el',
translateX: [
{ value: 250, duration: 1000, delay: 500, elasticity: 0 }, //第一步
{ value: 0, duration: 1000, delay: 500, elasticity: 0 } //第二步
]
});
- Timeline
var basicTimeline = anime.timeline();
basicTimeline
.add({
targets: '#basicTimeline .square.el',
translateX: 250,
easing: 'easeOutExpo'
})
.add({
targets: '#basicTimeline .circle.el',
translateX: 250,
easing: 'easeOutExpo'
})
.add({
targets: '#basicTimeline .triangle.el',
translateX: 250,
easing: 'easeOutExpo'
});
- 动画状态控制,播放、暂停、重新开始等(http://animejs.com/documentation/#playPause)
var playPause = anime({
targets: '#playPause .el',
translateX: 250,
delay: function(el, i, l) { return i * 100; },
direction: 'alternate',
loop: true,
autoplay: false
});
document.querySelector('#playPause .play').onclick = playPause.play;
document.querySelector('#playPause .pause').onclick = playPause.pause;
- 动画状态回调,开始,执行中,结束提供回调函数
var runLogEl = document.querySelector('#run .current-time-log');
var runProgressLogEl = document.querySelector('#run .progress-log');
var run = anime({
targets: '#run .el',
translateX: 250,
delay: 1000,
run: function(anim) {
runLogEl.value = 'running';
runProgressLogEl.value = 'progress : ' + Math.round(anim.progress) + '%';
},
complete: function(anim) {
runLogEl.value = 'not running';
runProgressLogEl.value = 'progress : 100%';
}
});
- 自定义贝塞尔曲线
- 支持promise,动画结束后,调用anime.finshed会返回一个promise对象
var finishedLogEl = document.querySelector('#finishedPromise .finished-log');
var finishedPromise = anime({
targets: '#promises .el',
translateX: 250,
delay: 1000
});
var promise = finishedPromise.finished.then(logFinished);
function logFinished() {
finishedLogEl.value = 'Promise resolved';
// Rebind the promise, since this demo can be looped.
setTimeout(function() {
promise = finishedPromise.finished.then(logFinished);
});
}
finishedPromise.update = function(anim) {
if (!anim.completed) {
finishedLogEl.value = '';
}
}
- 支持svg
业务中常见的使用为时间序列,每一个add()执行完之后,才会执行下一个add的内容,如果期望多个动画同时执行,可以使用offset
// 下列三个同时执行
.add({
targets: this.caihua1,
duration: 1000,
translateY: '7vh'
})
.add({
targets: this.caihua2,
duration: 1000,
translateY: '-7vh',
offset: '-=1000'
})
.add({
targets: [this.tiao1, this.tiao2],
duration: 1500,
opacity: 1,
offset: '-=1000'
})
Tables | Examples | Infos |
---|---|---|
+= | '+=100' | Starts 100ms after the previous animation ends |
-= | '-=100' | Starts 100ms before the previous animation ends |
*= | '*=2' | Starts at 2 times the previous animations duration |
优点:
1.实现了CSS3动画的深度封装
2.通过js驱动来操作动画状态,实现了对于多个动画分支的管理,利于实现复杂动画
缺点:
难以处理交互性动画(本篇文章暂时不涉及)
适用场景:
复杂动画场景,需要定制各个细节,UI难以出AE导出的json的时候