前端动画选择

零、前言

动画的种类。一般遇到的动画主要分为两类:展示性动画,交互性动画。展示动画一般是页面局部的动画效果,例如动态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类库,使用时直接加上类名即可。也可以在样式里层叠掉默认样式进行微调。

优点

  1. 使用简单粗暴
  2. 预设效果多
  3. 性能靠谱

缺点

  1. 灵活性较低
  2. 难以实现复杂动画序列

适用场景

  1. 在原有页面上进行动画增加、优化
  2. 不涉及复杂动画逻辑

三、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'
  });
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的时候

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容