所谓的动画,就是通过一些列的运动形成的动的画面。在网页中,我们可以通过不断的改变元素的css值,来达到动的效果。
JavaScript的动画用的最多的3个api就是setInterval()、setTimeout()和requestAnimationFrame()
据说,普通人眼能看到1/24秒,就是说1秒至少24帧,每次移位间隔需要小于1000/24=41.7毫秒,也就说setInterval要每隔至少40毫秒执行一次,一般地,我们采用10毫秒,当然间隔时间越短,客户端执行计算次数就越多,如果你code计算量大则可以适当调长些。
像setTimeout、setInterval一样,requestAnimationFrame是一个全局函数。调用requestAnimationFrame后,它会要求浏览器根据自己的频率进行一次重绘,它接收一个回调函数作为参数,在即将开始的浏览器重绘时,会调用这个函数,并会给这个函数传入调用回调函数时的时间作为参数。由于requestAnimationFrame的功效只是一次性的,所以若想达到动画效果,则必须连续不断的调用requestAnimationFrame,就像我们使用setTimeout来实现动画所做的那样。requestAnimationFrame函数会返回一个资源标识符,可以把它作为参数传入cancelAnimationFrame函数来取消requestAnimationFrame的回调。跟setTimeout的clearTimeout很相似啊。
可以这么说,requestAnimationFrame是setTimeout的性能增强版。
有一点需要注意的是,requestAnimationFrame不能自行指定函数运行频率,而是有浏览器决定刷新频率。所以这个更能达到浏览器所能达到的最佳动画效果了。
这个方法不是所有的浏览器都兼容。
下面就从最简单的版本进行出发:(可以直接看最后一个版本)
首先这些版本都有同一个js文件是一样的,那就是Easing文件,这里封装了很多的匀速,匀加速,弹跳等等动画,会返回一个动画算子,所以这个文件是共有的,我先放上来:
Easing.js
var pow = Math.pow,
BACK_CONST = 1.70158;
// t指的的是动画进度(百分比) 归一化的时间 前面的p
Easing = {
// 匀速运动
linear: function (t){
return t;
},
// 匀加速运动
easeIn: function (t){
return t * t;
},
// 减速运动
easeOut: function (t){
return (2 - t) * t;
},
//先加速后减速
easeBoth: function (t){
return (t *= 2) < 1 ? .5 * t * t : .5 * (1 - (--t) * (t - 2));
},
// 4次方加速
easeInStrong: function (t){
return t * t * t * t;
},
// 4次方法的减速
easeOutStrong: function (t){
return 1 - (--t) * t * t * t;
},
// 先加速后减速,加速和减速的都比较剧烈
easeBothStrong: function (t){
return (t *= 2) < 1 ? .5 * t * t * t * t : .5 * (2 - (t -= 2) * t * t * t);
},
//
easeOutQuart: function (t){
return -(Math.pow((t - 1), 4) - 1)
},
// 指数变化 加减速
easeInOutExpo: function (t){
if (t === 0) return 0;
if (t === 1) return 1;
if ((t /= 0.5) < 1) return 0.5 * Math.pow(2, 10 * (t - 1));
return 0.5 * (-Math.pow(2, -10 * --t) + 2);
},
//指数式减速
easeOutExpo: function (t){
return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1;
},
// 先回弹,再加速
swingFrom: function (t){
return t * t * ((BACK_CONST + 1) * t - BACK_CONST);
},
// 多走一段,再慢慢的回弹
swingTo: function (t){
return (t -= 1) * t * ((BACK_CONST + 1) * t + BACK_CONST) + 1;
},
//弹跳
bounce: function (t){
var s = 7.5625,
r;
if (t < (1 / 2.75)){
r = s * t * t;
}else if (t < (2 / 2.75)){
r = s * (t -= (1.5 / 2.75)) * t + .75;
}else if (t < (2.5 / 2.75)){
r = s * (t -= (2.25 / 2.75)) * t + .9375;
}else{
r = s * (t -= (2.625 / 2.75)) * t + .984375;
}
return r;
}
};
最基本的,没有用动画Easing.js文件:
Title
需要明白这一个个版本是相互递进的,每个新的版本实现的功能一步一步深入。
①用了Easing.js文件。
②HTML文件
Title
开始封装框架。
①用了Easing.js文件。
②HTML文件
Title
③框架js文件:
/*
为了实现更加复杂的动画,我们可以将动画进行 简易 的封装,要进行封装,我们先要抽象出动画相关的要素:
动画时长:T = duration
动画进程:p = t/T 进度(百分比)
easing: e = f(p) (动画算子:p的函数 )
动画方程: x = g(e) y = g(e) (动画的位移相对于动画算子的方程)
动画生命周期:开始、进程中、结束
*/
/*
参数1:动画的执行时间(周期duration)
参数2:动画算子. 如果没有传入动画算子,则默认使用匀速算子
参数3:动画执行的时候的回调函数(动画执行的要干的事情)
*/
function MyAnimator(duration,easing,callBack) {
this.duration=duration;
this.easing=easing || function (p) {
return p;
};
this.callBack=callBack;
}
MyAnimator.prototype={
start:function () {
var startTime=new Date();
var that= this; //回调函数this丢失
requestAnimationFrame(function step() {
var p = Math.min(new Date()-startTime)/that.duration; //p是动画算子
var e = that.easing(p); //得到p的具体值,然后去调用动画算子函数,返回一个值
that.callBack(e); //把需要的E给调用函数(在HTML中script中用户会自己去写这个回调函数)
if(p<1){
requestAnimationFrame(step);
}
});
},
}
①用了Easing.js文件。
②HTML文件
Title
③框架js文件:
/*
为了实现更加复杂的动画,我们可以将动画进行 简易 的封装,要进行封装,我们先要抽象出动画相关的要素:
动画时长:T = duration
动画进程:p = t/T 进度(百分比)
easing: e = f(p) (动画算子:p的函数 )
动画方程: x = g(e) y = g(e) (动画的位移相对于动画算子的方程)
动画生命周期:开始、进程中、结束
*/
/*
参数1:动画的执行时间(周期duration)
参数2:动画算子. 如果没有传入动画算子,则默认使用匀速算子
参数3:动画执行的时候的回调函数(动画执行的要干的事情)
*/
function MyAnimator(duration,easing,callBack) {
this.duration=duration;
this.easing=easing || function (p) {
return p;
};
this.callBack=callBack;
}
MyAnimator.prototype={
start:function () {
var startTime=new Date();
var that= this; //回调函数this丢失
requestAnimationFrame(function step() {
var p = Math.min(new Date()-startTime)/that.duration; //p是动画算子
var e = that.easing(p); //得到p的具体值,然后去调用动画算子函数,返回一个值
that.callBack(e); //把需要的E给调用函数(在HTML中script中用户会自己去写这个回调函数)
if(p<1){
requestAnimationFrame(step);
}
});
},
}
①用了Easing.js文件。
②HTML文件
Title
③框架js文件:
/*
参数1:动画的执行时间
参数2:动画算子. 如果没有传入动画算子,则默认使用匀速算子
参数3:动画执行的时候的回调函数(动画执行的要干的事情)
*/
function MyAnimatorPlus(duration,Easing,callBack) {
//默认参数,如果用户没有输入周期,动画运算函数,回调函数,我们就可以进行默认处理
this.duration = duration || 1000;
this.Easing = Easing || Easing.linear;
this.callBack = callBack || function () {};
}
MyAnimatorPlus.prototype = {
/*开始动画的方法,
参数:一个数字,执行的次数
*/
start: function(count) {
if(count==undefined || count ==null ||count <=0) count=1;
/*动画开始时间*/
var startTime = new Date();
/*回调函数的this失效*/
self = this;
/*定义动画执行函数*/
requestAnimationFrame(function step() {
/*得到动画执行进度*/
var p = Math.min(1,(new Date()-startTime)/self.duration);
self.callBack(self.Easing(p));
//判断动画进度是否完成,count进行判断,以实现循环执行
//当p==1时,那就是说明一次动画执行完成,判断count值
if(p==1){
count--; //一次写完就要要减去count值,再去判断是否小于0
if(count > 0){ //如果大于0 则说明还要在执行,那么这个时候就要有新的时间
startTime = new Date();
requestAnimationFrame(step);
}
}else{ //else的部分就是P<1说明每次的一次动画还未结束,所以要继续运动
requestAnimationFrame(step);
}
/*
//另外的写法,简便一些
//如果p等于1 并且 操作count-- ,如现在count的值是1,那么--就是0了(前面已经执行了一遍,初始化),便不在执行
if(p==1 && --count){
startTime = new Date();
requestAnimationFrame(step);
}else if(p<1){
requestAnimationFrame(step);
}*/
});
},
};
直接可以用这个,主要用到了:
①用了Easing.js文件。(最前面一个)我的命名是Easing.js
②HTML文件.(我的命名是lastVersion.html)
Title
③框架js文件
(我的命名是:MyAnimatorPlusPlus.js)
这个框架是直接把传入的实参直接用对象包括起来,多个不同的动画效果是用数组进行存贮。
这个框架可以实现的功能有:
可以反转:是为normal 这个正常运动,另外一个是reserve是一次正常一次反转。两个值可选。
周期:传入每一个动画的周期
动画算子函数:传入
回调函数:传入用户想要的效果(其中距离要自己写)
具体效果如下:
/*执行动画序列
封装一个对象
duration:动画的执行时间
eases:动画算子. 如果没有传入动画算子,则默认使用匀速算子
callBack:动画执行的时候的回调函数(动画执行的要干的事情)
*/
function MyAnimatorPlusPlus(opts){
this.opts = opts;
}
/*几个参数作用:
*count:是执行几回,用户传来3,就要循环执行三次。执行一次减掉一次。
*index:就是durations 、callBacks 、Easings三个数组的下标,一一对应,下标不要越界,一越界就变成0
* i:初始化为0,看他执行了几个count,如果是奇数,就是反转,反转就是要逆转数组。为反转做准备的变量。
*
* */
MyAnimatorPlusPlus.prototype = {
start: function (count){
//当在start()中存在不传,负值,字符串都可以进行操作
if (count == undefined || count == null || count <= 0) count = 1;
var durations = this.opts.durations,
Easings = this.opts.Easings,
callBacks = this.opts.callBacks,
selectDirection = this.opts.selectDirection;
var startTime = new Date(),
self = this,
index = 0; // 表示正在执行的动画的索引,默认是用数组0的开始执行
var i = 0; //默认0,为改变方向做准备的变量,当他为奇数(也就是执行第二次,那就改变方向咯)
requestAnimationFrame(function step(){
var p = Math.min(1, (new Date() - startTime) / durations[index]); //归一化时间,注意写法
/*self.callBacks[index](self.eases[index](p));*/
var e = Easings[index](p); //得到动画算子
/*判断如果是reverse,并且执行动画已经第二次了,那么就改变方向 i如果是奇数,那么就是第二次执行了,就改变方向*/
if (i % 2 == 1 && selectDirection == "reverse"){
e = 1 - e;
}
callBacks[index](e);
/*核心代码,深度理解:
* p==1表示已经执行了一回
* 那现在是反方向执行还是继续下一个回调函数的执行呢?
* 我们当然设计成下一个回调函数执行咯,所以,先对index加加,然后进行执行下一个回调函数
* 当一轮执行完之后,也就是index到达了回调函数的个数时,我们就要去对count执行次数减掉1
* 然后对已经执行的次数加1,用来判断是否要反方向
* 并且重现new date 进行一次新的变换
* */
if (p == 1){ // fun fun fun三个回调函数依次执行,p==1表示已经执行了一回
index++;
if (index < callBacks.length){
startTime = new Date();
requestAnimationFrame(step);
}else{
count--;
i++;
if (count > 0){
//走到这里就表示往右往下往左往下这一个轮回走完了,已经执行好了一回
index = 0; // 如果完成了一次, 下一次继续从回调函数序列中第 0 个继续执行
startTime = new Date();
//执行完一轮,下一轮就要反转数组,让从最后一个回调函数开始,也就是当前的位置
if (selectDirection == "reverse"){
durations.reverse();
Easings.reverse();
callBacks.reverse();
}
requestAnimationFrame(step)
}
}
}else{
requestAnimationFrame(step);
}
})
}
}