Created
March 9, 2012 13:52
-
-
Save tim-evans/2006591 to your computer and use it in GitHub Desktop.
CSS3 Transitions shim
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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