beast
Version:
provides a timer'd loop with hooks producing various animated effects
836 lines (708 loc) • 21.3 kB
JavaScript
// Filename: beast.full.js
// Timestamp: 2013/12/24 (last modified)
// Author(s): Bumblehead (www.bumblehead.com), Dan Pupius (www.pupius.co.uk)
// Filename: domlt.js
// Timestamp: 2013.12.24-17:13:49 (last modified)
// Author(s): Bumblehead (www.bumblehead.com)
//
// documentElement.getBoundingClientRect() is mature and widely supported:
// https://developer.mozilla.org/en-US/docs/Web/API/Element.getBoundingClientRect
var domlt = function (el) {
var rect = el.getBoundingClientRect();
return [rect.left, rect.top];
};
// Filename: incrnum.js
// Timestamp: 2013.10.22-11:32:49 (last modified)
// Author(s): Bumblehead (www.bumblehead.com)
var incrnum = ((typeof module === 'object') ? module : {}).exports = (function (uid, fn) {
fn = function () { return uid++; };
fn.toString = function () { return uid++; };
return fn;
}(0));
// Filename: eventhook.js
// Timestamp: 2013.10.30-10:51:34 (last modified)
// Author(s): Bumblehead (www.bumblehead.com)
// Requires: incrnum.js
var eventhook = ((typeof module === 'object') ? module : {}).exports = (function () {
var proto = {
fnArr: [],
addFn: function (fn) {
if (typeof fn === 'function') {
fn.oname = 'fn' + incrnum;
this.fnArr.push(fn);
}
},
rmFn: function (fn) {
var oname = fn.oname;
if (typeof fn === 'function') {
this.fnArr = this.fnArr.filter(function (fn) {
return fn.oname !== oname;
});
}
},
fire: function (a1,a2,a3,a4) {
this.fnArr.map(function (fn) {
fn(a1,a2,a3,a4);
});
}
};
return {
proto : proto,
getNew : function () {
var that = Object.create(proto);
that.fnArr = [];
return that;
}
};
}());
// Filename: beast.js
// Timestamp: 2013.12.24-16:37:55 (last modified)
// Author(s): Bumblehead (www.bumblehead.com)
// Requires: eventhook.js
var beast = ((typeof module === 'object') ? module : {}).exports = (function (p) {
var proto = {
frames : 30,
fpms : (30 / 1000),
mspf : 0.03,
timer : null,
frameindex : 0,
ngnArr : [],
isKilled : false,
isComplete : false,
// st: 1 continue, 2 sleep, 3 reset, 4 kill
st : 1,
onComplete : function (fn) {
this.onCompleteHook.addFn(fn);
return this;
},
onKill : function (fn) {
this.onKillHook.addFn(fn);
return this;
},
onBegin : function (fn) {
this.onBeginHook.addFn(fn);
return this;
},
updateNgnItems : function (frame) {
this.ngnArr.map(function (ngn) {
ngn.updatefn(frame);
});
},
initNgnItems : function (frames, opts) {
frames = this.frames;
this.ngnArr.map(function (ngn) {
ngn.init(frames, ngn.opts);
ngn.updatefn = ngn.getupdatefn(frames, ngn.opts);
});
},
killNgnItems : function (frames, opts) {
frames = this.frames;
this.ngnArr.map(function (ngn) {
ngn.kill(frames, ngn.opts);
});
},
completeNgns : function (frames, opts) {
frames = this.frames;
this.ngnArr.map(function (ngn) {
ngn.complete(frames, ngn.opts);
});
},
applyFnComplete : function () {
if (this.st === 1 || this.st === 4) {
this.onCompleteHook.fire();
}
},
applyFnKill : function () {
this.onKillHook.fire();
},
applyFnBegin : function () {
this.onBeginHook.fire();
},
updateNgns : function () {
var that = this;
that.updateNgnItems(that.frameindex);
++that.frameindex;
},
updateAnimationState : function () {
var that = this;
if (that.isKilled) {
that.st = 3;
} else if (that.frameindex >= that.frames) {
that.st = 4;
}
},
animate : function () {
var my = this;
(function nextFrame() {
my.updateAnimationState();
switch (my.st) {
case 1: // continue
my.updateNgns();
my.timer = setTimeout(nextFrame, my.mspf);
break;
case 2: // sleep
my.st = 1;
break;
case 3: // killed
my.st = 3;
my.kill();
break;
default: // completed
my.st = 4;
my.complete();
break;
}
}());
},
complete : function () {
var that = this;
if (!that.isKilled && !that.isComplete) {
that.isComplete = true;
// ... for external call to kill loop
that.st = 4;
that.completeNgns();
that.applyFnComplete();
}
},
kill : function () {
var that = this;
if (!that.isKilled && !that.isComplete) {
that.isKilled = true;
that.st = 3;
that.killNgnItems();
that.applyFnKill();
}
},
init : function (fn) {
var that = this;
if (typeof fn === 'function') {
that.onComplete(fn);
}
if (that.st === 4) {
that.st = 1;
that.applyFnBegin();
that.isKilled = false;
that.isComplete = false;
that.frameindex = 0;
that.initNgnItems();
that.animate();
}
},
reinit : function () {
var that = this;
that.kill();
that.st = 4;
that.init();
}
};
p = function (spec) {
var that;
that = Object.create(proto);
that.timer = null;
that.onCompleteHook = eventhook.getNew();
that.onKillHook = eventhook.getNew();
that.onBeginHook = eventhook.getNew();
that.ngnArr = [];
that.isKilled = false;
that.isComplete = false;
that.frameindex = 0;
that.st = 4;
that.frames = spec.frames || 30;
that.fpms = (spec.fps || 30) / 1000;
that.mspf = 1 / that.fpms;
return that;
};
p.proto = proto;
return p;
}());
// Filename: elemst.js
// Timestamp: 2013.12.16-00:25:06 (last modified)
// Author(s): Bumblehead (www.bumblehead.com)
//
// methods for handling "state" of an element through className:
//
// elemstate.up()
// elemstate.is()
// elemstate.rm()
var elemst = {
// elemstate.up(elem, 'select-active');
// - ensures `st-select-active` is defined on elem.className
// - if `st-select-active` is defined on elem.className, elem is not modified
// - if `st-select-\w*` is defined on elem.className, it is replaced.
//
// all states are prefixed with `st-`
// ex,
// elemstate.up(elem, 'select-active'); // st-select-active
// elemstate.up(elem, 'active'); // st-active
//
// why? easier to identify and replace classNames associated with this script
//
// if a hyphen is given the word behind the first hyphen
// is recognized as an *id*. word(s) after hyphen are the
// *state*. if no hyphen, value is assumed to be *state*.
//
// Use of *id* allows one to store multiple states on a className, associating
// each with a different *id*, for example:
// `st-isselected-true st-isopen-true st-iscomplete-false`
//
// *id* is not required -this OK:
// `st-active`
//
up : function (elem, stateidStr) {
var className,
newclass = 'st-' + stateidStr,
stateid = (stateidStr || '').match(/(\w*(?:-)|\b)(\w*)/) || [],
state = stateid[2],
id = stateid[1] || '';
if (elem && state) {
className = elem.className;
if (!className.match(newclass)) {
stateid = new RegExp('st-' + '\(' + id + '\\w*\)');
if (className.match(stateid)) {
elem.className = className.replace(stateid, newclass);
} else {
elem.className = className + ' ' + newclass;
}
}
}
},
// elemstate.rm(elem, 'select');
// - removes entire word class with substring at index 0 matching stateidstr
//
// console.log(elem.className); // bttn st-select-active st-active
// elemstate.rm(elem, 'select');
// console.log(elem.className); // bttn st-active
// elemstate.rm(elem, 'active');
// console.log(elem.className); // bttn
//
rm : function (elem, stateidStr) {
var className,
stateid = new RegExp('[\b ]st-' + stateidStr + '\(-\\w*\)?');
if (elem) {
className = elem.className;
if (className.match(stateid)) {
elem.className = className.replace(stateid, '');
}
}
},
is : function (elem, stateidStr) {
return (elem && elem.className.match('st-' + stateidStr)) ? true : false;
}
};
// Filename: beastplug.js
// Timestamp: 2013.12.26-21:24:57 (last modified)
// Author(s): Bumblehead (www.bumblehead.com)
// Requires: elemst.js, beast.js
var beastplug = ((typeof module === 'object') ? module : {}).exports = function (name, augmfn) {
// redefine these values with plugin
var pluginproto = {
className : ':name-beast-animating',
isclean : true,
// convenience methods for use by plugins
doframeloop : function (frames, fn) {
for (var i = 0, step = frames - 1; i < frames; i++) fn(i/step, i);
},
getComputed : function (elem, style) {
return getComputedStyle(elem, null).getPropertyValue(style);
},
isanimated : function (arr1, arr2, i) {
return arr1[i] !== arr2[i];
},
// methods called at begin of animation for setup
getdata : function (frames, opts) {},
getupdatefn : function (frames, opts) {
return function () {};
},
// methods called during the animation cycle
init : function (frames, opts) {
elemst.up(opts.elem, this.className);
opts.oparr = this.getdata(frames, opts);
},
clean : function (frames, opts) {},
kill : function (frames, opts) {
elemst.rm(opts.elem, this.className);
if (this.isclean) this.clean(frames, opts);
},
complete : function (frames, opts) {
if (opts.classNameEnd) {
elemst.up(opts.elem, opts.classNameEnd);
}
elemst.rm(opts.elem, this.className);
if (this.isclean) this.clean(frames, opts);
}
};
beast.proto[name] = function (opts) {
var that = Object.create(pluginproto);
augmfn(that);
that.opts = opts;
that.className = that.className.replace(/:name/, name);
if (typeof opts.isclean === 'boolean') {
that.isclean = opts.isclean;
}
this.ngnArr.push(that);
return this;
};
};
// Filename: curved.js
// Timestamp: 2013.12.26-21:18:26 (last modified)
// Author(s): Dan Pupius (www.pupius.co.uk), Bumblehead (www.bumblehead.com)
//
// thanks to Daniel Pupius
// http://13thparallel.com/archive/bezier-curves/
//
// Bernstein Basis Function
// 1 = t + (1 - t)
//
// Bernstein Basis Function, cubed
// 1^3 = (t + (1 - t))^3
//
// Above Function, represented in terms of 1.
// » 1 = (t + (1 - t)) . (t^2 + 2t(1 - t) + (1 - t)^2)
// » 1 = t^3 + 3t^2(1 - t) + 3t(1 - t)^2 + (1 - t)^3
//
// each function
// B[1](t) = t^3
// B[2](t) = 3t^2(1 - t)
// B[3](t) = 3t(1 - t)^2
// B[4](t) = (1 - t)^3
//
// Where C is the control, and '[ ]' indicates subscript
// point = C[1]B[1](d) + C[2]B[2](d) + C[3]B[3](d) + C[4]B[4](d)
//
// change to the scripting at the link above:
// - given values are 'shifted' into a positive axis so that curves may be
// generated when negative values are given.
var curved = ((typeof module === 'object') ? module : {}).exports = (function () {
function B1(t) { return t * t * t; }
function B2(t) { return 3 * t * t * (1 - t); }
function B3(t) { return 3 * t * (1 - t) * (1 - t); }
function B4(t) { return (1 - t) * (1 - t) * (1 - t); }
function getShift(x1, x2) {
var min = Math.min(x1, x2);
return min && -min;
}
// easeStr should be a string 'ease-end' or 'ease-bgn'
return function (bgnCoord, endCoord, easeStr) {
var shiftval = getShift(bgnCoord, endCoord),
C1 = endCoord + shiftval,
C4 = bgnCoord + shiftval,
C2_3 = easeStr === 'end' ? C1 : C4;
return function (per) {
return Math.round(
C1 * B1(per) +
C2_3 * B2(per) +
C2_3 * B3(per) +
C4 * B4(per)
) - shiftval;
};
};
}());
// Filename: beastmove.js
// Timestamp: 2013.12.26-21:23:51 (last modified)
// Author(s): Bumblehead (www.bumblehead.com)
// Requires: curved.js, elemst.js, beastplug.js, domlt.js
//
//
// ex,
//
// beast({ frames : 30 }).move({
// elem : elem,
// ltbgn : [0, 0],
// ltend : [200, 200]
// }).init();
//
// element should be styled 'absolute' and 'block'
beastplug('move', function (b) {
b.getdata = function (frames, opts) {
var elem = opts.elem,
ease = opts.ease,
ltcur = domlt(elem),
ltbgn = opts.ltbgn || ltcur,
ltend = opts.ltend || ltcur,
ltarr = [],
isl = b.isanimated(ltbgn, ltend, 0),
ist = b.isanimated(ltbgn, ltend, 1),
xcurve = isl && curved(ltbgn[0], ltend[0], ease),
ycurve = ist && curved(ltbgn[1], ltend[1], ease);
b.doframeloop(frames, function (per, frame) {
ltarr.push([
xcurve && xcurve(per),
ycurve && ycurve(per)
]);
});
b.isl = isl;
b.ist = ist;
b.ltarr = ltarr;
return ltarr;
};
b.clean = function (frames, opts) {
var style = opts.elem.style;
style.top = '';
style.left = '';
};
b.getupdatefn = function (frames, opts) {
var stylestr = ':0px',
ltarr = b.ltarr,
style = opts.elem.style,
isl = b.isl,
ist = b.ist,
lt;
return function (frame) {
lt = ltarr[frame];
if (isl) style.left = stylestr.replace(/:0/, lt[0]);
if (ist) style.top = stylestr.replace(/:0/, lt[1]);
};
};
});
// Filename: beastcolor.js
// Timestamp: 2013.12.26-21:24:12 (last modified)
// Author(s): Bumblehead (www.bumblehead.com)
// Requires: elemst.js, curved.js, beastplug.js
//
//
// ex,
//
// beast({ frames : 30 }).color({
// elem : elem,
// ease : 'out'
// bgnColor : '#aaf',
// endColor : '#aaf',
// endBackgroundColor : 'rgb("255", "255", "255")'
// }).init();
beastplug('color', function (b) {
b.extractRGBarr = function (rgbStr) {
if (rgbStr.match(/^#/)) {
return rgbStr
.replace(/^#(\w\w\w)$/, '$1$1')
.match(/(\w\w)(\w\w)(\w\w)/)
.slice(1, 4)
.map(function (n) { return parseInt(n, 16); });
} else if (rgbStr.match(/^rgba?/)) {
return rgbStr
.match(/^rgba?\((\d{1,3}), (\d{1,3}), (\d{1,3}),? ?(\d{1,3})?\)$/)
.slice(1, 4)
.map(function (n) { return parseInt(n, 10); });
}
};
b.buildRGBarray = function (frames, opts, bgnRGB, endRGB) {
var rgbarr = [],
ease = opts.ease,
bgnRGBarr = b.extractRGBarr(bgnRGB),
endRGBarr = b.extractRGBarr(endRGB),
rcurve = curved(bgnRGBarr[0], endRGBarr[0], ease),
gcurve = curved(bgnRGBarr[1], endRGBarr[1], ease),
bcurve = curved(bgnRGBarr[2], endRGBarr[2], ease);
b.doframeloop(frames, function (per, frame) {
rgbarr.push([
rcurve(per), gcurve(per), bcurve(per)
]);
});
return rgbarr;
};
b.clean = function (frames, opts) {
var style = opts.elem.style;
style.backgroundColor = '';
style.color = '';
};
b.getdata = function (frames, opts) {
var elem = opts.elem,
bgarr = [],
fgarr = [],
endfgrgb = opts.endColor ||
b.getComputed(elem, 'color'),
endbgrgb = opts.endBackgroundColor ||
b.getComputed(elem, 'background-color'),
bgnfgrgb = opts.bgnColor ||
b.getComputed(elem, 'color'),
bgnbgrgb = opts.bgnBackgroundColor ||
b.getComputed(elem, 'background-color');
opts.fgarr = b.buildRGBarray(frames, opts, bgnfgrgb, endfgrgb);
opts.bgarr = b.buildRGBarray(frames, opts, bgnbgrgb, endbgrgb);
};
b.getupdatefn = function (frames, opts) {
var bgarr = opts.bgarr,
fgarr = opts.fgarr,
style = opts.elem.style,
rgbStr = "rgb(:r, :g, :b)",
bg, fg;
return function (frame) {
bg = bgarr[frame];
fg = fgarr[frame];
style.backgroundColor =
rgbStr.replace(/:r/, bg[0]).replace(/:g/, bg[1]).replace(/:b/, bg[2]);
style.color =
rgbStr.replace(/:r/, fg[0]).replace(/:g/, fg[1]).replace(/:b/, fg[2]);
};
};
});
// Filename: domopacity.js
// Timestamp: 2013.12.22-20:53:15 (last modified)
// Author(s): Bumblehead (www.bumblehead.com)
//
// if undefined, getComputedStyle should be defined by polyfill.
//
// the 'is' method for the IE branch is from Garret Smith's APE library:
// - http://www.highdots.com/forums/javascript/get-opacity-value-291932.html
// - https://github.com/GarrettS/ape-javascript-library
var domopacity = (function (s, p, deffn) {
s = document.documentElement.style;
deffn = function () {};
if ('opacity' in s) {
p = function (elem, percent) {
elem.style.opacity = percent;
};
p.rm = function (elem) {
elem.style.opacity = '';
};
p.is = function (elem) {
return getComputedStyle(elem, null).getPropertyValue('opacity');
};
} else if ('filter' in s) {
p = function (elem, percent) {
elem.style.filter = 'alpha(opacity=:o)'.replace(/:o/, percent * 100);
};
p.rm = function (elem) {
elem.style.filter = '';
};
p.is = function (elem) {
var re = /\Wopacity\s*=\s*([\d]+)/i,
cs = elem.currentStyle, f, o;
if (typeof cs === 'object') {
f = cs.filter;
if (!re.test(f)) {
return 1;
} else {
o = re.exec(f);
return o[1]/100;
}
};
};
} else {
p = p.rm = p.is = deffn;
}
return p;
}());
// Filename: beastfade.js
// Timestamp: 2013.12.26-21:23:39 (last modified)
// Author(s): Bumblehead (www.bumblehead.com)
// Requires: elemst.js, curved.js, beastplug.js, domopacity.js
//
// 555 Smith Boulevard, #4459
// San Angelo, TX 76905
//
// ex,
//
// beast({ frames : 30 }).fade({
// elem : elem,
// ease : 'bgn'
// }).init();
beastplug('fade', function (b) {
b.getdata = function (frames, opts) {
var arr = [],
bgn = +(domopacity.is(opts.elem, 'opacity') || 1) * 100,
curve = curved(bgn, opts.endop, opts.ease);
b.doframeloop(frames, function (per, frame) {
arr.push(curve(per) / 100);
});
return arr;
};
b.clean = function (frames, opts) {
domopacity.rm(opts.elem);
};
b.getupdatefn = function (frames, opts) {
var oparr = opts.oparr || [],
elem = opts.elem;
return function (frame) {
domopacity(elem, oparr[frame]);
};
};
});
// Filename: domwh.js
// Timestamp: 2013.12.24-17:03:48 (last modified)
// Author(s): Bumblehead (www.bumblehead.com)
var domwh = (function (p, d, doc) {
doc = document;
p = function(elem) {
var d, dims = [elem.offsetWidth, elem.offsetHeight];
if (!dims[0]) {
d = elem.style;
if (d.display === 'none') {
d.display = 'block';
dims = [elem.offsetWidth, elem.offsetHeight];
d.display = 'none';
} else if (d.display === '') {
d.display = 'block';
dims = [elem.offsetWidth, elem.offsetHeight];
d.display = '';
}
}
return dims;
};
p.window = function () {
if (window && window.innerHeight) {
return [window.innerWidth, window.innerHeight];
} else if ((d = doc.documentElement)) {
d = d.clientWidth ? d : doc.documentBody;
return [d.clientWidth, d.clientHeight];
} else {
return null;
}
};
return p;
}());
// Filename: beastshape.js
// Timestamp: 2013.12.26-21:24:02 (last modified)
// Author(s): Bumblehead (www.bumblehead.com)
// Requires: curved.js, elemst.js, domwh.js, beastplug.js
//
//
// adds 'plugin' ability to the animation system here.
//
beastplug('shape', function (b) {
b.getdata = function (frames, opts) {
var elem = opts.elem,
ease = opts.ease,
whcur = domwh(elem),
whbgn = opts.whbgn || whcur,
whend = opts.whend || whcur,
wharr = [],
isw = b.isanimated(whbgn, whend, 0),
ish = b.isanimated(whbgn, whend, 1),
wcurve = isw && curved(whbgn[0], whend[0], ease),
hcurve = ish && curved(whbgn[1], whend[1], ease);
b.doframeloop(frames, function (per, frame) {
wharr.push([
wcurve && wcurve(per),
hcurve && hcurve(per)
]);
});
b.isw = isw;
b.ish = ish;
b.wharr = wharr;
return wharr;
};
b.clean = function (frames, opts) {
var style = opts.elem.style;
style.width = '';
style.height = '';
};
b.getupdatefn = function (frames, opts) {
var stylestr = ':0px',
wharr = b.wharr,
style = opts.elem.style,
isw = b.isw,
ish = b.ish,
wh;
return function (frame) {
wh = wharr[frame];
if (isw) style.width = stylestr.replace(/:0/, wh[0]);
if (ish) style.height = stylestr.replace(/:0/, wh[1]);
};
};
});
// Filename: beast.full.js
// Timestamp: 2013.12.24-15:56:25 (last modified)
// Author(s): Bumblehead (www.bumblehead.com)
// Requires: beast.js, beastshape.js, beastfade.js, beastcolor.js, beastmove.js
//
// placeholder file for directing scroungejs build