Skip to content

Instantly share code, notes, and snippets.

@halfmanhalfdonut
Created January 18, 2012 16:09
Show Gist options
  • Save halfmanhalfdonut/1633771 to your computer and use it in GitHub Desktop.
Save halfmanhalfdonut/1633771 to your computer and use it in GitHub Desktop.
Keanu in CS
INTERVAL_TIME = 10 # 10ms between each interval
# whoa
# Keanu constructor (new Keanu())
# @id - String - DOM element ID for desired canvas object
# returns Keanu object
class window.Keanu
constructor: (id) ->
return if not id
@canvas = document.getElementById id
@ctx = @canvas.getContext '2d'
@isAnimating = false # for requestAnimationFrame
@interval = null # backup for non-RAF browsers
@lastLoop = 0
@checkTimer = null
@triggering = false
@subscribers = {}
@checkedDimensions = false
@clearX = 0
@clearY = 0
@clearW = @canvas.width
@clearH = @canvas.height
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame
this
# Keanu.subscribe()
# @type - String - event type
# @listener - Function - callback fired when this event is triggered
# @zIndex - Number - z-index of anything animated within the provided callback
subscribe: (type, listener, zIndex = 0) ->
@subscribers[type] = [] if not @subscribers[type]
@subscribers[type][zIndex] = [] if not @subscribers[type][zIndex]
@subscribers[type][zIndex].push listener
@start() if not @interval and not @isAnimating and type is "enterFrame"
return
# Keanu.unsubscribe()
# @type - String - event type
# @listener - Function - callback
# @zIndex - Number - z-index on the animation stack
unsubscribe: (type, listener, zIndex) ->
cutIt = =>
@stop() if (@subscribers.enterFrame and @subscribers.enterFrame.length is 0) or (@subscribers.enterFrame and @subscribers.enterFrame.length is 1 and @subscribers.enterFrame[0].length is 0)
return
if zIndex # if they send a zIndex, use it
if @subscribers[type] instanceof Array
if @subscribers[type][zIndex] instanceof Array
for sub, i in @subscribers[type][zIndex]
if sub is listener
@subscribers[type][zIndex].splice i, 1
break
else # otherwise check all subscribers
if @subscribers[type] instanceof Array
for sub, i in @subscribers[type]
if sub instanceof Array
for s, j in sub
if s is listener
sub.splice j, 1
break
else
if subscribers is listener
@subscribers[type].splice i, 1
break
clearTimeout @checkTimer
@checkTimer = setTimeout cutIt, 100
return
# Keanu.trigger()
# @event - String - event type
# Fires a given event if a given event exists
trigger: (event) ->
@triggering = true
event = type: event if typeof event is "string"
event.target = this if not event.target
return if not event.type
if @subscribers[event.type] instanceof Array
for sub in @subscribers[event.type]
if sub instanceof Array
s.call this, event for s in sub when s
@triggering = false
return
# Keanu.start()
# Starts the animation loop
start: ->
looper = =>
if @isAnimating or @interval # this could be using RAF or a timer
@lastLoop = +new Date if @lastLoop is 0
@clear()
# Get rid of the elements if they've been sitting in wait for too long
if (+new Date) - @lastLoop > 30000
@reset()
else
@lastLoop = +new Date
@trigger "enterFrame"
window.requestAnimationFrame looper, @canvas if window.requestAnimationFrame
return
if window.requestAnimationFrame and not @isAnimating
@isAnimating = true
window.requestAnimationFrame looper, @canvas
@trigger "start"
else if not window.requestAnimationFrame and not @interval
@interval = setInterval looper, INTERVAL_TIME
@trigger "start"
return
# Keanu.stop()
# Stops the animation loop
stop: ->
@isAnimating = false
clearInterval(@interval)
@interval = null
@clear()
@trigger "stop"
return
# Keanu.reset()
# Reset the Keanu object
reset: ->
doIt = =>
@subscribers = []
clearInterval @checkTimer
@checkTimer = null
@stop()
@lastLoop = 0
@trigger "reset"
if @triggering
setTimeout @reset, 5
else
doIt()
return
# Keanu.clear()
# Clear only the region that is being used, or all of it if nothing is specified
clear: ->
x = 0
y = 0
w = @canvas.width
h = @canvas.height
@ctx.clearRect x, y, w, h
@checkedDimensions = false
@clearX = 0
@clearY = 0
@clearW = 0
@clearH = 0
return
# Keanu.setDimension()
# @dim - Number - dimension to set
# @val - Number - value to check against the dimension
# @fn - Function (Math.min or Math.max) - determine the appropriate value for dim
setDimension: (dim, val, fn) ->
if not isNaN(dim) then fn and fn(dim, val) else val
# Keanu.setDimensions()
# @dims - Object ({x: 0, y: 0, w: 0, h: 0}) - Dimensions to set
# Determines the minimum X and Y, maximum Width and Height points to clear on redraw
setDimensions: (dims) ->
if @checkedDimensions
@clearX = @setDimension @clearX, dims.x, Math.min
@clearY = @setDimension @clearY, dims.y, Math.min
@clearW = @setDimension @clearW, dims.w, Math.max
@clearH = @setDimension @clearH, dims.h, Math.max
else
@clearX = dims.x
@clearY = dims.y
@clearW = dims.w
@clearH = dims.h
@checkedDimensions = true
return
# Keanu.isEmpty()
# o - Object
# Checks a given object for properties
# returns true or false
isEmpty: (o) ->
for own p in o
if o[p] instanceof Array and o[p].length > 0
for i in o[p]
if o[p][i] instanceof Array and o[p][i].length > 0
return false
true
# Keanu.getIntervalTime()
# returns the time interval of the animation loop
getIntervalTime: ->
INTERVAL_TIME
# Beware! Math be livin' below here, me hearties.
# Keanu.tweens[linear|easeIn|easeOut|easeInOut|quadraticBezierCurve]();
# returns a value based upon the Time, Beginning value, Change between start and end, and animation Duration
tweens:
linear: (t, b, c, d) -> c * t / d + b
easeIn: (t, b, c, d) -> c * (t /= d) * t + b
easeOut: (t, b, c, d) -> -c * (t /= d) * (t - 2) + b
easeInOut: (t, b, c, d) ->
if t /= d / 2 < 1
c / 2 * t * t * b
else
-c / 2 * ((--t) * (t - 2) - 1) + b
quadraticBezierCurve: (t, p0, p1, p2) ->
~~(Math.pow((1 - t), 2) * p0 + 2 * (1 - t) * t * p1 + Math.pow(t, 2) * p2)
# Optional modules namespace for external modules
window.Keanu.modules = {}
(function() {
var INTERVAL_TIME;
INTERVAL_TIME = 10;
window.Keanu = (function() {
function Keanu(id) {
if (!id) return;
this.canvas = document.getElementById(id);
this.ctx = this.canvas.getContext('2d');
this.isAnimating = false;
this.interval = null;
this.lastLoop = 0;
this.checkTimer = null;
this.triggering = false;
this.subscribers = {};
this.checkedDimensions = false;
this.clearX = 0;
this.clearY = 0;
this.clearW = this.canvas.width;
this.clearH = this.canvas.height;
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
this;
}
Keanu.prototype.subscribe = function(type, listener, zIndex) {
if (zIndex == null) zIndex = 0;
if (!this.subscribers[type]) this.subscribers[type] = [];
if (!this.subscribers[type][zIndex]) this.subscribers[type][zIndex] = [];
this.subscribers[type][zIndex].push(listener);
if (!this.interval && !this.isAnimating && type === "enterFrame") {
this.start();
}
};
Keanu.prototype.unsubscribe = function(type, listener, zIndex) {
var cutIt, i, j, s, sub, _len, _len2, _len3, _ref, _ref2,
_this = this;
cutIt = function() {
if ((_this.subscribers.enterFrame && _this.subscribers.enterFrame.length === 0) || (_this.subscribers.enterFrame && _this.subscribers.enterFrame.length === 1 && _this.subscribers.enterFrame[0].length === 0)) {
_this.stop();
}
};
if (zIndex) {
if (this.subscribers[type] instanceof Array) {
if (this.subscribers[type][zIndex] instanceof Array) {
_ref = this.subscribers[type][zIndex];
for (i = 0, _len = _ref.length; i < _len; i++) {
sub = _ref[i];
if (sub === listener) {
this.subscribers[type][zIndex].splice(i, 1);
break;
}
}
}
}
} else {
if (this.subscribers[type] instanceof Array) {
_ref2 = this.subscribers[type];
for (i = 0, _len2 = _ref2.length; i < _len2; i++) {
sub = _ref2[i];
if (sub instanceof Array) {
for (j = 0, _len3 = sub.length; j < _len3; j++) {
s = sub[j];
if (s === listener) {
sub.splice(j, 1);
break;
}
}
} else {
if (subscribers === listener) {
this.subscribers[type].splice(i, 1);
break;
}
}
}
}
}
clearTimeout(this.checkTimer);
this.checkTimer = setTimeout(cutIt, 100);
};
Keanu.prototype.trigger = function(event) {
var s, sub, _i, _j, _len, _len2, _ref;
this.triggering = true;
if (typeof event === "string") {
event = {
type: event
};
}
if (!event.target) event.target = this;
if (!event.type) return;
if (this.subscribers[event.type] instanceof Array) {
_ref = this.subscribers[event.type];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
sub = _ref[_i];
if (sub instanceof Array) {
for (_j = 0, _len2 = sub.length; _j < _len2; _j++) {
s = sub[_j];
if (s) s.call(this, event);
}
}
}
}
this.triggering = false;
};
Keanu.prototype.start = function() {
var looper,
_this = this;
looper = function() {
if (_this.isAnimating || _this.interval) {
if (_this.lastLoop === 0) _this.lastLoop = +(new Date);
_this.clear();
if ((+(new Date)) - _this.lastLoop > 30000) {
_this.reset();
} else {
_this.lastLoop = +(new Date);
_this.trigger("enterFrame");
}
if (window.requestAnimationFrame) {
window.requestAnimationFrame(looper, _this.canvas);
}
}
};
if (window.requestAnimationFrame && !this.isAnimating) {
this.isAnimating = true;
window.requestAnimationFrame(looper, this.canvas);
this.trigger("start");
} else if (!window.requestAnimationFrame && !this.interval) {
this.interval = setInterval(looper, INTERVAL_TIME);
this.trigger("start");
}
};
Keanu.prototype.stop = function() {
this.isAnimating = false;
clearInterval(this.interval);
this.interval = null;
this.clear();
this.trigger("stop");
};
Keanu.prototype.reset = function() {
var doIt,
_this = this;
doIt = function() {
_this.subscribers = [];
clearInterval(_this.checkTimer);
_this.checkTimer = null;
_this.stop();
_this.lastLoop = 0;
return _this.trigger("reset");
};
if (this.triggering) {
setTimeout(this.reset, 5);
} else {
doIt();
}
};
Keanu.prototype.clear = function() {
var h, w, x, y;
x = 0;
y = 0;
w = this.canvas.width;
h = this.canvas.height;
this.ctx.clearRect(x, y, w, h);
this.checkedDimensions = false;
this.clearX = 0;
this.clearY = 0;
this.clearW = 0;
this.clearH = 0;
};
Keanu.prototype.setDimension = function(dim, val, fn) {
if (!isNaN(dim)) {
return fn && fn(dim, val);
} else {
return val;
}
};
Keanu.prototype.setDimensions = function(dims) {
if (this.checkedDimensions) {
this.clearX = this.setDimension(this.clearX, dims.x, Math.min);
this.clearY = this.setDimension(this.clearY, dims.y, Math.min);
this.clearW = this.setDimension(this.clearW, dims.w, Math.max);
this.clearH = this.setDimension(this.clearH, dims.h, Math.max);
} else {
this.clearX = dims.x;
this.clearY = dims.y;
this.clearW = dims.w;
this.clearH = dims.h;
this.checkedDimensions = true;
}
};
Keanu.prototype.isEmpty = function(o) {
var i, p, _i, _j, _len, _len2, _ref;
for (_i = 0, _len = o.length; _i < _len; _i++) {
p = o[_i];
if (o[p] instanceof Array && o[p].length > 0) {
_ref = o[p];
for (_j = 0, _len2 = _ref.length; _j < _len2; _j++) {
i = _ref[_j];
if (o[p][i] instanceof Array && o[p][i].length > 0) return false;
}
}
}
return true;
};
Keanu.prototype.getIntervalTime = function() {
return INTERVAL_TIME;
};
Keanu.prototype.tweens = {
linear: function(t, b, c, d) {
return c * t / d + b;
},
easeIn: function(t, b, c, d) {
return c * (t /= d) * t + b;
},
easeOut: function(t, b, c, d) {
return -c * (t /= d) * (t - 2) + b;
},
easeInOut: function(t, b, c, d) {
if (t /= d / 2 < 1) {
return c / 2 * t * t * b;
} else {
return -c / 2 * ((--t) * (t - 2) - 1) + b;
}
},
quadraticBezierCurve: function(t, p0, p1, p2) {
return ~~(Math.pow(1 - t, 2) * p0 + 2 * (1 - t) * t * p1 + Math.pow(t, 2) * p2);
}
};
return Keanu;
})();
window.Keanu.modules = {};
}).call(this);
var Keanu; // Whoa
(function() {
var INTERVAL_TIME = 10; // 10ms between each interval
/* Keanu constructor (new Keanu())
* @id - String - DOM element ID for desired canvas object
* returns Keanu object
*/
Keanu = function(id) {
if (!id) return false;
this.canvas = document.getElementById(id);
this.ctx = this.canvas.getContext('2d');
this.isAnimating = false; // for requestAnimationFrame
this.interval = null; // backup for browsers without RAF
this.lastLoop = 0;
this.checkTimer = null;
this.triggering = false;
this.subscribers = {};
this.checkedDimensions = false;
this.clearX = 0;
this.clearY = 0;
this.clearW = this.canvas.width;
this.clearH = this.canvas.height;
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
return this;
};
// Optional modules namespace for external modules
Keanu.modules = {};
// Keanu prototype
Keanu.prototype = {
constructor: Keanu,
/* Keanu.subscribe()
* @type - String - event type
* @listener - Function - callback fired when this event is triggered
* @zIndex - Number - z-index of anything animated within the provided callback
*/
subscribe: function(type, listener, zIndex) {
zIndex = zIndex || 0;
if (typeof this.subscribers[type] == "undefined") {
this.subscribers[type] = [];
}
if (typeof this.subscribers[type][zIndex] == "undefined") {
this.subscribers[type][zIndex] = [];
}
this.subscribers[type][zIndex].push(listener);
if (!this.interval && !this.isAnimating && type == "enterFrame") this.start();
},
/* Keanu.unsubscribe()
* @type - String - event type
* @listener - Function - callback
* @zIndex - Number - z-index on the animation stack
*/
unsubscribe: function(type, listener, zIndex) {
var self = this;
// use zIndex if they send it in
if (zIndex != undefined) {
if (this.subscribers[type] instanceof Array) {
if (this.subscribers[type][zIndex] instanceof Array) {
var subscribers = this.subscribers[type][zIndex];
for (var i = 0, len = subscribers.length; i < len; i += 1) {
if (subscribers[i] === listener) {
subscribers.splice(i, 1);
break;
}
}
}
}
// otherwise check all subscribers
} else {
if (this.subscribers[type] instanceof Array) {
var subscribers = this.subscribers[type];
for (var i = 0, len = subscribers.length; i < len; i += 1) {
if (subscribers[i] instanceof Array) {
var sub = subscribers[i];
for (var j = 0, gth = sub.length; j < gth; j += 1) {
if (sub[j] === listener) {
sub.splice(j, 1);
break;
}
}
} else {
if (subscribers[i] === listener) {
subscribers.splice(i, 1);
break;
}
}
}
}
}
clearTimeout(this.checkTimer);
this.checkTimer = setTimeout(function() {
if ((self.subscribers.enterFrame && self.subscribers.enterFrame.length == 0) || (self.subscribers.enterFrame && self.subscribers.enterFrame.length == 1 && self.subscribers.enterFrame[0].length == 0)) {
self.stop();
}
}, 100);
},
/* Keanu.trigger()
* @event - String - event type
* Fires a given event if a given event exists
*/
trigger: function(event) {
this.triggering = true;
if (typeof event == "string") {
event = { type: event };
}
if (!event.target) {
event.target = this;
}
if (!event.type) {
return;
}
if (this.subscribers[event.type] instanceof Array) {
var subscribers = this.subscribers[event.type];
for (var len = subscribers.length, i = len - 1; i >= 0; i -= 1) {
if (subscribers[i] instanceof Array) {
var sub = subscribers[i];
for (var j = 0, gth = sub.length; j < gth; j += 1) {
if (sub[j]) sub[j].call(this, event);
}
}
}
}
this.triggering = false;
},
/* Keanu.start()
* Starts the animation loop
*/
start: function() {
var self = this,
loop = function() {
if (self.isAnimating || self.interval) { // it could be using RAF or SI
if (self.lastLoop === 0) {
self.lastLoop = +new Date;
}
self.clear();
// Get rid of the elements if they've been sitting in wait for too long
if ((+new Date) - self.lastLoop > 30000) {
self.reset();
} else {
self.lastLoop = +new Date;
self.trigger("enterFrame");
}
if (window.requestAnimationFrame) {
window.requestAnimationFrame(loop, self.canvas);
}
}
};
if (window.requestAnimationFrame && !this.isAnimating) {
this.isAnimating = true;
window.requestAnimationFrame(loop, this.canvas);
self.trigger("start");
} else if (!window.requestAnimationFrame && !this.interval) {
this.interval = setInterval(loop, INTERVAL_TIME);
self.trigger("start");
}
},
/* Keanu.stop()
* Stops the animation loop
*/
stop: function() {
this.isAnimating = false;
clearInterval(this.interval);
this.interval = null;
this.clear();
this.trigger("stop");
},
/* Keanu.reset()
* Reset the Keanu object
*/
reset: function() {
var self = this,
doIt = function() {
self.subscribers = {};
clearInterval(self.checkTimer);
self.checkTimer = null;
self.stop();
self.lastLoop = 0;
self.trigger("reset");
};
if (this.triggering) {
setTimeout(this.reset, 5);
} else {
doIt();
}
},
/* Keanu.clear()
* Clear only the region that is being used, or all of it if nothing is specified
*/
clear: function() {
var x = 0,
y = 0,
w = this.canvas.width,
h = this.canvas.height;
this.ctx.clearRect(x, y, w, h);
this.checkedDimensions = false;
this.clearX = 0;
this.clearY = 0;
this.clearW = 0;
this.clearH = 0;
//this.trigger("clear");
},
/* Keanu.setDimension()
* @dim - Number - dimension to set
* @val - Number - value to check against the dimension
* @fn - Function (Math.min or Math.max) - determine the appropriate value for dim
*/
setDimension: function(dim, val, fn) {
dim = !isNaN(dim)
? fn && fn(dim, val)
: val;
return dim;
},
/* Keanu.setDimensions()
* @dims - Object ({x: 0, y: 0, w: 0, h: 0}) - Dimensions to set
* Determines the minimum X and Y, maximum Width and Height points to clear on redraw
*/
setDimensions: function(dims) {
if (this.checkedDimensions) {
this.clearX = this.setDimension(this.clearX, dims.x, Math.min);
this.clearY = this.setDimension(this.clearY, dims.y, Math.min);
this.clearW = this.setDimension(this.clearW, dims.w, Math.max);
this.clearH = this.setDimension(this.clearH, dims.h, Math.max);
} else {
this.clearX = dims.x;
this.clearY = dims.y;
this.clearW = dims.w;
this.clearH = dims.h;
this.checkedDimensions = true;
}
},
/* Keanu.isEmpty()
* o - Object
* Checks a given object for properties
* returns true or false
*/
isEmpty: function(o) {
var empty = true,
i, l, p;
for (p in o) {
if (o.hasOwnProperty(p)) {
if (o[p] instanceof Array && o[p].length > 0) {
for (i = 0, l = o[p].length; i < l; i += 1) {
if (o[p][i] instanceof Array && o[p][i].length > 0) {
empty = false;
break;
}
}
}
}
}
return empty;
},
/* Keanu.getIntervalTime()
* returns the time interval of the animation loop
*/
getIntervalTime: function() { return INTERVAL_TIME; },
/*
* Beware! Math be livin' below here, me hearties.
*/
tweens: {
/* Keanu.tweens[linear|easeIn|easeOut|easeInOut|quadraticBezierCurve]();
* returns a value based upon the Time, Beginning value, Change between start and end, and animation Duration
*/
linear: function(t, b, c, d) { return c * t / d + b; },
easeIn: function(t, b, c, d) { return c * (t /= d) * t + b; },
easeOut: function(t, b, c, d) { return -c * (t /= d) * (t - 2) + b; },
easeInOut: function(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t + b;
return -c / 2 * ((--t) * (t - 2) - 1) + b;
},
quadraticBezierCurve: function(t, p0, p1, p2) {
return ~~(Math.pow((1 - t), 2) * p0 + 2 * (1 - t) * t * p1 + Math.pow(t, 2) * p2);
}
}
};
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment