/**@license * * favloader v. 0.4.4 * * Vanilla JavaScript library for loading animation in favicon * * Copyright (c) 2018-2019 Jakub T. Jankiewicz * Released under the MIT license * * Build: Sat, 22 May 2021 11:37:31 +0000 */ /* global define, module, global, Worker, Blob, BlobBuilder, setTimeout, parseGIF */ (function(factory) { var root = typeof window !== 'undefined' ? window : global; if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. // istanbul ignore next define([], factory); } else if (typeof module === 'object' && module.exports) { // Node/CommonJS module.exports = factory(); } else { // Browser // istanbul ignore next root.favloader = factory(); } })(function(undefined) { // we use web worker to trigger interval since browser main thread is limited // when tab is not active function fworker(fn) { // ref: https://stackoverflow.com/a/10372280/387194 var str = '(' + fn.toString() + ')()'; var URL = window.URL || window.webkitURL; var blob; try { blob = new Blob([str], {type: 'application/javascript'}); } catch (e) { // Backwards-compatibility window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder; blob = new BlobBuilder(); blob.append(str); blob = blob.getBlob(); } return new Worker(URL.createObjectURL(blob)); } // ---------------------------------------------------------------------------------- var interval = (function() { var worker = fworker(function() { // rAF polyfil without setTimeout, ref: https://gist.github.com/paulirish/1579671 var vendors = ['ms', 'moz', 'webkit', 'o']; for(var x = 0; x < vendors.length && !self.requestAnimationFrame; ++x) { self.requestAnimationFrame = self[vendors[x]+'RequestAnimationFrame']; self.cancelAnimationFrame = self[vendors[x]+'CancelAnimationFrame'] || self[vendors[x]+'CancelRequestAnimationFrame']; } var raf = {}; self.addEventListener('message', function(response) { var data = response.data; var id = data.id; if (data.type !== 'RPC' || id === null) { return; } if (data.method == 'setInterval') { var interval_id = data.params[0]; if (typeof interval_id === 'number') { raf[interval_id] = self.setInterval(function() { self.postMessage({ type: 'interval', id: interval_id }); }, data.params[1]); self.postMessage({ type: 'RPC', id: id, result: interval_id }); } else { self.postMessage({ type: 'RPC', id: id, result: null, error: 'Invalid Argument, expected number' }); } } else if (data.method == 'clearInterval') { self.clearInterval(raf[data.params[0]]); delete raf[data.params[0]]; } }); }); var callbacks = {}; var rpc = (function() { var id = 0; return function rpc(method, params) { var _id = ++id; return new Promise(function(resolve, reject) { worker.addEventListener('message', function handler(response) { var data = response.data; if (data && data.type === 'RPC' && data.id === _id) { if (data.error) { reject(data.error); } else { resolve(data.result); } worker.removeEventListener('message', handler); } }); worker.postMessage({ type: 'RPC', method: method, id: _id, params: params }); }); }; })(); worker.addEventListener('message', function(response) { var data = response.data; if (data && data.type === 'interval' && callbacks[data.id]) { callbacks[data.id](); } }); return { set: function(fn, interval) { var interval_id = Date.now(); callbacks[interval_id] = fn; rpc('setInterval', [interval_id, interval]); return interval_id; }, clear: function(id) { delete callbacks[id]; return rpc('clearInterval', [id]); } }; })(); // ---------------------------------------------------------------------------------- var hidden, visibilityChange; if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support hidden = "hidden"; visibilityChange = "visibilitychange"; } else if (typeof document.msHidden !== "undefined") { hidden = "msHidden"; visibilityChange = "msvisibilitychange"; } else if (typeof document.webkitHidden !== "undefined") { hidden = "webkitHidden"; visibilityChange = "webkitvisibilitychange"; } // ---------------------------------------------------------------------------------- function warn(message) { if (console && console.warn) { console.warn(message); } else { setTimeout(function() { throw new Error(message); }, 0); } } // ---------------------------------------------------------------------------------- var ctx, c, link, icon, id, progress = 0, duration, initialized, settings, gif, type, initial_turns = -.25, interval_id, step; // ---------------------------------------------------------------------------------- function init(options) { if (document.readyState !== "complete") { setTimeout(init.bind(this, options), 100); return; } settings = Object.assign({ size: 16, radius: 6, thickness: 2, color: '#0F60A8', duration: 5000 }, options); if (!link) { link = document.querySelector('link[rel*="icon"]'); if (!link) { link = document.createElement('link'); link.setAttribute('rel', 'icon'); document.head.appendChild(link); warn("No default icon found, restore state will not work"); } else { icon = link.getAttribute('href'); type = link.getAttribute('type'); } } clear(); if (settings.gif) { if (typeof parseGIF === 'undefined') { throw new Error('parseGIF not defined, please include parseGIF.js file'); } parseGIF(settings.gif).then(function(data) { gif = data; }); } else { if (!c) { c = document.createElement('canvas'); } c.width = c.height = settings.size; ctx = c.getContext('2d'); ctx.lineCap = "round"; ctx.lineWidth = settings.thickness; ctx.strokeStyle = settings.color; duration = settings.duration; } initialized = true; } // ---------------------------------------------------------------------------------- function clear() { if (interval_id) { interval.clear(interval_id); interval_id = null; } } // ---------------------------------------------------------------------------------- function restore() { if (icon) { link.setAttribute('href', icon + '?' + Date.now()); link.setAttribute('type', type); } else if (link) { link.parentNode.removeChild(link); } clear(); } // ---------------------------------------------------------------------------------- function animate() { if (!initialized) { setTimeout(animate, 100); return; } if (interval_id) { return; } progress = 0; if (settings.gif && parseGIF) { if (!gif) { setTimeout(animate, 100); return; } interval_id = interval.set(animateGIF, 300); } else { interval_id = interval.set(draw, 300); } } // ---------------------------------------------------------------------------------- function turn(x) { return (x + initial_turns) * 2 * Math.PI; } // ---------------------------------------------------------------------------------- function animateGIF() { progress++; if (progress >= gif.uris.length) { progress = 0; } update(gif.uris[progress]); } // ---------------------------------------------------------------------------------- function arcStart(pos) { return turn(pos + initial_turns) + turn(Math.max(0, pos * 2 - 1)); } // ---------------------------------------------------------------------------------- function arcEnd(pos) { return turn(pos + initial_turns) + turn(Math.min(1, pos * 2)); } // ---------------------------------------------------------------------------------- function update(dataURI) { var newIcon, icon = document.querySelector('link[rel*="icon"]'); (newIcon = icon.cloneNode(true)).setAttribute('href', dataURI); icon.parentNode.replaceChild(newIcon, icon); link = newIcon; } // ---------------------------------------------------------------------------------- function draw() { ctx.clearRect(0, 0, settings.size, settings.size); if (typeof settings.frame === 'function') { settings.frame(ctx); } else { var position = progress % duration / duration; ctx.beginPath(); var center = Math.round(settings.size / 2); ctx.arc(center, center, settings.radius, arcStart(position), arcEnd(position)); ctx.stroke(); } update(ctx.canvas.toDataURL()); progress += duration / 100; } // ---------------------------------------------------------------------------------- return { init: init, start: animate, stop: restore, version: '0.4.4' }; });