Files
cappuccino/AppKit/CoreAnimation/CSSAnimation.j
T
cacaodev e5d883f97e CAKeyframeAnimation
CPAnimatablePropertyContainer protocol (CPView)
CPAnimationContext
_CPObjectAnimator CPViewAnimator
CSS Animations wrapper

CPAnimatablePropertyContainerTest : animations of views with custom Layout / Regular Layout / Custom DrawRect: / No DrawRect: / Subviews / No Subviews and combinations of these situations.
controls animation (text field, button, popup, slider ...). They animate their size via their action.
2013-04-13 16:29:43 +02:00

286 lines
8.9 KiB
Plaintext

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;
}
CSSAnimation = function(aTarget/*DOM Element*/, anIdentifier)
{
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.animationName = animationName;
this.listener = null;
this.styleElement = null;
this.propertyanimations = [];
this.animationsnames = [];
this.animationstimingfunctions = [];
this.animationsdurations = [];
this.islive = false;
this.didBuildDOMElements = false;
this.removeAnimationPropertyOnCompletion = true;
animation = this;
CURRENT_ANIMATIONS[anIdentifier] = animation;
}
return animation;
}
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 name = this.animationName + "_" + propertyName;
var animation = {name:name,
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(name);
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;
for (var j = 0; j < keytimescount; j++)
{
var keytime = keytimes[j],
value = values[j];
var cssvalue = valuefunction ? valuefunction(value) : value;
var keyframeContent = property + ": " + cssvalue + ";";
var timingFunction;
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)
{
for (var i = 0; i < animationsNames.length; i++)
{
var completion = animation.completionFunctionForAnimationName(animationsNames[i]);
if (completion)
completion();
}
var eventTarget = event.target,
style = eventTarget.style;
if (animation.removeAnimationPropertyOnCompletion)
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]);
}
};
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.setRemoveAnimationPropertyOnCompletion = function(flag)
{
this.removeAnimationPropertyOnCompletion = flag;
}
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;
}
var removeFromParent= function(aNode)
{
var parentNode = aNode.parentNode;
if (parentNode)
parentNode.removeChild(aNode);
}