Students who are used to jquery, I believe they all appreciate its animation engine. It is indeed relatively perfect! If your imagination is rich enough, I believe you can achieve effects beyond your imagination. Of course, compared with the 2D library, it is still far behind. jQuery is not designed specifically for animation at all. In terms of simulating the real world, it is still insufficient. But I am still comfortable in the web world. Animation has actually always been the exclusive domain of flash (only in the web area). It's just that it often becomes a loophole for hackers to attack, and it requires the installation of plug-ins. Sometimes the files are too large, and the performance consumption is really high. After HTML5 appeared, Adobe itself actually shifted its position to HTML5. Of course, I think flash will not be abandoned for a long time.
To make a long story short, let’s get to the point. Following the animation principle of flash, I wrote a very simple js animation engine.
First of all, students who have used flash all know it. Flash has a timeline, which is full of "frames". In fact, each frame is a shot, and the shots are connected together to create an animation effect. In fact, this is the same as the principle of movies. Anyone who played with film as a child knows that you can see a pair of lenses when you hold it against the light. There is a time limit for the human eye to distinguish the continuity of two images. If you don't want to see the flicker when the two images change, it's about 30 frames/second, and the movie is 24 frames. Therefore, if it can be guaranteed that animation switching can be guaranteed 30 times per second, basically, the smooth effect of animation will be achieved, but this depends on the environment. So it depends on the specific situation. In fact, I don’t know much about this aspect. As far as this animation engine is concerned, it is almost enough to know this much. If you are interested, you can look up related knowledge!
Let’s start with the design principles.
First you need a frame rate, which is how many frames per second. If you have the total time, you can calculate how many "pictures" (total number of frames) there are in the entire animation. This design obviously has a shortcoming. It cannot guarantee that the time is exactly an integer frame. Unless one frame is 1ms. This was suggested by a netizen, but I didn’t feel comfortable with it so I didn’t adopt it. So this engine has a time error. With the total number of frames, when the animation reaches the last frame, the entire animation will be played. If repeated playback is required, reset the current frame to 0. This kind of animation has the advantage that it can run directly to the specified frame. That is gotoAndStop and gotoAndPlay in flash. In fact, the entire animation design principle is implemented according to flash. Includes a very important method: enterFrame. The position is calculated based on entering the current frame. There are other methods: stop, play, next...etc. Because currently, this engine is very simple and rough to write, let's not go into such details. Let’s start with the code and examples!
animate.js animation core
animation.prototype = {
fps: 36,
init: function(props, duration, tween) {
//console.log('初始化');
this.curframe = 0;
this.initstate = {};
this.props = props;
this.duration = duration || 1000;
this.tween = tween || function(t, b, c, d) {
return t * c / d + b;
};
this.frames = Math.ceil(this.duration * this.fps/1000);
for (var prop in this.props) {
this.initstate[prop] = {
from: parseFloat($util.dom.getStyle(this.obj, prop)),
to: parseFloat(this.props[prop])
};
}
},
start: function() {
if (!this.running && this.hasNext()) {
//console.log('可以执行...');
this.ms.shift().call(this)
}
return this;
},
//开始播放
play: function(callback) {
//console.log('开始动画!');
var that = this;
this.running = true;
if (this.timmer) {
this.stop();
}
this.timmer = setInterval(function() {
if (that.complete()) {
that.stop();
that.running = false;
if (callback) {
callback.call(that);
}
return;
}
that.curframe++;
that.enterFrame.call(that);
},
/ this.fps);
return this;
},
// 停止动画
stop: function() {
//console.log('结束动画!');
if (this.timmer) {
clearInterval(this.timmer);
// 清除掉timmer id
this.timmer = undefined;
}
},
go: function(props, duration, tween) {
var that = this;
//console.log(tween)
this.ms.push(function() {
that.init.call(that, props, duration, tween);
that.play.call(that, that.start);
});
return this;
},
//向后一帧
next: function() {
this.stop();
this.curframe ;
this.curframe = this.curframe > this.frames ? this.frames: this.curframe;
this.enterFrame.call(this);
},
//向前一帧
prev: function() {
this.stop();
this.curframe--;
this.curframe = this.curframe < 0 ? 0 : this.curframe;
this.enterFrame.call(this);
},
//跳跃到指定帧并播放
gotoAndPlay: function(frame) {
this.stop();
this.curframe = frame;
this.play.call(this);
},
//跳到指定帧停止播放
gotoAndStop: function(frame) {
this.stop();
this.curframe = frame;
this.enterFrame.call(this);
},
//进入帧动作
enterFrame: function() {
//console.log('进入帧:' this.curframe)
var ds;
for (var prop in this.initstate) {
//console.log('from: ' this.initstate[prop]['from'])
ds = this.tween(this.curframe, this.initstate[prop]['from'], this.initstate[prop]['to'] - this.initstate[prop]['from'], this.frames).toFixed(2);
//console.log(prop ':' ds)
$util.dom.setStyle(this.obj, prop, ds)
}
},
//动画结束
complete: function() {
return this.curframe >= this.frames;
},
hasNext: function() {
return this.ms.length > 0;
}
}
util.js
//complete version
ver: null
};
// 浏览器
var browser = {
//browsers
ie: 0,
firefox: 0,
safari: 0,
konq: 0,
opera: 0,
chrome: 0,
//specific version
ver: null
};
// 客户端平台platform/device/OS
var system = {
win: false,
mac: false,
x11: false,
//移动设备
iphone: false,
ipod: false,
ipad: false,
ios: false,
android: false,
nokiaN: false,
winMobile: false,
//game systems
wii: false,
ps: false
};
// 检测浏览器引擎
var ua = navigator.userAgent;
if (window.opera){
engine.ver = browser.ver = window.opera.version();
engine.opera = browser.opera = parseFloat(engine.ver);
} else if (/AppleWebKit/(S )/.test(ua)){
engine.ver = RegExp["$1"];
engine.webkit = parseFloat(engine.ver);
//figure out if it's Chrome or Safari
if (/Chrome/(S )/.test(ua)){
browser.ver = RegExp["$1"];
browser.chrome = parseFloat(browser.ver);
} else if (/Version/(S )/.test(ua)){
browser.ver = RegExp["$1"];
browser.safari = parseFloat(browser.ver);
} else {
//approximate version
var safariVersion = 1;
if (engine.webkit < 100){
safariVersion = 1;
} else if (engine.webkit < 312){
safariVersion = 1.2;
} else if (engine.webkit < 412){
safariVersion = 1.3;
} else {
safariVersion = 2;
}
browser.safari = browser.ver = safariVersion;
}
} else if (/KHTML/(S )/.test(ua) || /Konqueror/([^;] )/.test(ua)){
engine.ver = browser.ver = RegExp["$1"];
engine.khtml = browser.konq = parseFloat(engine.ver);
} else if (/rv:([^)] )) Gecko/d{8}/.test(ua)){
engine.ver = RegExp["$1"];
engine.gecko = parseFloat(engine.ver);
//determine if it's Firefox
if (/Firefox/(S )/.test(ua)){
browser.ver = RegExp["$1"];
browser.firefox = parseFloat(browser.ver);
}
} else if (/MSIE ([^;] )/.test(ua)){
engine.ver = browser.ver = RegExp["$1"];
engine.ie = browser.ie = parseFloat(engine.ver);
}
//detect browsers
browser.ie = engine.ie;
browser.opera = engine.opera;
//detect platform
var p = navigator.platform;
system.win = p.indexOf("Win") == 0;
system.mac = p.indexOf("Mac") == 0;
system.x11 = (p == "X11") || (p.indexOf("Linux") == 0);
//detect windows operating systems
if (system.win){
if (/Win(?:dows )?([^do]{2})s?(d .d )?/.test(ua)){
if (RegExp["$1"] == "NT"){
switch(RegExp["$2"]){
case "5.0":
system.win = "2000";
break;
case "5.1":
system.win = "XP";
break;
case "6.0":
system.win = "Vista";
break;
case "6.1":
system.win = "7";
break;
default:
system.win = "NT";
break;
}
} else if (RegExp["$1"] == "9x"){
system.win = "ME";
} else {
system.win = RegExp["$1"];
}
}
}
//mobile devices
system.iphone = ua.indexOf("iPhone") > -1;
system.ipod = ua.indexOf("iPod") > -1;
system.ipad = ua.indexOf("iPad") > -1;
system.nokiaN = ua.indexOf("NokiaN") > -1;
//windows mobile
if (system.win == "CE"){
system.winMobile = system.win;
} else if (system.win == "Ph"){
if(/Windows Phone OS (\d+.\d+)/.test(ua)){;
system.win = "Phone";
system.winMobile = parseFloat(RegExp["$1"]);
}
}
//determine iOS version
if (system.mac && ua.indexOf("Mobile") > -1){
if (/CPU (?:iPhone )?OS (\d+_\d+)/.test(ua)){
system.ios = parseFloat(RegExp.$1.replace("_", "."));
} else {
system.ios = 2; //can't really detect - so guess
}
}
//determine Android version
if (/Android (\d+\.\d+)/.test(ua)){
system.android = parseFloat(RegExp.$1);
}
//gaming systems
system.wii = ua.indexOf("Wii") > -1;
system.ps = /playstation/i.test(ua);
//return it
return {
engine: engine,
browser: browser,
system: system
};
}(),
/**
*Tween easing related
*/
tween: {
Linear: function(t, b, c, d) {
return c * t / d b;
},
Quad: {
easeIn: function(t, b, c, d) {
return c * (t /= d) * t b;
},
easeOut: function(t, b, c, d) {
return - c * (t /= d) * (t - 2) b;
},
easeInOut: function(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t b;
return - c / 2 * ((--t) * (t - 2) - 1) b;
}
},
Cubic: {
easeIn: function(t, b, c, d) {
return c * (t /= d) * t * t b;
},
easeOut: function(t, b, c, d) {
return c * ((t = t / d - 1) * t * t 1) b;
},
easeInOut: function(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t b;
return c / 2 * ((t -= 2) * t * t 2) b;
}
},
Quart: {
easeIn: function(t, b, c, d) {
return c * (t /= d) * t * t * t b;
},
easeOut: function(t, b, c, d) {
return - c * ((t = t / d - 1) * t * t * t - 1) b;
},
easeInOut: function(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t * t b;
return - c / 2 * ((t -= 2) * t * t * t - 2) b;
}
},
Quint: {
easeIn: function(t, b, c, d) {
return c * (t /= d) * t * t * t * t b;
},
easeOut: function(t, b, c, d) {
return c * ((t = t / d - 1) * t * t * t * t 1) b;
},
easeInOut: function(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t b;
return c / 2 * ((t -= 2) * t * t * t * t 2) b;
}
},
Sine: {
easeIn: function(t, b, c, d) {
return - c * Math.cos(t / d * (Math.PI / 2)) c b;
},
easeOut: function(t, b, c, d) {
return c * Math.sin(t / d * (Math.PI / 2)) b;
},
easeInOut: function(t, b, c, d) {
return - c / 2 * (Math.cos(Math.PI * t / d) - 1) b;
}
},
Expo: {
easeIn: function(t, b, c, d) {
return (t == 0) ? b: c * Math.pow(2, 10 * (t / d - 1)) b;
},
easeOut: function(t, b, c, d) {
return (t == d) ? b c: c * ( - Math.pow(2, -10 * t / d) 1) b;
},
easeInOut: function(t, b, c, d) {
if (t == 0) return b;
if (t == d) return b c;
if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) b;
return c / 2 * ( - Math.pow(2, -10 * --t) 2) b;
}
},
Circ: {
easeIn: function(t, b, c, d) {
return - c * (Math.sqrt(1 - (t /= d) * t) - 1) b;
},
easeOut: function(t, b, c, d) {
return c * Math.sqrt(1 - (t = t / d - 1) * t) b;
},
easeInOut: function(t, b, c, d) {
if ((t /= d / 2) < 1) return - c / 2 * (Math.sqrt(1 - t * t) - 1) b;
return c / 2 * (Math.sqrt(1 - (t -= 2) * t) 1) b;
}
},
Elastic: {
easeIn: function(t, b, c, d, a, p) {
if (t == 0) return b;
if ((t /= d) == 1) return b c;
if (!p) p = d * .3;
if (!a || a < Math.abs(c)) {
a = c;
var s = p / 4;
} else var s = p / (2 * Math.PI) * Math.asin(c / a);
return - (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) b;
},
easeOut: function(t, b, c, d, a, p) {
if (t == 0) return b;
if ((t /= d) == 1) return b c;
if (!p) p = d * .3;
if (!a || a < Math.abs(c)) {
a = c;
var s = p / 4;
} else var s = p / (2 * Math.PI) * Math.asin(c / a);
return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) c b);
},
easeInOut: function(t, b, c, d, a, p) {
if (t == 0) return b;
if ((t /= d / 2) == 2) return b c;
if (!p) p = d * (.3 * 1.5);
if (!a || a < Math.abs(c)) {
a = c;
var s = p / 4;
} else var s = p / (2 * Math.PI) * Math.asin(c / a);
if (t < 1) return - .5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) b;
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * .5 c b;
}
},
Back: {
easeIn: function(t, b, c, d, s) {
if (s == undefined) s = 1.70158;
return c * (t /= d) * t * ((s 1) * t - s) b;
},
easeOut: function(t, b, c, d, s) {
if (s == undefined) s = 1.70158;
return c * ((t = t / d - 1) * t * ((s 1) * t s) 1) b;
},
easeInOut: function(t, b, c, d, s) {
if (s == undefined) s = 1.70158;
if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) 1) * t - s)) b;
return c / 2 * ((t -= 2) * t * (((s *= (1.525)) 1) * t s) 2) b;
}
},
Bounce: {
easeIn: function(t, b, c, d) {
return c - Tween.Bounce.easeOut(d - t, 0, c, d) b;
},
easeOut: function(t, b, c, d) {
if ((t /= d) < (1 / 2.75)) {
return c * (7.5625 * t * t) b;
} else if (t < (2 / 2.75)) {
return c * (7.5625 * (t -= (1.5 / 2.75)) * t .75) b;
} else if (t < (2.5 / 2.75)) {
return c * (7.5625 * (t -= (2.25 / 2.75)) * t .9375) b;
} else {
return c * (7.5625 * (t -= (2.625 / 2.75)) * t .984375) b;
}
},
easeInOut: function(t, b, c, d) {
if (t < d / 2) return Tween.Bounce.easeIn(t * 2, 0, c, d) * .5 b;
else return Tween.Bounce.easeOut(t * 2 - d, 0, c, d) * .5 c * .5 b;
}
}
}
}
下面是个应用: