var ANIMATIONS_GLOBAL_ID = 0, CURRENT_ANIMATIONS = {}, ANIMATION_END_EVENT_NAME, ANIMATION_PROPERTY, ANIMATION_NAME_PROPERTY, ANIMATION_DURATION_PROPERTY, ANIMATION_TIMING_FUNCTION_PROPERTY, ANIMATION_FILL_MODE_PROPERTY, ANIMATION_KEYFRAMES_RULE; var defineCSSProperties = function() { if (this.done) return; ANIMATION_END_EVENT_NAME = CPBrowserStyleProperty("animationend"); ANIMATION_PROPERTY = CPBrowserCSSProperty("animation"); ANIMATION_NAME_PROPERTY = CPBrowserCSSProperty("animation-name"); ANIMATION_DURATION_PROPERTY = CPBrowserCSSProperty("animation-duration"); ANIMATION_TIMING_FUNCTION_PROPERTY = CPBrowserCSSProperty("animation-timing-function"); ANIMATION_FILL_MODE_PROPERTY = CPBrowserCSSProperty("animation-fill-mode"); ANIMATION_KEYFRAMES_RULE = "@" + ANIMATION_PROPERTY.substring(0, ANIMATION_PROPERTY.indexOf("animation")) + "keyframes"; this.done = true; }; var removeFromParent = function(aNode) { var parentNode = aNode.parentNode; if (parentNode) parentNode.removeChild(aNode); }; CSSAnimation = function(aTarget/* DOM Element */, anIdentifier, aTargetName) { defineCSSProperties(); if (!anIdentifier) anIdentifier = ANIMATIONS_GLOBAL_ID++; var animationName = "anim_" + anIdentifier, animation = CURRENT_ANIMATIONS[anIdentifier]; if (animation) { console.warn("Animation " + anIdentifier + " is already in use. Ignoring."); } else { this.target = aTarget; this.identifier = anIdentifier; this.targetName = aTargetName; this.animationName = animationName; this.listener = null; this.styleElement = null; this.propertyanimations = []; this.animationsnames = []; this.animationstimingfunctions = []; this.animationsdurations = []; this.islive = false; this.didBuildDOMElements = false; animation = this; CURRENT_ANIMATIONS[anIdentifier] = animation; } return animation; }; CSSAnimation.prototype.description = function() { return ""; } CSSAnimation.prototype.addPropertyAnimation = function(propertyName/* String */, valueFunction/* Function */, aDuration/* float */, aKeyTimes/* (d, [d]) */, aValues/* Array */, aTimingFunctions/* [d,d,d,d], [[d,d,d,d]] */, aCompletionfunction/* Function */) { if (this.islive) return false; // TODO: If a property already exist, replace its values & valueFunctions. var fullAnimationName = this.animationName + "_" + propertyName; var animation = { name: fullAnimationName, property: propertyName, valuefunction: valueFunction, keytimes: aKeyTimes, values: aValues, duration: aDuration, completionfunction: aCompletionfunction }; var animationTimingFunction; if (aTimingFunctions && (aTimingFunctions[0] instanceof Array)) { animation.keyframestimingFunctions = aTimingFunctions; // dummy timing function overriden by keyframes timings functions animationTimingFunction = "linear"; } else { animationTimingFunction = "cubic-bezier(" + aTimingFunctions + ")"; } this.animationstimingfunctions.push(animationTimingFunction); this.propertyanimations.push(animation); this.animationsnames.push(fullAnimationName); this.animationsdurations.push(aDuration + "s"); return true; }; CSSAnimation.prototype.keyFrames = function() { var keyframesRules = []; var count = this.propertyanimations.length; for (var i = 0; i < count; i++) { var animation = this.propertyanimations[i], property = animation.property, valuefunction = animation.valuefunction, keytimes = animation.keytimes, values = animation.values, timingFunctions = animation.keyframestimingFunctions; var keyframes = [], keytimescount = keytimes.length, start_value = values[0]; for (var j = 0; j < keytimescount; j++) { var keytime = keytimes[j], value = values[j], timingFunction; if (valuefunction !== null) value = valuefunction(start_value, value); var keyframeContent = property + ": " + value + ";"; if (timingFunctions && timingFunctions.length && (timingFunction = timingFunctions[j])) { keyframeContent += ANIMATION_TIMING_FUNCTION_PROPERTY + ":cubic-bezier(" + timingFunction + ");"; } var keyframe = "\t" + Math.round(keytime * 100) + "% {\n\t\t" + keyframeContent + "\n\t}\n"; keyframes.push(keyframe); } // TODO ! Add keyframe rule to CPCompatibility var rule = ANIMATION_KEYFRAMES_RULE + " " + animation.name + " {\n" + keyframes.join(" ") + "}\n"; keyframesRules.push(rule); } return keyframesRules.join("\n"); }; CSSAnimation.prototype.appendKeyFramesRule = function() { var styleElement = this.createKeyFramesStyleElement(), keyframesText = this.keyFrames(), nodeText = document.createTextNode(keyframesText); styleElement.appendChild(nodeText); document.head.appendChild(styleElement); }; CSSAnimation.prototype.createKeyFramesStyleElement = function() { if (!this.styleElement) { var styleElement = document.createElement("style"); styleElement.setAttribute("type", "text/css"); this.styleElement = styleElement; } return this.styleElement; }; CSSAnimation.prototype.endEventListener = function() { var animation = this, animationsNames = this.animationsnames, inFlightAnimationsNames = animationsNames.slice(); if (!animation.listener) { var AnimationEndListener = function(event) { var idx = inFlightAnimationsNames.indexOf(event.animationName); if (idx !== -1) inFlightAnimationsNames.splice(idx, 1); if (inFlightAnimationsNames.length > 0) return; for (var i = 0; i < animationsNames.length; i++) { var completion = animation.completionFunctionForAnimationName(animationsNames[i]); if (completion) completion(); } var eventTarget = event.target, style = eventTarget.style; try { style.removeProperty(ANIMATION_NAME_PROPERTY); style.removeProperty(ANIMATION_DURATION_PROPERTY); style.removeProperty(ANIMATION_FILL_MODE_PROPERTY); style.removeProperty("-webkit-backface-visibility"); if (animation.animationstimingfunctions.length) style.removeProperty(ANIMATION_TIMING_FUNCTION_PROPERTY); removeFromParent(animation.styleElement); eventTarget.removeEventListener(ANIMATION_END_EVENT_NAME, AnimationEndListener); animation.listener = null; delete (CURRENT_ANIMATIONS[animation.identifier]); } catch (err) { console.warn("CSSAnimation.j - " + err); } }; this.listener = AnimationEndListener; } return this.listener; }; CSSAnimation.prototype.completionFunctionForAnimationName = function(aName) { var propanims = this.propertyanimations, count = propanims.length; while (count--) { var anim = propanims[count]; if (anim.name === aName) return anim.completionfunction; } return null; }; CSSAnimation.prototype.addAnimationEndEventListener = function() { var listener = this.endEventListener(); this.target.addEventListener(ANIMATION_END_EVENT_NAME, listener, false); }; CSSAnimation.prototype.setTargetStyleProperties = function() { var style = this.target.style; if (this.animationstimingfunctions.length) style.setProperty(ANIMATION_TIMING_FUNCTION_PROPERTY, this.animationstimingfunctions.join(",")); style.setProperty(ANIMATION_DURATION_PROPERTY, this.animationsdurations.join(",")); style.setProperty(ANIMATION_FILL_MODE_PROPERTY, "forwards"); // http://webdesign.tutsplus.com/tutorials/htmlcss-tutorials/css3-animations-the-hiccups-and-bugs-youll-want-to-avoid/ style.setProperty("-webkit-backface-visibility", "hidden"); }; CSSAnimation.prototype.buildDOMElements = function() { this.appendKeyFramesRule(); this.addAnimationEndEventListener(); this.setTargetStyleProperties(); this.didBuildDOMElements = true; }; CSSAnimation.prototype.start = function() { if (this.propertyanimations.length === 0 || this.islive) return false; if (!this.didBuildDOMElements) this.buildDOMElements(); this.target.style.setProperty(ANIMATION_NAME_PROPERTY, this.animationsnames.join(",")); this.islive = true; return true; };