Skip to content

Instantly share code, notes, and snippets.

@tim-evans
Created March 9, 2012 13:52
Show Gist options
  • Select an option

  • Save tim-evans/2006591 to your computer and use it in GitHub Desktop.

Select an option

Save tim-evans/2006591 to your computer and use it in GitHub Desktop.
CSS3 Transitions shim
var getStyle = function (el, name) {
var style;
if (el.currentStyle) {
style = el.currentStyle[name];
} else if (window.getComputedStyle) {
style = document.defaultView.getComputedStyle(el, null).getPropertyValue(name);
}
return style;
};
// Internal function to mix the arguments
// passed in into the target object.
//
// For example:
//
// var o = {};
// mix({
// foo: 'bar'
// }).into(o);
//
// assert(o.foo === 'bar');
//
var mix = function () {
var mixins = arguments,
i = 0, len = mixins.length;
return {
into: function (target) {
var mixin, key;
for (; i < len; i += 1) {
mixin = mixins[i];
for (key in mixin) {
target[key] = mixin[key];
}
}
return target;
}
};
};
var CUBIC_BEZIER = /cubic-bezier\(([.\d]+),\s*([.\d]+),\s*([.\d]+),\s*([.\d]+)\)/;
var Animation = function (layer, end, options) {
options = options || {};
var start = {},
keys = [],
delta = {},
units = {},
key, style,
plan = [],
timing = options.timing || "ease",
isBezier = false;
if (options.framerate) {
this.framerate = options.framerate;
}
if (typeof timing === "string") {
if (Animation.timings.hasOwnProperty(timing)) {
timing = Animation.timings[timing];
}
if (CUBIC_BEZIER.test(timing)) {
var match = timing.match(CUBIC_BEZIER);
isBezier = true;
timing = cubicBezier(parseFloat(match[1]),
parseFloat(match[2]),
parseFloat(match[3]),
parseFloat(match[4]));
}
}
this.callback = options.callback;
if (options.duration) {
this.duration = options.duration;
}
for (key in end) {
if (end.hasOwnProperty(key)) {
style = getStyle(layer, key);
keys.push(key);
start[key] = parseInt(style);
units[key] = style.slice(style.length - (start[key] + "").length);
delta[key] = end[key] - start[key];
}
}
// Create a plan of all the points that we should hit
// in this animation.
var duration = this.duration,
framerate = this.framerate,
nFrames = (framerate * this.duration),
frames = new Array(nFrames),
i, j, kLen = keys.length, frame,
time, percent;
for (i = 0; i <= nFrames; i++) {
frames[i] = frame = {};
percent = i / nFrames;
time = i / framerate;
for (j = 0; j < kLen; j++) {
key = keys[j];
// Bezier curve
if (isBezier) {
frame[key] = step(timing(percent), start[key], delta[key]);
// Keyframes
} else {
throw new Error("Keyframe animations aren't currently supported");
}
}
}
this.frames = frames;
this.lastFrame = frames.length;
this.timeBetweenFrames = 1 / framerate * 1000;
this.layer = layer;
RunLoop.schedule(this);
};
var step = function (percent, start, delta) {
return start + percent * delta;
};
Animation.prototype = {
layer: null,
duration: 0.25,
framerate: 60,
lastFrame: 0,
frame: 0,
lastRuntime: 0,
callback: null,
frames: null,
timeBetweenFrames: 1,
run: function (now) {
var frame = this.frame,
lastRuntime = this.lastRuntime;
if (lastRuntime) {
frame += Math.floor((now - lastRuntime) / this.timeBetweenFrames);
if (frame === this.frame) {
return; // NOOP
}
}
if (frame >= this.lastFrame) {
this.stop();
} else {
mix(this.frames[frame]).into(this.layer.style);
this.frame = frame;
this.lastRuntime = now;
}
},
stop: function () {
if (this.callback) {
this.callback(this.layer);
}
RunLoop.stop(this);
},
destroy: function () {
this.frames = null;
this.layer = null;
this.callback = null;
}
};
// shim layer with setTimeout fallback
var requestAnimationFrame = (function () {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
};
}());
var RunLoop = {
tasks: {},
completed: [],
guid: 0,
running: 0,
schedule: function (task) {
task.__guid__ = this.guid;
this.tasks[this.guid++] = task;
this.running++;
var self = this;
requestAnimationFrame(function () {
self.run();
});
},
run: function () {
var tasks = this.tasks,
start = new Date().getTime(),
self = this,
key;
for (key in tasks) {
if (tasks.hasOwnProperty(key)) {
tasks[key].run(start);
}
}
this.flush();
if (this.running) {
requestAnimationFrame(function () {
self.run();
});
}
},
flush: function () {
var completed = this.completed,
len = completed.length,
tasks = this.tasks,
task,
idx, i;
for (i = 0; i < len; i ++) {
task = completed[i];
idx = task.__guid__;
// GC
task.destroy();
delete tasks[idx];
}
this.completed = [];
},
stop: function (task) {
this.completed.push(task);
this.running--;
}
};
var cubicBezier = function (X1, Y1, X2, Y2) {
var X0 = 0,
Y0 = 0,
X3 = 1,
Y3 = 0,
A = X3 - 3 * X2 + 3 * X1 - X0,
B = 3 * X2 - 6 * X1 + 3 * X0,
C = 3 * X1 - 3 * X0,
D = X0,
E = Y3 - 3 * Y2 + 3 * Y1 - Y0,
F = 3 * Y2 - 6 * Y1 + 3 * Y0,
G = 3 * Y1 - 3 * Y0,
H = Y0;
return function (t, start, delta) {
return ((((A * t) + B) * t + C) * t + D);
};
};
this.Animation = Animation;
// CSS3 default timings via cubic béziers
Animation.timings = {
"linear": "cubic-bezier(0.250, 0.250, 0.750, 0.750)",
"ease": "cubic-bezier(0.250, 0.100, 0.250, 1.000)",
"ease-in": "cubic-bezier(0.420, 0.000, 1.000, 1.000)",
"ease-out": "cubic-bezier(0.000, 0.000, 0.580, 1.000)",
"ease-in-out": "cubic-bezier(0.420, 0.000, 0.580, 1.000)"
};
var pad = function (string, fill, length) {
while (string.length < length) {
string += fill;
}
return string;
};
Color = function (color) {
var C = Color,
r, g, b;
if (Color.KEYWORDS.hasOwnProperty(color)) {
color = Color.KEYWORDS[color];
}
if (C.RGB.test(color)) {
this.type = 'rgb';
color = color.match(C.RGB);
r = C.percentOrGamut(color[1]);
g = C.percentOrGamut(color[2]);
b = C.percentOrGamut(color[3]);
} else if (C.RGBa.test(color)) {
this.type = 'rgba';
color = color.match(C.RGBa);
r = C.percentOrGamut(color[1]);
g = C.percentOrGamut(color[2]);
b = C.percentOrGamut(color[3]);
this.alpha = C.clipToPercent(parseFloat(color[4], 10));
} else if (C.HEX.test(color)) {
this.type = '#rrggbb';
// The three-digit RGB notation (#rgb)
// is converted into six-digit form (#rrggbb)
// by replicating digits, not by adding zeros.
if (color.length === 4) {
color = '#' + color.charAt(1) + color.charAt(1)
+ color.charAt(2) + color.charAt(2)
+ color.charAt(3) + color.charAt(3);
}
r = parseInt(color.slice(1, 3), 16);
g = parseInt(color.slice(3, 5), 16);
b = parseInt(color.slice(5, 7), 16);
} else if (C.ARGB.test(color)) {
this.type = '#aarrggbb';
r = parseInt(color.slice(3, 5), 16);
g = parseInt(color.slice(5, 7), 16);
b = parseInt(color.slice(7, 9), 16);
this.alpha = parseInt(color.slice(1, 3), 16) / 255;
} else if (C.HSL.test(color)) {
this.type = 'hsl';
color = color.match(C.HSL);
color = Color.hslToRGB((parseInt(color[1], 10) % 360) / 360,
C.clipToPercent(parseInt(color[2], 10) / 100),
C.clipToPercent(parseInt(color[3], 10) / 100));
r = Math.round(color[0] * 255);
g = Math.round(color[1] * 255);
b = Math.round(color[2] * 255);
} else if (C.HSLa.test(color)) {
this.type = 'hsla';
color = color.match(C.HSLa);
this.alpha = C.clipToPercent(parseFloat(color[4], 10));
color = Color.hslToRGB((parseInt(color[1], 10) % 360) / 360,
C.clipToPercent(parseInt(color[2], 10) / 100),
C.clipToPercent(parseInt(color[3], 10) / 100));
r = Math.round(color[0] * 255);
g = Math.round(color[1] * 255);
b = Math.round(color[2] * 255);
// See http://www.w3.org/TR/css3-color/#transparent-def
} else if (color === "transparent") {
this.type = 'rgba';
r = g = b = 0;
this.alpha = 0;
} else if (color != null) {
return false;
}
this.r = C.clipToDeviceGamut(r);
this.g = C.clipToDeviceGamut(g);
this.b = C.clipToDeviceGamut(b);
};
Color.percentOrGamut = function (value) {
var v = parseInt(value, 10);
return value.slice(-1) === "%"
? Math.round(Math.max(Math.min(v / 100, 1), 0) * 255)
: Math.max(Math.min(v, 255), 0);
};
Color.clipToDeviceGamut = function (value) {
return Math.max(Math.min(value, 255), 0);
};
Color.clipToPercent = function (value) {
return Math.max(Math.min(value, 1), 0);
};
// See http://www.w3.org/TR/css3-color/#hsl-color
// HOW TO RETURN hsl.to.rgb(h, s, l):
Color.hslToRGB = function (h, s, l) {
var m1, m2, hueToRGB = Color.hueToRGB;
// SELECT:
if (l <= 0.5) {
// l<=0.5: PUT l*(s+1) IN m2
m2 = l * (s + 1);
} else {
// ELSE: PUT l+s-l*s IN m2
m2 = l + s - l * s;
}
// PUT l*2-m2 IN m1
m1 = l * 2 - m2;
// PUT hue.to.rgb(m1, m2, h+1/3) IN r
// PUT hue.to.rgb(m1, m2, h ) IN g
// PUT hue.to.rgb(m1, m2, h-1/3) IN b
// RETURN (r, g, b)
return [hueToRGB(m1, m2, h + 1/3),
hueToRGB(m1, m2, h),
hueToRGB(m1, m2, h - 1/3)];
};
// HOW TO RETURN hue.to.rgb(m1, m2, h):
Color.hueToRGB = function (m1, m2, h) {
// IF h<0: PUT h+1 IN h
if (h < 0) h++;
// IF h>1: PUT h-1 IN h
if (h > 1) h--;
// IF h*6<1: RETURN m1+(m2-m1)*h*6
if (h * 6 < 1) return m1 + (m2 - m1) * h * 6;
// IF h*2<1: RETURN m2
if (h * 2 < 1) return m2;
// IF h*3<2: RETURN m1+(m2-m1)*(2/3-h)*6
if (h * 6 < 1) return m1 + (m2 - m1) * (2 / 3 - h) * 6;
// RETURN m1
return m1;
};
Color.RGBa = /rgba\(([\d]+%?),\s*([\d]+%?),\s*([\d]+%?),\s*([.\d]+)\)/;
Color.RGB = /rgb\(([\d]+%?),\s*([\d]+%?),\s*([\d]+%?)\)/;
Color.HSLa = /hsla\(([\d]+),\s*([\d]+)%,\s*([\d]+)%,\s*([.\d]+)\)/;
Color.HSL = /hsl\(([\d]+),\s*([\d]+)%,\s*([\d]+)%\)/;
Color.HEX = /#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?/;
Color.ARGB = /#[0-9a-fA-F]{8}/;
// See http://www.w3.org/TR/css3-color/#svg-color
//
// Computed by the following code:
//
// var T = {}, color = null,
// colors = document.querySelectorAll('.colortable')[1].querySelectorAll('.c');
//
// for (var i = 0; i < colors.length; i++) {
// if (i % 4 === 0) {
// color = colors[i].getAttribute('style').split(':')[1];
// } else if (i % 4 === 1) {
// T[color] = colors[i].getAttribute('style').split(':')[1].toUpperCase();
// }
// }
// JSON.stringify(T);
//
Color.KEYWORDS = {"aliceblue":"#F0F8FF","antiquewhite":"#FAEBD7","aqua":"#00FFFF","aquamarine":"#7FFFD4","azure":"#F0FFFF","beige":"#F5F5DC","bisque":"#FFE4C4","black":"#000000","blanchedalmond":"#FFEBCD","blue":"#0000FF","blueviolet":"#8A2BE2","brown":"#A52A2A","burlywood":"#DEB887","cadetblue":"#5F9EA0","chartreuse":"#7FFF00","chocolate":"#D2691E","coral":"#FF7F50","cornflowerblue":"#6495ED","cornsilk":"#FFF8DC","crimson":"#DC143C","cyan":"#00FFFF","darkblue":"#00008B","darkcyan":"#008B8B","darkgoldenrod":"#B8860B","darkgray":"#A9A9A9","darkgreen":"#006400","darkgrey":"#A9A9A9","darkkhaki":"#BDB76B","darkmagenta":"#8B008B","darkolivegreen":"#556B2F","darkorange":"#FF8C00","darkorchid":"#9932CC","darkred":"#8B0000","darksalmon":"#E9967A","darkseagreen":"#8FBC8F","darkslateblue":"#483D8B","darkslategray":"#2F4F4F","darkslategrey":"#2F4F4F","darkturquoise":"#00CED1","darkviolet":"#9400D3","deeppink":"#FF1493","deepskyblue":"#00BFFF","dimgray":"#696969","dimgrey":"#696969","dodgerblue":"#1E90FF","firebrick":"#B22222","floralwhite":"#FFFAF0","forestgreen":"#228B22","fuchsia":"#FF00FF","gainsboro":"#DCDCDC","ghostwhite":"#F8F8FF","gold":"#FFD700","goldenrod":"#DAA520","gray":"#808080","green":"#008000","greenyellow":"#ADFF2F","grey":"#808080","honeydew":"#F0FFF0","hotpink":"#FF69B4","indianred":"#CD5C5C","indigo":"#4B0082","ivory":"#FFFFF0","khaki":"#F0E68C","lavender":"#E6E6FA","lavenderblush":"#FFF0F5","lawngreen":"#7CFC00","lemonchiffon":"#FFFACD","lightblue":"#ADD8E6","lightcoral":"#F08080","lightcyan":"#E0FFFF","lightgoldenrodyellow":"#FAFAD2","lightgray":"#D3D3D3","lightgreen":"#90EE90","lightgrey":"#D3D3D3","lightpink":"#FFB6C1","lightsalmon":"#FFA07A","lightseagreen":"#20B2AA","lightskyblue":"#87CEFA","lightslategray":"#778899","lightslategrey":"#778899","lightsteelblue":"#B0C4DE","lightyellow":"#FFFFE0","lime":"#00FF00","limegreen":"#32CD32","linen":"#FAF0E6","magenta":"#FF00FF","maroon":"#800000","mediumaquamarine":"#66CDAA","mediumblue":"#0000CD","mediumorchid":"#BA55D3","mediumpurple":"#9370DB","mediumseagreen":"#3CB371","mediumslateblue":"#7B68EE","mediumspringgreen":"#00FA9A","mediumturquoise":"#48D1CC","mediumvioletred":"#C71585","midnightblue":"#191970","mintcream":"#F5FFFA","mistyrose":"#FFE4E1","moccasin":"#FFE4B5","navajowhite":"#FFDEAD","navy":"#000080","oldlace":"#FDF5E6","olive":"#808000","olivedrab":"#6B8E23","orange":"#FFA500","orangered":"#FF4500","orchid":"#DA70D6","palegoldenrod":"#EEE8AA","palegreen":"#98FB98","paleturquoise":"#AFEEEE","palevioletred":"#DB7093","papayawhip":"#FFEFD5","peachpuff":"#FFDAB9","peru":"#CD853F","pink":"#FFC0CB","plum":"#DDA0DD","powderblue":"#B0E0E6","purple":"#800080","red":"#FF0000","rosybrown":"#BC8F8F","royalblue":"#4169E1","saddlebrown":"#8B4513","salmon":"#FA8072","sandybrown":"#F4A460","seagreen":"#2E8B57","seashell":"#FFF5EE","sienna":"#A0522D","silver":"#C0C0C0","skyblue":"#87CEEB","slateblue":"#6A5ACD","slategray":"#708090","slategrey":"#708090","snow":"#FFFAFA","springgreen":"#00FF7F","steelblue":"#4682B4","tan":"#D2B48C","teal":"#008080","thistle":"#D8BFD8","tomato":"#FF6347","turquoise":"#40E0D0","violet":"#EE82EE","wheat":"#F5DEB3","white":"#FFFFFF","whitesmoke":"#F5F5F5","yellow":"#FFFF00","yellowgreen":"#9ACD32"};
Color.prototype = {
type: null,
alpha: 1,
r: 0,
g: 0,
b: 0,
toARGB: function () {
var a = Math.round(255 * this.alpha);
return '#' + pad(a.toString(16), '0', 2) +
this.toHex.slice(1);
},
toHex: function () {
return '#' + pad(this.r.toString(16), '0', 2) +
pad(this.g.toString(16), '0', 2) +
pad(this.b.toString(16), '0', 2);
},
toRGB: function () {
return 'rgb(' + this.r + ','
+ this.g + ','
+ this.b + ')';
},
toRGBa: function () {
var rgb = this.toRGB();
return 'rgba' + rgb.slice(3, -1) + ',' + this.alpha + ')';
},
toHSL: function () {
var r = this.r / 255,
g = this.g / 255,
b = this.b / 255;
var max = Math.max(r, g, b),
min = Math.min(r, g, b),
h, s, l = ((max + min) / 2),
d = max - min;
if (max === min) {
h = s = 0;
} else {
s = l > 0.5
? d / (2 - max - min)
: d / (max + min);
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / d + 2;
break;
case b:
h = (r - g) / d + 4;
break;
}
h /= 6;
}
h = Math.round(h * 360);
s = Math.round(s * 100);
l = Math.round(l * 100);
return 'hsl(' + h + ','
+ s + '%,'
+ l + '%)';
},
toHSLa: function () {
var hsl = this.toHSL();
return 'hsla' + hsl.slice(3, -1) + ',' + this.alpha + ')';
},
toString: function () {
var str;
switch (this.type) {
case 'rgb':
str = this.toRGB();
break;
case 'rgba':
str = this.toRGBa();
break;
case '#rrggbb':
str = this.toHex();
break;
case '#aarrggbb':
str = this.toARGB();
break;
case 'hsl':
str = this.toHSL();
break;
case 'hsla':
str = this.toHSLa();
break;
}
return str;
},
/**
Returns a new Color that is between
*/
sub: function (color) {
return mix({
r: this.r - color.r,
g: this.g - color.g,
b: this.b - color.b,
alpha: this.alpha - color.alpha
}).into(new Color());
},
add: function (color) {
return mix({
r: this.r + color.r,
g: this.g + color.g,
b: this.b + color.b,
alpha: this.alpha + color.alpha
}).into(new Color());
},
mult: function (multiplier) {
return mix({
type: this.type,
r: this.r * multiplier,
g: this.g * multiplier,
b: this.b * multiplier,
alpha: this.alpha * multiplier
}).into(new Color());
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment