/* * hyCMS * Copyright(C)2008 by Friedrich Gräter * Published under the terms of the Lesser GNU General Public License v2 * * AnimationKit backend: Animation controller * * Please Note: This module has to be clean from all dependencies to predicateJS! * */ var ak_behaviours = {}; /* * ak_sizeToNumber(size) * * Converts a string of the form "XXXXpx" (whereas X are digits) to * a number. */ function ak_sizeToNumber(sz) { if (sz == null) return 0; return Number(sz.match(/[0-9]*/)[0]) }; /* * ak_getAnchoredCoordinate(domElement, coordinate, anchor) * * Returns the CSS position of a domElement of the given coordinate (x or y). * The coordinate is seen to the given "anchor" (left/middle/right or top/middle/bottom). * */ function ak_getAnchoredCoordinate(domElement, coordinate, anchor) { var domPos = anchor; var variance = 0; if (anchor == "middle") { var varianceElement = (coordinate == "x") ? "width" : "height"; variance = ak_sizeToNumber(domElement.style[varianceElement]) / 2; domPos = (coordinate == "x") ? "left" : "top"; } else if (anchor == "right") { variance = ak_sizeToNumber(domElement.style["width"]); domPos = "left"; } else if (anchor == "bottom") { variance = ak_sizeToNumber(domElement.style["height"]); domPos = "top"; } return ak_sizeToNumber(domElement.style[domPos]) + variance; } /* * ak_setAnchoredCoordinate(domElement, coordinate, anchor, position) * * Sets the CSS position of a domElement of the given coordinate (x or y) to "position". * The coordinate is seen to the given "anchor" (left/middle/right or top/middle/bottom). * */ function ak_setAnchoredCoordinate(domElement, coordinate, anchor, position) { var domPos = anchor; var variance = 0; if (anchor == "middle") { var varianceElement = (coordinate == "x") ? "width" : "height"; variance = ak_sizeToNumber(domElement.style[varianceElement]) / 2; domPos = (coordinate == "x") ? "left" : "top"; } else if (anchor == "right") { variance = ak_sizeToNumber(domElement.style["width"]); domPos = "left"; } else if (anchor == "bottom") { variance = ak_sizeToNumber(domElement.style["height"]); domPos = "top"; } domElement.style[domPos] = (position - variance) + "px"; } /* * ak_registerBehaviour(name, timerResolution, progessSpaceDimension, startAnimation, stopAnimation, updateAnimation) * * Registers a behaviour with the given "name" to the animationKit. The behaviour requests * a timer resolution of "timerResolution" (frames per second). * * Each point of time of the animation will be associated with a vector of numbers between 0 and 1 * of the dimension "progessSpaceDimension". These vector represent the progress of the animation. For * example a two dimensional progress vector can be used to represent the position of a moving object in * a two dimensional space. A one dimensional vector could be used to represent the opacity of a object * in a fading animation. * * If an animation should be started, the function "startAnimation" will be called. * This function should initialize all internal data structures required to execute the * animation. It has the following signature: * * startAnimation(specification, animationState) ==> boolean * * The parameter "specification" contains the specification object of the animation (a * structure containing animation-specific parameters). The parameter "animationState" is a * reference to a structure, that should be used to store the state data of the animation). * If the function returns "false", the animation will not be started. * * If the animation should stop, the function "stopAnimation" will be called, to clean up * the animated object and associated resources. It has the following signature: * * stopAnimation( animationState, animationAborted, isReplaced ) * * The parameter "animationState" is the same as for "startAnimation". * The parameter "animationAborted" is true, if the animation could not finish. If the * parameter "isReplaced" is true, the animation should be replaced by an identical * animation. * * The parameter "updateAnimation" is a function, that will be called whenever the animation * should be updated. This function has the following signature * * updateAnimation(animationState) * * The parameter "animationState" is the same as for "startAnimation". * The function should use the "progress[]" of the animation object to update the display * of the animated object. * * The animation state object used in startAnimation, stopAnimation and stepAnimation * can store any animation specific data. The animationKit will store the following values * in it: * * domElement The dom element associated with the animation * domTimer The dom interval timer associated with the animation * animationObject The animation descriptor * * duration The duration of the animation * ellapsedTime The time ellapsed since the start of the animation * progress[] The progress states of the animation (numbers between 0..1) * calculateProgress A user-defined function that calculates the progress vector * for a certain point in time * */ function ak_registerBehaviour(name, timerResolution, progressSpaceDimension, startAnimation, stopAnimation, updateAnimation) { var behaviour = {name: name, timerResolution: 1000/timerResolution, progressSpaceDimension: progressSpaceDimension, startAnimation: startAnimation, stopAnimation: stopAnimation, updateAnimation: updateAnimation }; ak_behaviours[name] = behaviour; } /* * ak_applyAnimation(name, domElement, specification, duration[, replaceExisting, calculateProgress, updateCallback, finishCallback]) * * Applies the given behaviour "name" to the given domElement. Further parameters of the animation * are given in the object "specification". The animation should take "duration" ms. If "replaceExisting" * is true, another animation with the same name will be aborted. * * "calculateProgress(animationState)" is a function, that returns the progress of the animation for a certain point * in time. If the parameter is not given, a linear progress function will be used. * * The return value of the function can be either a number between 0 and 1. This number will be used * to determine the entire progress vector. If the function returns an array of numbers, this array * will be used to determine each single element of the progress vector. * * After each update the "updateCallback" will be called. After finishing the animation "finishCallback" will be called. * * Return value: returns the animation state object * */ function ak_applyAnimation(name, domElement, specification, duration, replaceExisting, calculateProgress, updateCallback, finishCallback) { var animation = ak_behaviours[name]; var animationState = {domElement: domElement, domTimer: 0, animationObject: animation, duration: duration, ellapsedTime: 0, progress: [], calculateProgress: calculateProgress }; // Is it a valid animation? if (animation == null) { throw new "Unknown animation"; } // Is a progress function given? if (animationState.calculateProgress == null) { animationState.calculateProgress = ak_linearProgress; } // Setup progress vector if (animation.progressSpaceDimension > 1) { animationState.progress = []; for (var idx = 0; idx < animation.progressSpaceDimension; idx ++) { animationState.progress.push(0); } } else { animationState.progress = 0; } // Setup animation registry if (domElement.__runningAnimations == null) { domElement.__runningAnimations = ({}); } if (domElement.__runningAnimations[name] == null) { domElement.__runningAnimations[name] = []; } // Replacing existing animation? if (replaceExisting == true) { while (domElement.__runningAnimations[name].length > 0) { var oldState = domElement.__runningAnimations[name].pop(); window.clearInterval(oldState.domTimer); animation.stopAnimation( oldState, true, true ); } } // Setup new animation domElement.__runningAnimations[name].push(animationState); // Setup animation animation.startAnimation( specification, animationState ); // Start animation timer animationState.startTime = (new Date()).getTime(); animationState.domTimer = window.setInterval( __callUpdateAnimation, animation.timerResolution ); // Animation update function function __callUpdateAnimation() { // Calculate position var progress = animationState.calculateProgress(animationState); // Update progress if (progress instanceof Array) { for (var idx = 0; idx < progress.length; idx ++) { animationState.progress[idx] = progress[idx]; } } else { if (animationState.progress instanceof Array) { for (var idx = 0; idx < animationState.progress.length; idx ++) { animationState.progress[idx] = progress.valueOf(); } } else { animationState.progress = progress.valueOf(); } } // Update animation animation.updateAnimation(animationState); if (updateCallback != null) updateCallback(animationState); // Meassure ellapsed time // animationState.ellapsedTime += animation.timerResolution; animationState.ellapsedTime = (new Date()).getTime() - animationState.startTime; // Stop animation, if required if (animationState.ellapsedTime > duration) { // Animation reached its end of time window.clearInterval(animationState.domTimer); // Remove animation for (var idx = 0; idx < domElement.__runningAnimations[name].length; idx ++) { if (domElement.__runningAnimations[name][idx] == animationState) { domElement.__runningAnimations[name].splice(idx, 1); break; } } // Stop animation animation.stopAnimation( animationState, false, false); if (finishCallback != null) finishCallback(animationState); } } return animationState; } /* * ak_removeAnimation(name, animationState) * * Cancels the given animation. * */ function ak_removeAnimation(name, animationState) { var running = animationState.domElement.__runningAnimations[name]; for (var idx = 0; idx < running.length; idx ++) { if (running[idx] == animationState) { running.splice(idx, 1); break; } } } /* * ak_linearProgress(animationState) * * Linear progress. * */ function ak_linearProgress(animationState) { return animationState.ellapsedTime / animationState.duration; } /* * ak_outQuadraticProgress(animationState) * * Quadratic progress (curve outward). * */ function ak_outQuadraticProgress(animationState) { var progress = animationState.ellapsedTime / animationState.duration; return progress * progress; } /* * ak_inQuadraticProgress(animationState) * * Quadratic progress (curve inward). * */ function ak_inQuadraticProgress(animationState) { var progress = animationState.ellapsedTime / animationState.duration; return -1 * progress * (progress - 2); } /* * ak_switchingQuadraticProgress(animationState) * * 0 < x < 0.5 Quadratic progress, curve outward * 0.5 < x < 1 Quadratic progress, curve inward * */ function ak_switchingQuadraticProgress(animationState) { var progress = animationState.ellapsedTime / animationState.duration; if (progress < 0.5) return 2 * progress * progress; else return (-2 * progress * (progress - 2)) - 1; } /* * ak_inCubicProgress(animationState) * * Cubic progress, curve inward * */ function ak_inCubicProgress(animationState) { var progress = animationState.ellapsedTime / animationState.duration; return progress * progress * progress; } /* * ak_outCubicProgress(animationState) * * Cubic progress, curve outward * */ function ak_outCubicProgress(animationState) { var progress = (animationState.ellapsedTime / animationState.duration) -1; return progress * progress * progress + 1; } /* * ak_switchingCubicProgress(animationState) * * 0 < x < 0.5 Cubic progress, curve outward * 0.5 < x < 1 Cubic progress, curve inward * */ function ak_switchingCubicProgress(animationState) { var progress_a = (animationState.ellapsedTime / animationState.duration); var progress_b = ((animationState.ellapsedTime / animationState.duration)- 1); if (progress_a < 0.5) return 4 * progress_a * progress_a * progress_a; else return 4 * progress_b * progress_b * progress_b + 1; } /* * ak_inQuarticProgress(animationState) * * Quartic progress, curve inward * */ function ak_inQuarticProgress(animationState) { var progress = animationState.ellapsedTime / animationState.duration; return progress * progress * progress * progress; } /* * ak_outQuarticProgress(animationState) * * Quartic progress, curve outward * */ function ak_outQuarticProgress(animationState) { var progress = (animationState.ellapsedTime / animationState.duration); return - 1 * (progress * progress * progress * progress - 1); } /* * ak_switchingQuarticProgress(animationState) * * 0 < x < 0.5 Quartic progress, curve outward * 0.5 < x < 1 Quartic progress, curve inward * */ function ak_switchingQuarticProgress(animationState) { var progress_a = (animationState.ellapsedTime / animationState.duration); var progress_b = ((animationState.ellapsedTime / animationState.duration)- 1); if (progress_a < 1) return 8 * progress_a * progress_a * progress_a; else return -8 * progress_b * progress_b * progress_b + 1; } /* * ak_inSineProgress(animationState) * * Sinus progress, curve inward * */ function ak_inSineProgress(animationState) { var progress = animationState.ellapsedTime / animationState.duration; return -1 * (Math.cos(progress * (Math.PI / 2))) + 1; } /* * ak_outSineProgress(animationState) * * Sinus progress, curve outward * */ function ak_outSineProgress(animationState) { var progress = (animationState.ellapsedTime / animationState.duration); return Math.sin(progress * (Math.PI / 2)); } /* * ak_switchingSineProgress(animationState) * * Switching inward / outward sinus curve. * */ function ak_switchingSineProgress(animationState) { var progress = (animationState.ellapsedTime / animationState.duration); return -0.5 * Math.cos(progress * Math.PI) + 0.5; } /* * ak_inExponentialProgress(animationState) * * Exponential progress, curve inward * */ function ak_inExponentialProgress(animationState) { var progress = animationState.ellapsedTime / animationState.duration; return Math.pow(2, 10 * (progress - 1)); } /* * ak_outExponentialProgress(animationState) * * Sinus exponential, curve outward * */ function ak_outExponentialProgress(animationState) { var progress = (animationState.ellapsedTime / animationState.duration); return (-1 / Math.pow(2, 10 * progress)) + 1; } /* * ak_switchingExponentialProgress(animationState) * * Switching inward / outward exponential curve. * */ function ak_switchingExponentialProgress(animationState) { var progress = (animationState.ellapsedTime / animationState.duration); if (progress < 0.5) return 0.5 * Math.pow(2, 10 * (2* progress - 1)); else return 0.5 * (-1 / Math.pow(2, 10 * (2 * progress - 1))) + 1; } /* * ak_inCircularProgress(animationState) * * Circular progress, curve inward * */ function ak_inCircularProgress(animationState) { var progress = animationState.ellapsedTime / animationState.duration; return -1 * Math.sqrt(1 - progress*progress) + 1; } /* * ak_outCircularProgress(animationState) * * Sinus progress, curve outward * */ function ak_outCircularProgress(animationState) { var progress = (animationState.ellapsedTime / animationState.duration); return Math.sqrt(1 - progress*progress); } /* * ak_outPeakEnd(animationState) * * 0 over the entire time line, * 1 at the end * */ function ak_outPeakEnd(animationState) { return (animationState.ellapsedTime == animationState.duration) ? 1 : 0; } /* * ak_outPeakStart(animationState) * * 0 over the entire time line, * 1 at the start * */ function ak_outPeakStart(animationState) { return (animationState.ellapsedTime == 0) ? 1 : 0; }