/** * @license MIT * @fileOverview Favico animations * @author Miroslav Magda, http://blog.ejci.net * @version 0.3.7 */ /** * Create new favico instance * @param {Object} Options * @return {Object} Favico object * @example * var favico = new Favico({ * bgColor : '#d00', * textColor : '#fff', * fontFamily : 'sans-serif', * fontStyle : 'bold', * position : 'down', * type : 'circle', * animation : 'slide', * dataUrl: function(url){} * }); */ (function() { var Favico = (function(opt) { 'use strict'; opt = (opt) ? opt : {}; var _def = { bgColor : '#d00', textColor : '#fff', fontFamily : 'sans-serif', //Arial,Verdana,Times New Roman,serif,sans-serif,... fontStyle : 'bold', //normal,italic,oblique,bold,bolder,lighter,100,200,300,400,500,600,700,800,900 type : 'circle', position : 'down', // down, up, left, leftup (upleft) animation : 'slide', elementId : false, dataUrl : false }; var _opt, _orig, _h, _w, _canvas, _context, _img, _ready, _lastBadge, _running, _readyCb, _stop, _browser, _animTimeout, _drawTimeout; _browser = {}; _browser.ff = typeof InstallTrigger != 'undefined'; _browser.chrome = !!window.chrome; _browser.opera = !!window.opera || navigator.userAgent.indexOf('Opera') >= 0; _browser.ie = /*@cc_on!@*/false; _browser.safari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; _browser.supported = (_browser.chrome || _browser.ff || _browser.opera); var _queue = []; _readyCb = function() { }; _ready = _stop = false; /** * Initialize favico */ var init = function() { //merge initial options _opt = merge(_def, opt); _opt.bgColor = hexToRgb(_opt.bgColor); _opt.textColor = hexToRgb(_opt.textColor); _opt.position = _opt.position.toLowerCase(); _opt.animation = (animation.types['' + _opt.animation]) ? _opt.animation : _def.animation; var isUp = _opt.position.indexOf('up') > -1; var isLeft = _opt.position.indexOf('left') > -1; //transform animation if (isUp || isLeft) { for (var i = 0; i < animation.types['' + _opt.animation].length; i++) { var step = animation.types['' + _opt.animation][i]; if (isUp) { if (step.y < 0.6) { step.y = step.y - 0.4; } else { step.y = step.y - 2 * step.y + (1 - step.w); } } if (isLeft) { if (step.x < 0.6) { step.x = step.x - 0.4; } else { step.x = step.x - 2 * step.x + (1 - step.h); } } animation.types['' + _opt.animation][i] = step; } } _opt.type = (type['' + _opt.type]) ? _opt.type : _def.type; _orig = link.getIcon(); //create temp canvas _canvas = document.createElement('canvas'); //create temp image _img = document.createElement('img'); if (_orig.hasAttribute('href')) { _img.setAttribute('src', _orig.getAttribute('href')); //get width/height _img.onload = function() { _h = (_img.height > 0) ? _img.height : 32; _w = (_img.width > 0) ? _img.width : 32; _canvas.height = _h; _canvas.width = _w; _context = _canvas.getContext('2d'); icon.ready(); }; } else { _img.setAttribute('src', ''); _h = 32; _w = 32; _img.height = _h; _img.width = _w; _canvas.height = _h; _canvas.width = _w; _context = _canvas.getContext('2d'); icon.ready(); } }; /** * Icon namespace */ var icon = {}; /** * Icon is ready (reset icon) and start animation (if ther is any) */ icon.ready = function() { _ready = true; icon.reset(); _readyCb(); }; /** * Reset icon to default state */ icon.reset = function() { //reset if (!_ready) { return; } _queue = []; _lastBadge = false; _running = false; _context.clearRect(0, 0, _w, _h); _context.drawImage(_img, 0, 0, _w, _h); //_stop=true; link.setIcon(_canvas); //webcam('stop'); //video('stop'); window.clearTimeout(_animTimeout); window.clearTimeout(_drawTimeout); }; /** * Start animation */ icon.start = function() { if (!_ready || _running) { return; } var finished = function() { _lastBadge = _queue[0]; _running = false; if (_queue.length > 0) { _queue.shift(); icon.start(); } else { } }; if (_queue.length > 0) { _running = true; var run = function() { // apply options for this animation ['type', 'animation', 'bgColor', 'textColor', 'fontFamily', 'fontStyle'].forEach(function(a) { if ( a in _queue[0].options) { _opt[a] = _queue[0].options[a]; } }); animation.run(_queue[0].options, function() { finished(); }, false); }; if (_lastBadge) { animation.run(_lastBadge.options, function() { run(); }, true); } else { run(); } } }; /** * Badge types */ var type = {}; var options = function(opt) { opt.n = (( typeof opt.n) === 'number') ? Math.abs(opt.n | 0) : opt.n; opt.x = _w * opt.x; opt.y = _h * opt.y; opt.w = _w * opt.w; opt.h = _h * opt.h; opt.len = ("" + opt.n).length; return opt; }; /** * Generate circle * @param {Object} opt Badge options */ type.circle = function(opt) { opt = options(opt); var more = false; if (opt.len === 2) { opt.x = opt.x - opt.w * 0.4; opt.w = opt.w * 1.4; more = true; } else if (opt.len >= 3) { opt.x = opt.x - opt.w * 0.65; opt.w = opt.w * 1.65; more = true; } _context.clearRect(0, 0, _w, _h); _context.drawImage(_img, 0, 0, _w, _h); _context.beginPath(); _context.font = _opt.fontStyle + " " + Math.floor(opt.h * (opt.n > 99 ? 0.85 : 1)) + "px " + _opt.fontFamily; _context.textAlign = 'center'; if (more) { _context.moveTo(opt.x + opt.w / 2, opt.y); _context.lineTo(opt.x + opt.w - opt.h / 2, opt.y); _context.quadraticCurveTo(opt.x + opt.w, opt.y, opt.x + opt.w, opt.y + opt.h / 2); _context.lineTo(opt.x + opt.w, opt.y + opt.h - opt.h / 2); _context.quadraticCurveTo(opt.x + opt.w, opt.y + opt.h, opt.x + opt.w - opt.h / 2, opt.y + opt.h); _context.lineTo(opt.x + opt.h / 2, opt.y + opt.h); _context.quadraticCurveTo(opt.x, opt.y + opt.h, opt.x, opt.y + opt.h - opt.h / 2); _context.lineTo(opt.x, opt.y + opt.h / 2); _context.quadraticCurveTo(opt.x, opt.y, opt.x + opt.h / 2, opt.y); } else { _context.arc(opt.x + opt.w / 2, opt.y + opt.h / 2, opt.h / 2, 0, 2 * Math.PI); } _context.fillStyle = 'rgba(' + _opt.bgColor.r + ',' + _opt.bgColor.g + ',' + _opt.bgColor.b + ',' + opt.o + ')'; _context.fill(); _context.closePath(); _context.beginPath(); _context.stroke(); _context.fillStyle = 'rgba(' + _opt.textColor.r + ',' + _opt.textColor.g + ',' + _opt.textColor.b + ',' + opt.o + ')'; //_context.fillText((more) ? '9+' : opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); if (( typeof opt.n) === 'number' && opt.n > 999) { _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000) ) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2)); } else { _context.fillText(opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); } _context.closePath(); }; /** * Generate rectangle * @param {Object} opt Badge options */ type.rectangle = function(opt) { opt = options(opt); var more = false; if (opt.len === 2) { opt.x = opt.x - opt.w * 0.4; opt.w = opt.w * 1.4; more = true; } else if (opt.len >= 3) { opt.x = opt.x - opt.w * 0.65; opt.w = opt.w * 1.65; more = true; } _context.clearRect(0, 0, _w, _h); _context.drawImage(_img, 0, 0, _w, _h); _context.beginPath(); _context.font = _opt.fontStyle + " " + Math.floor(opt.h * (opt.n > 99 ? 0.9 : 1)) + "px " + _opt.fontFamily; _context.textAlign = 'center'; _context.fillStyle = 'rgba(' + _opt.bgColor.r + ',' + _opt.bgColor.g + ',' + _opt.bgColor.b + ',' + opt.o + ')'; _context.fillRect(opt.x, opt.y, opt.w, opt.h); _context.fillStyle = 'rgba(' + _opt.textColor.r + ',' + _opt.textColor.g + ',' + _opt.textColor.b + ',' + opt.o + ')'; //_context.fillText((more) ? '9+' : opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); if (( typeof opt.n) === 'number' && opt.n > 999) { _context.fillText(((opt.n > 9999) ? 9 : Math.floor(opt.n / 1000) ) + 'k+', Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2)); } else { _context.fillText(opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); } _context.closePath(); }; /** * Set badge */ var badge = function(number, opts) { opts = (( typeof opts) === 'string' ? { animation : opts } : opts) || {}; _readyCb = function() { try { if ( typeof (number) === 'number' ? (number > 0) : (number !== '')) { var q = { type : 'badge', options : { n : number } }; if ('animation' in opts && animation.types['' + opts.animation]) { q.options.animation = '' + opts.animation; } if ('type' in opts && type['' + opts.type]) { q.options.type = '' + opts.type; } ['bgColor', 'textColor'].forEach(function(o) { if ( o in opts) { q.options[o] = hexToRgb(opts[o]); } }); ['fontStyle', 'fontFamily'].forEach(function(o) { if ( o in opts) { q.options[o] = opts[o]; } }); _queue.push(q); if (_queue.length > 100) { throw 'Too many badges requests in queue.'; } icon.start(); } else { icon.reset(); } } catch(e) { throw 'Error setting badge. Message: ' + e.message; } }; if (_ready) { _readyCb(); } }; /** * Set image as icon */ var image = function(imageElement) { _readyCb = function() { try { var w = imageElement.width; var h = imageElement.height; var newImg = document.createElement('img'); var ratio = (w / _w < h / _h) ? (w / _w) : (h / _h); newImg.setAttribute('src', imageElement.getAttribute('src')); newImg.height = (h / ratio); newImg.width = (w / ratio); _context.clearRect(0, 0, _w, _h); _context.drawImage(newImg, 0, 0, _w, _h); link.setIcon(_canvas); } catch(e) { throw 'Error setting image. Message: ' + e.message; } }; if (_ready) { _readyCb(); } }; /** * Set video as icon */ var video = function(videoElement) { _readyCb = function() { try { if (videoElement === 'stop') { _stop = true; icon.reset(); _stop = false; return; } //var w = videoElement.width; //var h = videoElement.height; //var ratio = (w / _w < h / _h) ? (w / _w) : (h / _h); videoElement.addEventListener('play', function() { drawVideo(this); }, false); } catch(e) { throw 'Error setting video. Message: ' + e.message; } }; if (_ready) { _readyCb(); } }; /** * Set video as icon */ var webcam = function(action) { //UR if (!window.URL || !window.URL.createObjectURL) { window.URL = window.URL || {}; window.URL.createObjectURL = function(obj) { return obj; }; } if (_browser.supported) { var newVideo = false; navigator.getUserMedia = navigator.getUserMedia || navigator.oGetUserMedia || navigator.msGetUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia; _readyCb = function() { try { if (action === 'stop') { _stop = true; icon.reset(); _stop = false; return; } newVideo = document.createElement('video'); newVideo.width = _w; newVideo.height = _h; navigator.getUserMedia({ video : true, audio : false }, function(stream) { newVideo.src = URL.createObjectURL(stream); newVideo.play(); drawVideo(newVideo); }, function() { }); } catch(e) { throw 'Error setting webcam. Message: ' + e.message; } }; if (_ready) { _readyCb(); } } }; /** * Draw video to context and repeat :) */ function drawVideo(video) { if (video.paused || video.ended || _stop) { return false; } //nasty hack for FF webcam (Thanks to Julian Ćwirko, kontakt@redsunmedia.pl) try { _context.clearRect(0, 0, _w, _h); _context.drawImage(video, 0, 0, _w, _h); } catch(e) { } _drawTimeout = setTimeout(drawVideo, animation.duration, video); link.setIcon(_canvas); } var link = {}; /** * Get icon from HEAD tag or create a new element */ link.getIcon = function() { var elm = false; //get link element var getLink = function() { var link = document.getElementsByTagName('head')[0].getElementsByTagName('link'); for (var l = link.length, i = (l - 1); i >= 0; i--) { if ((/(^|\s)icon(\s|$)/i).test(link[i].getAttribute('rel'))) { return link[i]; } } return false; }; if (_opt.element) { elm = _opt.element; } else if (_opt.elementId) { //if img element identified by elementId elm = document.getElementById(_opt.elementId); elm.setAttribute('href', elm.getAttribute('src')); } else { //if link element elm = getLink(); if (elm === false) { elm = document.createElement('link'); elm.setAttribute('rel', 'icon'); document.getElementsByTagName('head')[0].appendChild(elm); } } elm.setAttribute('type', 'image/png'); return elm; }; link.setIcon = function(canvas) { var url = canvas.toDataURL('image/png'); if (_opt.dataUrl) { //if using custom exporter _opt.dataUrl(url); } if (_opt.element) { _opt.element.setAttribute('src', url); } else if (_opt.elementId) { //if is attached to element (image) document.getElementById(_opt.elementId).setAttribute('src', url); } else { //if is attached to fav icon if (_browser.ff || _browser.opera) { //for FF we need to "recreate" element, atach to dom and remove old //var originalType = _orig.getAttribute('rel'); var old = _orig; _orig = document.createElement('link'); //_orig.setAttribute('rel', originalType); if (_browser.opera) { _orig.setAttribute('rel', 'icon'); } _orig.setAttribute('rel', 'icon'); _orig.setAttribute('type', 'image/png'); document.getElementsByTagName('head')[0].appendChild(_orig); _orig.setAttribute('href', url); if (old.parentNode) { old.parentNode.removeChild(old); } } else { _orig.setAttribute('href', url); } } }; //http://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb#answer-5624139 //HEX to RGB convertor function hexToRgb(hex) { var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; hex = hex.replace(shorthandRegex, function(m, r, g, b) { return r + r + g + g + b + b; }); var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r : parseInt(result[1], 16), g : parseInt(result[2], 16), b : parseInt(result[3], 16) } : false; } /** * Merge options */ function merge(def, opt) { var mergedOpt = {}; var attrname; for (attrname in def) { mergedOpt[attrname] = def[attrname]; } for (attrname in opt) { mergedOpt[attrname] = opt[attrname]; } return mergedOpt; } /** * Cross-browser page visibility shim * http://stackoverflow.com/questions/12536562/detect-whether-a-window-is-visible */ function isPageHidden() { return document.hidden || document.msHidden || document.webkitHidden || document.mozHidden; } /** * @namespace animation */ var animation = {}; /** * Animation "frame" duration */ animation.duration = 40; /** * Animation types (none,fade,pop,slide) */ animation.types = {}; animation.types.fade = [{ x : 0.4, y : 0.4, w : 0.6, h : 0.6, o : 0.0 }, { x : 0.4, y : 0.4, w : 0.6, h : 0.6, o : 0.1 }, { x : 0.4, y : 0.4, w : 0.6, h : 0.6, o : 0.2 }, { x : 0.4, y : 0.4, w : 0.6, h : 0.6, o : 0.3 }, { x : 0.4, y : 0.4, w : 0.6, h : 0.6, o : 0.4 }, { x : 0.4, y : 0.4, w : 0.6, h : 0.6, o : 0.5 }, { x : 0.4, y : 0.4, w : 0.6, h : 0.6, o : 0.6 }, { x : 0.4, y : 0.4, w : 0.6, h : 0.6, o : 0.7 }, { x : 0.4, y : 0.4, w : 0.6, h : 0.6, o : 0.8 }, { x : 0.4, y : 0.4, w : 0.6, h : 0.6, o : 0.9 }, { x : 0.4, y : 0.4, w : 0.6, h : 0.6, o : 1.0 }]; animation.types.none = [{ x : 0.4, y : 0.4, w : 0.6, h : 0.6, o : 1 }]; animation.types.pop = [{ x : 1, y : 1, w : 0, h : 0, o : 1 }, { x : 0.9, y : 0.9, w : 0.1, h : 0.1, o : 1 }, { x : 0.8, y : 0.8, w : 0.2, h : 0.2, o : 1 }, { x : 0.7, y : 0.7, w : 0.3, h : 0.3, o : 1 }, { x : 0.6, y : 0.6, w : 0.4, h : 0.4, o : 1 }, { x : 0.5, y : 0.5, w : 0.5, h : 0.5, o : 1 }, { x : 0.4, y : 0.4, w : 0.6, h : 0.6, o : 1 }]; animation.types.popFade = [{ x : 0.75, y : 0.75, w : 0, h : 0, o : 0 }, { x : 0.65, y : 0.65, w : 0.1, h : 0.1, o : 0.2 }, { x : 0.6, y : 0.6, w : 0.2, h : 0.2, o : 0.4 }, { x : 0.55, y : 0.55, w : 0.3, h : 0.3, o : 0.6 }, { x : 0.50, y : 0.50, w : 0.4, h : 0.4, o : 0.8 }, { x : 0.45, y : 0.45, w : 0.5, h : 0.5, o : 0.9 }, { x : 0.4, y : 0.4, w : 0.6, h : 0.6, o : 1 }]; animation.types.slide = [{ x : 0.4, y : 1, w : 0.6, h : 0.6, o : 1 }, { x : 0.4, y : 0.9, w : 0.6, h : 0.6, o : 1 }, { x : 0.4, y : 0.9, w : 0.6, h : 0.6, o : 1 }, { x : 0.4, y : 0.8, w : 0.6, h : 0.6, o : 1 }, { x : 0.4, y : 0.7, w : 0.6, h : 0.6, o : 1 }, { x : 0.4, y : 0.6, w : 0.6, h : 0.6, o : 1 }, { x : 0.4, y : 0.5, w : 0.6, h : 0.6, o : 1 }, { x : 0.4, y : 0.4, w : 0.6, h : 0.6, o : 1 }]; /** * Run animation * @param {Object} opt Animation options * @param {Object} cb Callabak after all steps are done * @param {Object} revert Reverse order? true|false * @param {Object} step Optional step number (frame bumber) */ animation.run = function(opt, cb, revert, step) { var animationType = animation.types[isPageHidden() ? 'none' : _opt.animation]; if (revert === true) { step = ( typeof step !== 'undefined') ? step : animationType.length - 1; } else { step = ( typeof step !== 'undefined') ? step : 0; } cb = (cb) ? cb : function() { }; if ((step < animationType.length) && (step >= 0)) { type[_opt.type](merge(opt, animationType[step])); _animTimeout = setTimeout(function() { if (revert) { step = step - 1; } else { step = step + 1; } animation.run(opt, cb, revert, step); }, animation.duration); link.setIcon(_canvas); } else { cb(); return; } }; //auto init init(); return { badge : badge, video : video, image : image, webcam : webcam, reset : icon.reset, browser : { supported : _browser.supported } }; }); // AMD / RequireJS if ( typeof define !== 'undefined' && define.amd) { define([], function() { return Favico; }); } // CommonJS else if ( typeof module !== 'undefined' && module.exports) { module.exports = Favico; } // included directly via