bungee
Version:
Bungee is a declarative language engine to run inside a browser. The node module contains the offline compiler.
302 lines (246 loc) • 9.49 kB
JavaScript
/*
**************************************************
* Bungee.js
*
* (c) 2012-2013 Johannes Zellner
*
* Bungee may be freely distributed under the MIT license.
* For all details and documentation:
* http://bungeejs.org
**************************************************
*/
;
/*
**************************************************
* Animation
**************************************************
*/
var Bungee = require('./engine.js');
Bungee._animationIndex = 0;
Bungee._debugAnimation = false;
/*
**************************************************
* Basic Animation
**************************************************
*/
Bungee.Step = function (engine, id, parent) {
var elem = new Bungee.Element(engine, id, parent, "object");
elem.addProperty("percentage", 0);
return elem;
};
Bungee.Animation = function (engine, id, parent) {
var elem = new Bungee.Element(engine, id, parent, "object");
var index = Bungee._animationIndex++;
var dirty = true;
var hasRules = false;
var animationName = "bungeeAnimation" + index;
var keyFramesName = "bungeeAnimationKeyFrames" + index;
elem.addProperty("target", function () { return this.parent; });
elem.addProperty("duration", 250);
elem.addProperty("delay", 0);
elem.addProperty("loops", 1);
elem.addProperty("reverse", false);
elem.addProperty("easing", "ease");
function animationStart(event) {
Bungee._debugAnimation && console.log("start", event);
elem.emit("started");
}
function animationIteration(event) {
Bungee._debugAnimation && console.log("iteration", event);
}
function animationEnd(event) {
Bungee._debugAnimation && console.log("end", event);
elem.stop();
elem.emit("finished");
}
function updateRules() {
var rule1 = "";
var rule2 = "";
var rule3 = "";
var rule4 = "";
if (!Bungee._style) {
Bungee._style = document.createElement('style');
document.getElementsByTagName('head')[0].appendChild(Bungee._style);
}
if (hasRules) {
var tmpName = "." + animationName;
var i;
// remove key frames rule
for (i = 0; i < Bungee._style.sheet.cssRules.length; ++i) {
if (Bungee._style.sheet.cssRules[i].name === keyFramesName) {
Bungee._style.sheet.deleteRule(i);
break;
}
}
// remove animation rule
for (i = 0; i < Bungee._style.sheet.cssRules.length; ++i) {
if (Bungee._style.sheet.cssRules[i].selectorText === tmpName) {
Bungee._style.sheet.deleteRule(i);
break;
}
}
}
rule1 += "." + animationName + " {\n";
rule1 += " animation: ";
rule1 += keyFramesName + " ";
rule1 += elem.duration + "ms ";
rule1 += elem.easing + " ";
rule1 += elem.delay + " ";
rule1 += elem.loops + " ";
rule1 += (elem.reverse ? "alternate" : "normal") + ";\n";
rule1 += " -webkit-animation: ";
rule1 += keyFramesName + " ";
rule1 += elem.duration + "ms ";
rule1 += elem.easing + " ";
rule1 += elem.delay + " ";
rule1 += elem.loops + " ";
rule1 += (elem.reverse ? "alternate" : "normal") + ";\n";
rule1 += "}\n";
rule2 += "@keyframes " + keyFramesName + " { \n";
rule3 += "@-webkit-keyframes " + keyFramesName + " { \n";
for (var j in elem.children()) {
var child = elem.children()[j];
if (typeof child.percentage === 'undefined') {
continue;
}
rule2 += " " + child.percentage + "% {\n";
rule3 += " " + child.percentage + "% {\n";
for (var property in child._properties) {
if (child.hasOwnProperty(property) && property !== 'percentage') {
rule2 += " " + property + ": " + child[property] + ";\n";
rule3 += " " + property + ": " + child[property] + ";\n";
}
}
rule2 += " }\n";
rule3 += " }\n";
}
rule2 += "}\n";
rule3 += "}\n";
Bungee._debugAnimation && console.log("Bungee Animation rules:\n", rule2, rule3, rule1);
try {
Bungee._style.sheet.insertRule(rule3, Bungee._style.sheet.rules.length);
} catch (e) {
Bungee._debugAnimation && console.error("Bungee Animation rule", rule3, "could not be inserted.", e);
}
try {
Bungee._style.sheet.insertRule(rule2, Bungee._style.sheet.rules.length);
} catch (e) {
Bungee._debugAnimation && console.error("Bungee Animation rule", rule2, "could not be inserted.", e);
}
try {
Bungee._style.sheet.insertRule(rule1, Bungee._style.sheet.rules.length);
} catch (e) {
Bungee._debugAnimation && console.error("Bungee Animation rule", rule1, "could not be inserted.", e);
}
hasRules = true;
}
function addEventListeners() {
elem._element = elem.target;
if (elem._element && elem._element.element) {
elem._element.element.addEventListener("webkitAnimationStart", animationStart, false);
elem._element.element.addEventListener("webkitAnimationIteration", animationIteration, false);
elem._element.element.addEventListener("webkitAnimationEnd", animationEnd, false);
}
}
function markDirty() {
dirty = true;
}
elem.addChanged("target", addEventListeners);
elem.addChanged("changed", markDirty);
elem.start = function () {
if (dirty) {
updateRules();
dirty = false;
}
if (elem._element)
elem._element.className = animationName;
};
elem.stop = function () {
elem._element.className = "";
};
elem.restart = function () {
elem.stop();
elem.start();
};
return elem;
};
Bungee.Behavior = function (engine, id, parent) {
var elem = new Bungee.Element(engine, id, parent, "object");
var index = Bungee._animationIndex++;
var hasRules = false;
var animationName = "bungeeAnimation" + index;
elem.addProperty("target", function () { return this.parent; });
function animationStart(event) {
Bungee._debugAnimation && console.log("start", event);
elem.emit("started");
}
function animationIteration(event) {
Bungee._debugAnimation && console.log("iteration", event);
}
function animationEnd(event) {
Bungee._debugAnimation && console.log("end", event);
elem.stop();
elem.emit("finished");
}
function updateRules() {
var rule = "";
var rulepart = "";
var gotProperties = false;
if (!Bungee._style) {
Bungee._style = document.createElement('style');
document.getElementsByTagName('head')[0].appendChild(Bungee._style);
}
if (hasRules) {
var tmpName = "." + animationName;
var i;
// remove animation rule
for (i = 0; i < Bungee._style.sheet.cssRules.length; ++i) {
if (Bungee._style.sheet.cssRules[i].selectorText === tmpName) {
Bungee._style.sheet.deleteRule(i);
break;
}
}
}
// nothing to do when we got no properties
if (elem._properties.length === 0) {
return;
}
// create shared parts of the css
for (var property in elem._properties) {
if (elem.hasOwnProperty(property) && property !== 'target') {
if (gotProperties) {
rulepart += ", ";
} else {
gotProperties = true;
}
rulepart += property + " " + elem[property];
}
}
rule += "." + animationName + " {\n";
rule += " -webkit-transition: " + rulepart + ";\n";
rule += " transition: " + rulepart + ";\n";
rule += "}\n";
// only actually insert rules if there is no property undefined
if (gotProperties && rule.indexOf('undefined') === -1) {
Bungee._debugAnimation && console.log("Bungee Behavior rule", rule);
try {
Bungee._style.sheet.insertRule(rule, Bungee._style.sheet.rules.length);
} catch (e) {
Bungee._debugAnimation && console.error("Bungee Animation rule", rule, "could not be inserted.", e);
}
hasRules = true;
}
}
function addEventListeners() {
elem._element = elem.target;
if (elem._element && elem._element.element) {
elem._element.element.addEventListener("webkitAnimationStart", animationStart, false);
elem._element.element.addEventListener("webkitAnimationIteration", animationIteration, false);
elem._element.element.addEventListener("webkitAnimationEnd", animationEnd, false);
elem._element.className = animationName;
}
}
elem.addChanged("target", addEventListeners);
elem.addChanged("changed", updateRules);
return elem;
};