static

wm-css3animate.js 13KB

    /** * wm.css3animate - a css3 animation library that supports chaning/callbacks */ /* EXAMPLE $("#animate").css3Animate({ width: "100px", height: "100px", x: "20%", y: "30%", time: "1000ms", opacity: .5, callback: function () { //execute when finished } }); //Chain animations $("#animate").css3Animate({ x: 20, y: 30, time: "300ms", callback: function () { $("#animate").css3Animate({ x: 20, y: 30, time: "500ms", previous: true, callback: function () { reset(); } }); } }); */ /* global wm*/ /* global numOnly*/ define(["jcl","wmBase"],function($){ "use strict"; var cache = []; var objId = function(obj) { if (!obj.wmCSS3AnimateId) obj.wmCSS3AnimateId = $.uuid(); return obj.wmCSS3AnimateId; }; var getEl = function(elID) { if (typeof elID === "string" || elID instanceof String) { return document.getElementById(elID); } else if ($.is$(elID)) { return elID[0]; } else { return elID; } }; var getCSS3Animate = function(obj, options) { var tmp, id, el = getEl(obj); //first one id = objId(el); if (cache[id]) { cache[id].animate(options); tmp = cache[id]; } else { tmp = css3Animate(el, options); cache[id] = tmp; } return tmp; }; $.fn.css3Animate = function(opts) { //keep old callback system - backwards compatibility - should be deprecated in future versions if (!opts.complete && opts.callback) opts.complete = opts.callback; //first on var tmp = getCSS3Animate(this[0], opts); opts.complete = null; opts.sucess = null; opts.failure = null; for (var i = 1; i < this.length; i++) { tmp.link(this[i], opts); } return tmp; }; $.css3AnimateQueue = function() { return new css3Animate.queue(); }; var translateOpen = $.feat.cssTransformStart; var translateClose = $.feat.cssTransformEnd; var transitionEnd = $.feat.cssPrefix.replace(/-/g, "") + "TransitionEnd"; transitionEnd = ($.os.fennec || $.feat.cssPrefix === "" || $.os.ie) ? "transitionend" : transitionEnd; transitionEnd = transitionEnd.replace(transitionEnd.charAt(0), transitionEnd.charAt(0).toLowerCase()); var css3Animate = (function() { var css3Animate = function(elID, options) { if (!(this instanceof css3Animate)) return new css3Animate(elID, options); //start doing stuff this.callbacksStack = []; this.activeEvent = null; this.countStack = 0; this.isActive = false; this.el = elID; this.linkFinishedProxy = $.proxy(this.linkFinished, this); if (!this.el) return; this.animate(options); var that = this; $(this.el).bind("destroy", function() { var id = that.el.wmCSS3AnimateId; that.callbacksStack = []; if (cache[id]) { cache[id] = null; } }); }; css3Animate.prototype = { animate: function(options) { //cancel current active animation on this object if (this.isActive) this.cancel(); this.isActive = true; if (!options) { window.alert("Please provide configuration options for animation of " + this.el.id); return; } var classMode = !! options.addClass; var scale, time; var timeNum = numOnly(options.time); if (classMode) { //class defines properties being changed if (options.removeClass) { $(this.el).replaceClass(options.removeClass, options.addClass); } else { $(this.el).addClass(options.addClass); } } else { //property by property if (timeNum === 0) options.time = 0; if (!options.y) options.y = 0; if (!options.x) options.x = 0; if (options.previous) { var cssMatrix = new $.getCssMatrix(this.el); options.y += numOnly(cssMatrix.f); options.x += numOnly(cssMatrix.e); } if (!options.origin) options.origin = "0% 0%"; if (!options.scale) options.scale = "1"; if (!options.rotateY) options.rotateY = "0"; if (!options.rotateX) options.rotateX = "0"; if (!options.skewY) options.skewY = "0"; if (!options.skewX) options.skewX = "0"; if (!options.timingFunction) options.timingFunction = "linear"; //check for percent or numbers if (typeof(options.x) === "number" || (options.x.indexOf("%") === -1 && options.x.toLowerCase().indexOf("px") === -1 && options.x.toLowerCase().indexOf("deg") === -1)) options.x = parseInt(options.x, 10) + "px"; if (typeof(options.y) === "number" || (options.y.indexOf("%") === -1 && options.y.toLowerCase().indexOf("px") === -1 && options.y.toLowerCase().indexOf("deg") === -1)) options.y = parseInt(options.y, 10) + "px"; var trans = "translate" + translateOpen + (options.x) + "," + (options.y) + translateClose + " scale(" + parseFloat(options.scale) + ") rotate(" + options.rotateX + ")"; if (!$.os.opera) trans += " rotateY(" + options.rotateY + ")"; trans += " skew(" + options.skewX + "," + options.skewY + ")"; this.el.style[$.feat.cssPrefix + "Transform"] = trans; this.el.style[$.feat.cssPrefix + "BackfaceVisibility"] = "hidden"; var properties = $.feat.cssPrefix + "Transform"; if (options.opacity !== undefined) { this.el.style.opacity = options.opacity; properties += ", opacity"; } if (options.width) { this.el.style.width = options.width; properties = "all"; } if (options.height) { this.el.style.height = options.height; properties = "all"; } this.el.style[$.feat.cssPrefix + "TransitionProperty"] = "all"; if (("" + options.time).indexOf("s") === -1) { scale = "ms"; time = options.time + scale; } else if (options.time.indexOf("ms") !== -1) { scale = "ms"; time = options.time; } else { scale = "s"; time = options.time + scale; } if (options.delay) { this.el.style[$.feat.cssPrefix + "TransitionDelay"] = options.delay; } this.el.style[$.feat.cssPrefix + "TransitionDuration"] = time; this.el.style[$.feat.cssPrefix + "TransitionTimingFunction"] = options.timingFunction; this.el.style[$.feat.cssPrefix + "TransformOrigin"] = options.origin; } //add callback to the stack this.callbacksStack.push({ complete: options.complete, success: options.success, failure: options.failure }); this.countStack++; var that = this, duration; var style = window.getComputedStyle(this.el); if (classMode) { //get the duration duration = style[$.feat.cssPrefix + "TransitionDuration"]; timeNum = numOnly(duration); options.time = timeNum; if (duration.indexOf("ms") !== -1) { scale = "ms"; } else { scale = "s"; options.time *= 1000; } } //finish asap if (timeNum === 0 || (scale === "ms" && timeNum < 5) || style.display === "none") { //the duration is nearly 0 or the element is not displayed, finish immediatly $.asap($.proxy(this.finishAnimation, this, [false])); //this.finishAnimation(); //set transitionend event } else { //setup the event normally this.activeEvent = function(event) { clearTimeout(that.timeout); that.finishAnimation(event); that.el.removeEventListener(transitionEnd, that.activeEvent, false); }; that.timeout = setTimeout(this.activeEvent, numOnly(options.time) + 50); this.el.addEventListener(transitionEnd, this.activeEvent, false); } }, addCallbackHook: function(callback) { if (callback) this.callbacksStack.push(callback); this.countStack++; return this.linkFinishedProxy; }, linkFinished: function(canceled) { if (canceled) this.cancel(); else this.finishAnimation(); }, finishAnimation: function(event) { if (event && event.preventDefault) event.preventDefault(); if (!this.isActive) return; this.countStack--; if (this.countStack === 0) this.fireCallbacks(false); }, fireCallbacks: function(canceled) { this.clearEvents(); //keep callbacks after cleanup // (if any of the callbacks overrides this object, callbacks will keep on fire as expected) var callbacks = this.callbacksStack; //cleanup this.cleanup(); //fire all callbacks for (var i = 0; i < callbacks.length; i++) { var complete = callbacks[i].complete; var success = callbacks[i].success; var failure = callbacks[i].failure; //fire callbacks if (typeof(complete) === "function") complete(canceled); //success/failure if (canceled && typeof(failure) === "function") failure(); else if (typeof(success) === "function") success(); } }, cancel: function() { if (!this.isActive) return; this.fireCallbacks(true); //fire failure callbacks }, cleanup: function() { this.callbacksStack = []; this.isActive = false; this.countStack = 0; }, clearEvents: function() { if (this.activeEvent) { this.el.removeEventListener(transitionEnd, this.activeEvent, false); } this.activeEvent = null; }, link: function(elID, opts) { var callbacks = { complete: opts.complete, success: opts.success, failure: opts.failure }; opts.complete = this.addCallbackHook(callbacks); opts.success = null; opts.failure = null; //run the animation with the replaced callbacks getCSS3Animate(elID, opts); //set the old callback back in the obj to avoid strange stuff opts.complete = callbacks.complete; opts.success = callbacks.success; opts.failure = callbacks.failure; return this; } }; return css3Animate; })(); css3Animate.queue = function() { return { elements: [], push: function(el) { this.elements.push(el); }, pop: function() { return this.elements.pop(); }, run: function() { var that = this; if (this.elements.length === 0) return; if (typeof(this.elements[0]) === "function") { var func = this.shift(); func(); } if (this.elements.length === 0) return; var params = this.shift(); if (this.elements.length > 0) { params.complete = function(canceled) { if (!canceled) that.run(); }; } css3Animate(document.getElementById(params.id), params); }, shift: function() { return this.elements.shift(); } }; }; return $; });