Files
cappuccino/AppKit/CoreAnimation/CSSAnimation.j
T
2025-09-03 13:30:47 +03:00

314 lines
9.2 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;
};
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 "<animation " + this.identifier + " target=" + this.targetName + " properties=" + this.propertyanimations.map(function(anim){return anim.property;}).join(",") + " >";
}
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;
};
CSSAnimation.prototype.setFillMode = function(aProperty)
{
this.target.style.setProperty(ANIMATION_FILL_MODE_PROPERTY, aProperty);
};