diff --git a/_includes/head.html b/_includes/head.html index 61d1a58..cf9536d 100644 --- a/_includes/head.html +++ b/_includes/head.html @@ -27,7 +27,7 @@ - + diff --git a/img/favicon-sprite.png b/img/favicon-sprite.png new file mode 100644 index 0000000..8d66332 Binary files /dev/null and b/img/favicon-sprite.png differ diff --git a/img/favicon-sprite@2x.png b/img/favicon-sprite@2x.png new file mode 100644 index 0000000..a7aa791 Binary files /dev/null and b/img/favicon-sprite@2x.png differ diff --git a/js/page.js b/js/page.js index 22962cc..675e119 100644 --- a/js/page.js +++ b/js/page.js @@ -40,6 +40,14 @@ Modules.Support = (function() { return dfd.promise(); }; + // Fill rAF + var rAF = window.requestAnimationFrame || + window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || + window.msRequestAnimationFrame; + + window.requestAnimationFrame = rAF; + return self; }()); @@ -188,6 +196,176 @@ Modules.Nav = (function( $ ) { }( jQuery )); +Modules.Favicon = (function( doc ) { + 'use strict'; + + var Favicon = function( src, numFrames, framesPerAnimation, animationDelay ) { + var self = this; + + // Variables based on params + self.src = src; + self.numFrames = numFrames; + self.framesPerAnimation = framesPerAnimation; + self.animationDelay = animationDelay; + + // Elements + self.canvas = doc.createElement('canvas'); + self.img = doc.createElement('img'); + self.html = doc.documentElement; + + // Calculations + self.size = window.devicePixelRatio > 1 ? 32 : 16; + + // If it's not a data url, pick apart the filename and add @2x for retina + if ( !self.src.match(/data:/) && window.devicePixelRatio > 1 ) { + var dot = self.src.lastIndexOf('.'); + self.src = self.src.substring( 0, dot ) + '@2x' + self.src.substring( dot ); + } + + self.currentFrame = 0; + self.thirtyFPS = 1000 / 30; + + self.init(); + }; + + Favicon.prototype.init = function() { + var self = this; + + // No #favicon element or browser doesn't support canvas or < IE9, stop + if ( !doc.getElementById('favicon') || !self.canvas.getContext || self.html.className.indexOf('lt-ie9') > -1 ) { + return; + } + + // Save context + self.ctx = self.canvas.getContext('2d'); + + // Set canvas dimensions based on device DPI + self.canvas.height = self.canvas.width = self.size; + + // Create a new sprite 32x32 size with 32x32 sprites + self.sprite = new Sprite( self.ctx, self.img, self.size ); + + // Bind the image load handler + self.img.onload = self.onSpriteLoaded.bind( self ); + + // Trigger image to load + self.img.src = self.src; + }; + + Favicon.prototype.getData = function() { + return this.canvas.toDataURL('image/png'); + }; + + // Clone the current #favicon and replace it with a new element + // which has the updated data URI href + Favicon.prototype.setFavicon = function() { + var self = this, + data = self.getData(), + originalFavicon = doc.getElementById('favicon'), + clone = originalFavicon.cloneNode( true ); + + clone.setAttribute( 'href', data ); + originalFavicon.parentNode.replaceChild( clone, originalFavicon ); + }; + + // Request Animation Frame Loop + Favicon.prototype.loop = function( timestamp ) { + var self = this, + lastCall = self.lastTimestamp; + + // If not enough time has elapse since the last call + // immediately call the next rAF + if ( timestamp - lastCall < self.timeToElapse ) { + return requestAnimationFrame( self.loop.bind( self ) ); + } + + // Increment current frame + self.currentFrame += 1; + if ( self.currentFrame === self.numFrames ) { + self.currentFrame = 0; + } + + // Completed an animation state + self.timeToElapse = self.currentFrame % self.framesPerAnimation === 0 ? + self.animationDelay : + self.thirtyFPS; + + // Draw current frame from sprite + self.sprite.drawFrame( self.currentFrame ); + + // Swap + self.setFavicon(); + + // Set a timeout to draw again + self.lastTimestamp = timestamp; + + // Continue loop + return requestAnimationFrame( self.loop.bind( self ) ); + }; + + // Sprite loaded + Favicon.prototype.onSpriteLoaded = function() { + var self = this; + + // Draw the first frame when the image loads + self.sprite.drawFrame( self.currentFrame ); + + // Swap + self.setFavicon(); + + // Start loop + requestAnimationFrame( self.loop.bind( self ) ); + }; + + + var Sprite = function( context, img, size ) { + var self = this; + self.ctx = context; + self.img = img; + self.width = size; + self.height = size; + self.frameWidth = size; + self.frameHeight = size; + }; + + // Assuming horizontal sprite + Sprite.prototype.getFrame = function( frame ) { + return { + x: frame * this.frameWidth, + y: 0 + }; + }; + + Sprite.prototype.clearCanvas = function() { + this.ctx.clearRect( 0, 0, this.width, this.height ); + }; + + Sprite.prototype.drawFrame = function( frameNumber ) { + var self = this; + + var frame = self.getFrame( frameNumber ); + + // Clear out the last frame + self.clearCanvas(); + + // Draw to the context. This method is really confusing... + self.ctx.drawImage( + self.img, + frame.x, + frame.y, + self.width, + self.height, + 0, + 0, + self.width, + self.height + ); + }; + + return Favicon; +}( document )); + + // Analytics var _gaq = [ ['_setAccount', 'UA-39355642-1'], ['_trackPageview'] ]; @@ -222,4 +400,15 @@ $(document).ready(function() { Modules.Nav.init(); Modules.Polyfill.init(); -}); \ No newline at end of file + + var src = '/img/favicon-sprite.png'; + new Modules.Favicon( src, 21, 7, 3000 * 1 ); +}); + + + + + + + +