Home >Web Front-end >JS Tutorial >js animation (animate) simple engine code example_javascript skills

js animation (animate) simple engine code example_javascript skills

WBOY
WBOYOriginal
2016-05-16 17:47:201741browse

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

Copy code The code is as follows:

var animation = function(obj) {
    this.obj = obj;
    this.frames = 0;
    this.timmer = undefined;
    this.running = false;
    this.ms = [];
}

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

复制代码 代码如下:

$util = {
    /**
* Type detection
*/
    type : function(obj){
        var rep = /[objects (w )]/i;
        var str = Object.prototype.toString.call(obj).toLowerCase();
        str.match(rep);
        return RegExp.$1;
    },
    /**
* Deep copy
*/
    $unlink :function (object){
        var unlinked;
        switch ($type(object)){
            case 'object':
                unlinked = {};
                for (var p in object) {
                    unlinked[p] = $unlink(object[p]);
                }
            break;
            case 'array':
                unlinked = [];
                for (var i = 0, l = object.length; i < l; i ) {
                    unlinked[i] = $unlink(object[i]);
                }
            break;
            default: return object;
        }
        return unlinked;
    },
    /**
*Dom related operations
*/
    dom:{
        $: function(id) {
            return document.getElementById(id);
        },
        getStyle: function(obj, prop) {
            var style = obj.currentStyle || window.getComputedStyle(obj, '');
            if (obj.style.filter) {
                return obj.style.filter.match(/d /g)[0];
            }
            return style[prop];
        },
        setStyle: function(obj, prop, val) {
            switch (prop) {
            case 'opacity':
                if($util.client.browser.ie){
                    obj.style.filter = 'alpha(' prop '=' val*100 ')'   
                }else{
                    obj.style[prop] = val;
                }
                break;
            default:
                obj.style[prop] = val 'px';
                break;
            }
        },
        setStyles: function(obj, props) {
            for (var prop in props) {
                switch (prop) {
                case 'opacity':
                    if($util.client.browser.ie){
                        obj.style.filter = 'alpha(' prop '=' props[prop] ')'       
                    }else{
                        obj.style[prop] = props[prop];
                    }
                    break;
                default:
                    obj.style[prop] = props[prop] 'px';
                    break;
                }
            }
        }
    },
    /**
*Event event related
*/
    evt : {
        addEvent : function(oTarget, sEventType, fnHandler) {
            if (oTarget.addEventListener) {
                oTarget.addEventListener(sEventType, fnHandler, false);
            } else if (oTarget.attachEvent) {
                oTarget.attachEvent("on" sEventType, fnHandler);
            } else {
                oTarget["on" sEventType] = fnHandler;
            }
        },
        rmEvent : function removeEventHandler (oTarget, sEventType, fnHandler) {
            if (oTarget.removeEventListener) {
                oTarget.removeEventListener(sEventType, fnHandler, false);
            } else if (oTarget.detachEvent) {
                oTarget.detachEvent("on" sEventType, fnHandler);
            } else {
                oTarget["on" sEventType] = null;
            }
        }
    },
    /**
*Ajax asynchronous loading
*/
    ajax : {
        request:function (options) {
                var xhr, res;
                var url = options.url,
                    context = options.context,
                    success = options.success,
                    type = options.type,
                    datatype = options.datatype,
                    async = options.async,
                    send = options.send,
                    headers = options.headers;

                try {
                    xhr = new XMLHttpRequest();
                } catch(e) {
                    try {
                        xhr = new ActiveXObject('MSXML2.XMLHTTP');
                    } catch(e) {
                        xhr = new ActiveXObject('Microsoft.XMLHTTP');
                    }
                }

                xhr.onreadystatechange = function() {
                    if (xhr.readyState == 4 && xhr.status == 200) {
                        res = xhr.responseText;
                        success(res);
                    }
                }
                xhr.open(type, url, async);
                xhr.send(send);
        }
    },
    /**
*Array array related
*/
    array : {
        minIndex : function(ary){
            return Math.min.apply(null,ary);   
        },
        maxitem : function(ary){
            return Math.max.apply(null,ary);
        }
    },
/**
*Client 客户端检测
*/
client : function(){
// 浏览器渲染引擎 engines
var engine = {
ie: 0,
gecko: 0,
webkit: 0,
khtml: 0,
opera: 0,

//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;
            }
        }
    }

}

下面是个应用:

复制代码 代码如下:





无标题文档




   


       

       

           

                   

  •                

  •                

  •                

  •                

  •                

  •                

  •                

  •