接触Animejs的契机
笔者在有一天制作threejs的动画时,发现一个问题,之前我一直在用的requestAnimationFrame,不适合用于制作大量的动画
1.我需要将大量的动画目标放到全局,或者要写多个requestAnimationFrame,不便于控制
let a1 = ...
let a2 = ...
function animation(){
//a1执行动画
//a2执行动画
requestAnimationFrame(animation);
}
//或
let a1 = ...
let raf1;
function animation(){
raf1 = requestAnimationFrame(animation);
if(不满足a1动画条件){
cancelAnimationFrame(raf1);
}else{
//执行a1动画
}
}
-
如果我要使用定时器,一旦帧率出现波动,动画将会出现错位问题
-
笔者也使用过threejs圈内流传最广的tweenjs,但是网络上能搜索到的tweenjs就有好几个版本,threejs自带的开发包中带一个,npm上有很多个版本,然后createjs下带一个,createjs.tween,还有一个tweenMax
可以说,tween的版本众多,且很难找到能匹配的文档,所以在前期,我只能选择文档比较足的createjs.tween
- 但是,我随后又遇到了麻烦,笔者当时使用的createjs.tween的写法比较蛋疼,且不能做非匀速的动画,一旦遇到加速动画,就成了麻烦
5.在上述的这么多问题的驱使下,我自己开发了一个动画库,一个基于requestAnimationFrame的动画库,但是,紧接着又一次遇到了,非匀速动画的算法问题
在一次又一次的搜寻中,我遇到了Animejs
animejs介绍
原先animejs是有中文站的,但是animejs.cn这个站点在这两年挂掉了
animejs是一个很全面很专业的动画库,除了动画的基础功能外,它还有内置了N种缓动函数,内置了自定义缓动方式,时间线控制等,各位可以自行查看官网的效果演示,来感受animejs的动画效果
animejs与threejs是可以完美兼容的,所以对于看我文章学习threejs的同学,如果对threejs的动画遇到困难,可以尝试使用一下animejs
闲话到此为止,接下来进入animejs的正式教程
animejs安装
npm
npm install animejs --save
引入方式
原生
<script src="anime.min.js"></script>
ES6:
import anime from 'animejs/lib/anime.es.js';
CommonJS
const anime = require('animejs');
基础动画
animejs支持给任何 对象 做动画,比如说一个div,一个js对象,甚至一个数组等
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#d1{
position: absolute;
width: 50px;
height: 50px;
top:50px;
background-color: #7FFF7F;
}
</style>
</head>
<body>
<div id="d1"></div>
<input type="button" value="播放动画" id="btn">
<script type="module">
//由于笔者写threejs的习惯,这里已经习惯了使用ES版本的文件
import anime from "./anime.es.js";
//获取d1这个div
let d1 = document.getElementById('d1');
//获取按钮
let btn = document.getElementById('btn');
//给按钮绑定点击事件,点击后执行动画
btn.onclick = ()=>{
anime({
targets:d1, //动画目标
left:"100px", //目标指定的属性值要变化到多少,这里是让 css样式中的left变化到100px
duration:3000 //动画播放时间
})
}
</script>
</body>
</html>
animejs如何绑定动画目标
上面的代码,可以用另一种写法同样的效果
id选择器
let btn = document.getElementById('btn');
btn.onclick = ()=>{
anime({
targets:"#d1",
left:"100px",
duration:3000
})
}
class选择器
使用class依然可行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.d1{
position: absolute;
width: 50px;
height: 50px;
top:50px;
background-color: #7FFF7F;
}
</style>
</head>
<body>
<div class="d1"></div>
<input type="button" value="播放动画" id="btn">
<script type="module">
import anime from "./anime.es.js";
let btn = document.getElementById('btn');
btn.onclick = ()=>{
anime({
targets:".d1",
left:"100px",
duration:3000
})
}
</script>
</body>
</html>
js对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#d1{
position: absolute;
width: 50px;
height: 50px;
top:50px;
background-color: #7FFF7F;
}
</style>
</head>
<body>
<div id="d1"></div>
<input type="button" value="播放动画" id="btn">
<script type="module">
import anime from "./anime.es.js";
let btn = document.getElementById('btn');
let d1 = document.getElementById('d1');
let param = {
left:0
}
btn.onclick = ()=>{
anime({
targets:param,
left:100,
duration:3000,
update:()=>{ //动画每播放一帧执行一次
d1.style.left = param.left + "px";
}
})
}
</script>
</body>
</html>
以上所有写法都是同样的效果
threejs对象
以下代码均在threejs基础教程中做过讲解,这里不再赘述
function addMesh(){
let geometry = new THREE.BoxGeometry(1,1,1);
let material = new THREE.MeshStandardMaterial({color:0xff0000});
let mesh = new THREE.Mesh(geometry,material);
scene.add(mesh);
document.getElementById('btn').onclick = ()=>{
anime({
targets: mesh.position,//animejs的targets必须是一个对象
x:5,//这里的x,必须是当前对象下拥有的属性,mesh.position是一个Vector3对象,它的下面有x这条属性
duration:3000
})
}
}
多个目标
btn.onclick = ()=>{
anime({
targets:['#d1','#d2'],
left:100,
duration:3000
})
}
这里非常好理解,就是变成了数组,然后让数组内的所有元素做相同的动作
如果你想让所有的元素做不同的动作,请看下面
animejs动画对象的编写格式
上面演示的过程中,我们不难发现,执行一次动画,必须需要的东西有:
- 动画目标
- 执行时间
- 动画执行方式
- 动画配置
animejs中,我们需要传入一个对象,比如说上面的
anime({
targets:'#d1',
left:100,
duration:3000
})
//完全可以写成这样
let animeConfig = {
//动画目标
targets:"#d1",
//动画的执行方式
left:100,
//动画的执行时间,默认为1000
duration:3000
//动画配置
}
animejs支持的动画
dom元素动画
比如说
anime({
targets: '.d1',
left: '240px',
backgroundColor: '#FFF',
borderRadius: ['0%', '50%'],
});
animejs支持 给大部分常见的css元素属性做动画,比如元素的位置,旋转角度,大小,背景色,边框等,css的动画这里就介绍这一点,其他的可以参考官方的说明
animejs官网对css属性动画的说明
对象动画
let btn = document.getElementById('btn');
let d1 = document.getElementById('d1');
let param = {
left:0,
backgroundColor:"#7fff7f",
borderRadius:"0%"
}
btn.onclick = ()=>{
anime({
targets: param,
left:270,
backgroundColor: '#00ffff',
borderRadius: "50%",
update:()=>{
d1.style.left = param.left + "px";
d1.style.backgroundColor = param.backgroundColor;
d1.style.borderRadius = param.borderRadius;
}
});
}
上述代码效果与上面dom动画基本一致
可以看出,animejs支持基本数据的动画变化,支持颜色变化,也支持字符串的变化
有些人要问了:为什么要单独写一个param来创建动画呢?
动画目标与实际目标
在上面的代码中,我们创建了一个param,作为动画目标,让它进行变化,这样我们就可以更方便写一些其他的控制逻辑
创建动画目标还能解决不同对象同时开始做动画的逻辑问题,如threejs中,同时要做位移和旋转,甚至缩放动画时
function addMesh(){
let geometry = new THREE.BoxGeometry(1,1,1);
let material = new THREE.MeshStandardMaterial({color:0xff0000});
let mesh = new THREE.Mesh(geometry,material);
scene.add(mesh);
document.getElementById('btn').onclick = ()=>{
let param = {
px : 0, py : 0, pz : 0,
rx : 0, ry : 0, rz : 0
}
anime({
targets: param,
px:5,
py:-1,
pz:5,
rx:Math.PI * 0.5,
ry:Math.PI,
rz:Math.PI * 0.3,
update:()=>{
mesh.position.x = param.px;
mesh.position.y = param.py;
mesh.position.z = param.pz;
mesh.rotation.x = param.rx;
mesh.rotation.y = param.ry;
mesh.rotation.z = param.rz;
}
})
}
}
//并不是说,做复杂动画,就必须要和上面一致,这里更强调的是逻辑清晰
//同时写3个anime动画也可行,这个根据自己的实际需求去写即可
DOM数值与SVG
这两部分笔者很少用,直接看官方文档吧
DOM数值动画
SVG动画
动画基本配置
常用属性duration,delay,endDelay,easing
anime({
targets: "#d1",
left:270,
duration:2000,//动画时间
delay:500,//延迟多久播放
endDelay:500,//播放完成后延迟多久动画彻底结束
easing:"easeInOutElastic" //动画缓动方式
});
duration:动画播放时间,这个好理解
delay,是指动画播放前的延迟时间,而这个时间被算在Duration内
EndDelay,是指动画播放完成后,还需要停顿多久,这个属性在后面的循环时会用到
easing,缓动方式,缓动方式,决定了我们能做更好看的动画
缓动方式算法表以及缓动动画演示
只要指向每一个缓动的线,我们就能知道我们的物体将会怎样运动
上图我们选择了
可以看出,我们的动画,是刚开始先抖动,然后极速到达后方,再抖动后到达指定位置
这就是缓动方式
缓动方式特别适合用于做一些高级动画效果,笔者这里用的最多的,就是在数字孪生项目中的飞入效果,先提前定义一个相机的飞入位置,然后再执行缓动动画,先快后慢的到达目的地,来实现一个很舒服的飞入效果
document.getElementById('btn').onclick = ()=>{
anime({
targets: camera.position,
x:0,
y:10,
z:10,
easing:"easeOutCubic",
update:()=>{
orbitControls.update();
}
})
}
限制小数点的round
非常简单,就是限制你动画现实的数据是否要做小数点的限制,自己看官网吧,不做介绍了
对不同的属性做不同的动画效果
这里我就上官网文档的代码了,其实就是对不同的属性做了个不同的效果动画,也是用于做复杂动画时使用
anime({
targets: '.specific-prop-params-demo .el',
translateX: {
value: 250,
duration: 800
},
rotate: {
value: 360,
duration: 1800,
easing: 'easeInOutSine'
},
scale: {
value: 2,
duration: 1600,
delay: 800,
easing: 'easeInOutQuart'
},
delay: 250 // All properties except 'scale' inherit 250ms delay
});
方向Direction
也是超级好理解的属性,默认是正常的动画播放
direction : reverse 倒着播放动画
direction :alternate ,反复播放,需要循环次数大于2才生效
代码过于简单,不写那么多了
anime({
targets: '.dir-normal',
translateX: 250,
easing: 'easeInOutSine',
direction: 'reverse',
});
循环loop
超级好理解,就是动画要播放几次,或是否要无限播放
loop这个属性,可以传入数字,可以传入true或false,默认值是false,即动画播放一次后就停止
传入数字必须是正整数,传入的数字是几就循环播放几次
传入的是true的情况下,动画将无限播放
anime({
targets:'#d1',
loop: true,
})
自动播放Autoplay
动画是否创建时就播放
let a1 = anime({
targets:"#d1",
left:200,
autoplay:false
})
btn.onclick = ()=>{
a1.play();
}
用于控制动画什么时候播放
动画支持的数据类型
过于简单的笔者这里就不写代码了,直接摘抄官网文档的代码来做讲解
css基本样式
//基本的数据操作
anime({
targets: '.unitless-values-demo .el',
translateX: 250, // -> 沿x轴平移250px
rotate: 540, // -> 旋转540deg
backgroundColor:'#000000', //从默认色变成黑色,支持各种css颜色,如'#fff',rgb(),rgba,hsl等等
});
//是个css属性它都支持
anime({
targets: '.specific-unit-values-demo .el',
width: '100%', // -> 从 '28px' 变化到 '100%',
});
//相对值
anime({
targets:'d1',
left:'+=100px',//让d1这个dom的left增加100px
})
//设定初始值和结束值
anime({
targets:'d1',
left:['0','200px'],//从0px变化到200px
})
数据
基本所有数字类型的数据都支持,同时也支持上述的相对值,百分比等,具体使用请根据个人需求来使用
时间线
时间线说白了就是连续的动画播放的时间线
时间线基础
如果我们需要做一段连续动画,如果没有时间线概念的话,就会出现无限套娃的情况
anime({
targets:anime1,
...
changeComplete:()=>{
anime({
targets:anime2,
...
changeComplete:()=>{
anime({
targets:anime3,
...
changeComplete:()=>{
...
}
})
}
})
}
})
上述的套娃逻辑,我相信是个人都不愿意写,这个时候官方提供了一种时间线的解决方案
//创建时间线
let timeline = anime.timeline({
targets:"#d1",
autoplay:false
})
//1秒 向右移动到300px
timeline.add({
left:300,
duration:1000,
easing:"linear"
});
//1.5秒向下移动到150px
timeline.add({
top:150,
duration:1500,
easing:"easeOutBounce"
});
//0.5秒向左移动到0
timeline.add({
left:0,
duration:500,
easing:"easeInElastic"
});
//2秒向上移动到50
timeline.add({
top:50,
duration:2000,
easing:"easeOutBack"
});
btn.onclick = ()=>{
timeline.play();
}
时间线在add了新的动画后,在执行动画时,会默认按照顺向播放来播放,所以总动画时间 = 每个时间线添加的动画总时间之和
时间线偏移
但是,我们也不是永远都是顺向播放的动画,也有可能前面的一段动画没有播完,就要开始播下一段
这里我们就使用时间线的第二个功能,时间线偏移
偏移有两种方法,一种是相对一种是绝对
let timeline = anime.timeline({
targets:"#d1",
autoplay:false
})
timeline.add({
left:300,
duration:1000,
easing:"linear"
});
timeline.add({
top:150,
duration:1500,
easing:"easeOutBounce"
},"-=500"); //让向下移动的动画提前0.5秒执行
timeline.add({
left:0,
duration:500,
easing:"easeInElastic"
},1000); //让向左移动的动画,在时间线播放第一秒的时候执行
timeline.add({
top:50,
duration:2000,
easing:"easeOutBack"
});
btn.onclick = ()=>{
timeline.play();
}
timeline.add( animeConfig, timelineOffset )
animeConfig:本质上就前面介绍的那一大堆
timelineOffset:时间线偏移,比如说,你希望你的某一段动画,在指定的时间执行,那么这里填入具体执行的数字即可,如果你希望你的动画提前或延后一段时间执行,那么,只需要填入字符串表达式即可,如 : “-=500” (提前0.5秒执行)
动画控制
前面讲到了autoplay时,我们其实就已经用了一个动画控制
anime.play(); 就可以控制动画的播放
anime.pause(); 就可以控制动画的暂停,暂停后再使用 play(); 即可从暂停位置继续播放动画
但是要注意,animejs中没有停止,如果你希望停止动画并让动画恢复到原位,需要你调整动画的时间位置
anime.restart() 重播,执行重播后,动画将从第0秒第一帧开始播放
anime.reverse(); 让动画反向播放,这里和动画配置里的direction:reverse 功能一样,但是,使用函数可以更有效的控制动画的正向反向播放
anime.seek( time ) 让动画播放到指定的时间,比如说,我当前动画是10秒,那么,我anime.seek(5000),就可以让动画切换到第5秒的位置
上述属性对timeline均有效
回调函数
let anime1 = anime({
targets:"#d1",
left:500,
autoplay:false,
delay:500,
endDelay:500,
loop:5,
begin:()=>{ //动画开始前执行,在delay之前
index ++;
console.log(index + " begin");
},
update:()=>{
//动画每执行一帧执行一次
},
loopBegin:()=>{//循环开始前执行
index ++;
console.log(index + " loopBegin");
},
loopComplete: ()=>{//一轮循环结束后执行
index ++;
console.log(index + " loopComplete");
},
complete:()=>{
index ++;
console.log(index + " complete");
},
change:()=>{
//动画每变化一次执行一次
},
changeBegin:()=>{//变化开始前执行
index ++;
console.log(index + " changeBegin");
},
changeComplete: ()=>{//变化结束后执行
index ++;
console.log(index + " changeComplete");
}
});
btn.onclick = ()=>{
anime1.play();
}
除去两个执行频率特别高的,我打印了执行五次循环的一段动画的生命周期
begin 在动画的延迟时间之前,一般用于执行一些动画开始前的代码
loopBegin 是在每一轮循环的开始之前,一般用于动画循环过程中的监听
loopComplete是在每一轮循环播放结束后,同上,但是该函数主要在循环结束时执行
changeComplete是在每次动画目标数据变化结束时执行,会跟在loopComplete后面执行,即使没有循环它也会在动画播放完成后执行,主要用于动画结束后执行下一段脚本时使用
complete会在动画全部执行完毕时执行,若loop设置为true时,则不执行,同上
一般来说我们常用的就是update和change
比如说在threejs中,相机飞入动画
我们不仅要控制相机移动,我们还需要让相机每一帧都看向指定的目标,所以这个时候,update或者change函数就用得上了,我们可以用这两属性监听相机,每次相机变化就让它lookAt一下目标,这样就能实现流畅平滑的动画了,这个案例在上方有写
结语
到此为止,animejs的基础教程就结束了
部分animejs的内容比较绕,需要你多写代码才能够感受到,尤其是时间线的部分
animejs特别适合与threejs,pixijs等webgl框架搭配使用,如果你在学习threejs的话,那么笔者强烈推荐你学习一下animejs
animejs有很多高级用法,这里笔者并未提及,如官方文档中的helper,svg动画,函数参数等,这些由于笔者并未使用过,所以并未将这些内容写出,若需要使用到这些功能时,请参阅官方文档