├── .gitignore ├── LICENSE.md ├── README.md ├── dist ├── emoji-favicon-toolkit.js ├── emoji-favicon-toolkit.min.js ├── index.html └── test-img-element.html ├── package.json ├── src └── emoji-favicon-toolkit.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright © 2017 [OFTN Inc.][1] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | [1]: https://oftn.org 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Emoji Favicon Toolkit 2 | 3 | This code only works in browsers that support ServiceWorker favicons, such as Chrome 72+. 4 | 5 | ## Usage 6 | 7 | 1. Download [/dist/emoji-favicon-toolkit.js](https://raw.githubusercontent.com/eligrey/emoji-favicon-toolkit/master/dist/emoji-favicon-toolkit.js) 8 | 2. Include `` 9 | 3. Don't put it in a webpack or rollup. 10 | 11 | ### Static usage 12 | 13 | 14 | 15 | ### API usage 16 | 17 | set_emoji_favicon(emoji="", cacheWithServiceWorker=false) 18 | 19 | -------------------------------------------------------------------------------- /dist/emoji-favicon-toolkit.js: -------------------------------------------------------------------------------- 1 | var set_emoji_favicon = (function () { 2 | var is_worker = !self.document; 3 | var mime_image = 'image/png'; 4 | if (is_worker) { 5 | // Assume we are running in a service worker. 6 | // Constants 7 | var cache_name_1 = 'favicon-cache'; 8 | var favicon_uri_1 = '/favicon.ico'; 9 | self.addEventListener('message', function (event) { 10 | var array_buffer = event.data; 11 | var faviconRequest = new Request(favicon_uri_1); 12 | var faviconResponse = new Response(array_buffer, { 13 | headers: { 14 | "Content-Type": mime_image, 15 | "Content-Length": String(array_buffer.byteLength) 16 | } 17 | }); 18 | caches.open(cache_name_1).then(function (cache) { 19 | cache.put(faviconRequest, faviconResponse); 20 | }); 21 | }); 22 | self.addEventListener('fetch', function (event) { 23 | event.respondWith(caches.open(cache_name_1).then(function (cache) { 24 | return cache.match(event.request).then(function (response) { 25 | if (response) { 26 | // Return the favicon stored in the cache 27 | return response; 28 | } 29 | else { 30 | // Perform an actual request to the server 31 | return fetch(event.request); 32 | } 33 | }); 34 | })); 35 | }); 36 | } 37 | else { 38 | // Assume we are running in the browser. 39 | // Window load promise 40 | var window_load_1 = new Promise(function (resolve) { 41 | window.addEventListener('load', resolve); 42 | }); 43 | // Constants 44 | var ns = 'http://www.w3.org/1999/xhtml'; 45 | var mime_text_regex_1 = /^\s*(?:text\/plain)\s*(?:$|;)/i; 46 | var size_1 = 256; // Anything larger will causes problems in Google Chrome 47 | var pixelgrid_1 = 16; 48 | var self_uri_1 = document.currentScript.getAttribute('src'); 49 | var service_worker_container_1 = navigator.serviceWorker; 50 | // Elements 51 | var canvas_1 = document.createElementNS(ns, 'canvas'); 52 | var link_1 = document.createElementNS(ns, 'link'); 53 | var context_1 = canvas_1.getContext('2d'); 54 | // Canvas setup 55 | canvas_1.width = canvas_1.height = size_1; 56 | context_1.font = "normal normal normal " + size_1 + "px/" + size_1 + "px sans-serif"; 57 | context_1.textAlign = 'center'; 58 | context_1.textBaseline = 'middle'; 59 | // Link setup 60 | link_1.rel = 'icon'; 61 | link_1.type = mime_image; 62 | link_1.setAttribute('sizes', size_1 + "x" + size_1); 63 | // Scan document for statically-defined favicons 64 | var lastlink = [].slice.call(document.getElementsByTagNameNS(ns, 'link'), 0).filter(function (link) { 65 | return link.rel.toLowerCase() === 'icon' && mime_text_regex_1.test(link.type); 66 | }).pop(); 67 | if (lastlink) { 68 | var xhr_1 = new XMLHttpRequest; 69 | var uri = lastlink.href.trim().replace(/^data:(;base64)?,/, "data:text/plain;charset=utf-8$1,"); 70 | xhr_1.open('GET', uri); 71 | xhr_1.addEventListener('load', function () { 72 | if (xhr_1.readyState === xhr_1.DONE && xhr_1.status === 200) { 73 | var emoji = xhr_1.responseText; 74 | set_emoji_favicon(emoji, false); 75 | } 76 | }); 77 | xhr_1.send(); 78 | } 79 | function set_emoji_favicon(emoji, cacheWithServiceWorker) { 80 | // Normalize arguments 81 | var char = String(emoji) || ''; 82 | var cache = Boolean(cacheWithServiceWorker); 83 | // Calculate sizing 84 | var metric = context_1.measureText(char); 85 | var iconsize = metric.width; 86 | var center = (size_1 + size_1 / pixelgrid_1) / 2; 87 | var scale = Math.min(size_1 / iconsize, 1); 88 | var center_scaled = center / scale; 89 | // Draw emoji 90 | context_1.clearRect(0, 0, size_1, size_1); 91 | context_1.save(); 92 | context_1.scale(scale, scale); 93 | context_1.fillText(char, center_scaled, center_scaled); 94 | context_1.restore(); 95 | // Update favicon element 96 | link_1.href = canvas_1.toDataURL(mime_image); 97 | document.getElementsByTagName('head')[0].appendChild(link_1); 98 | // Add favicon to cache 99 | if (cache && service_worker_container_1) { 100 | canvas_1.toBlob(function (blob) { 101 | var reader = new FileReader(); 102 | reader.addEventListener('loadend', function () { 103 | var array_buffer = reader.result; 104 | // https://developers.google.com/web/fundamentals/primers/service-workers/registration 105 | window_load_1.then(function () { 106 | service_worker_container_1.register(self_uri_1, { scope: '/' }); 107 | service_worker_container_1.ready.then(function (registration) { 108 | // https://developers.google.com/web/updates/2011/12/Transferable-Objects-Lightning-Fast 109 | registration.active.postMessage(array_buffer, [array_buffer]); 110 | }); 111 | }); 112 | }); 113 | reader.readAsArrayBuffer(blob); 114 | }, mime_image); 115 | } 116 | } 117 | return set_emoji_favicon; 118 | } 119 | })(); 120 | -------------------------------------------------------------------------------- /dist/emoji-favicon-toolkit.min.js: -------------------------------------------------------------------------------- 1 | var set_emoji_favicon=function(){function e(e,a){var r=String(e)||"",f=Boolean(a),h=u.measureText(r).width,m=(i+i/o)/2,p=Math.min(i/h,1),v=m/p;u.clearRect(0,0,i,i),u.save(),u.scale(p,p),u.fillText(r,v,v),u.restore(),l.href=d.toDataURL(t),document.getElementsByTagName("head")[0].appendChild(l),f&&c&&d.toBlob(function(e){var t=new FileReader;t.addEventListener("loadend",function(){var e=t.result;n.then(function(){c.register(s,{scope:"/"}),c.ready.then(function(t){t.active.postMessage(e,[e])})})}),t.readAsArrayBuffer(e)},t)}var t="image/png";if(!!self.document){var n=new Promise(function(e){window.addEventListener("load",e)}),a="http://www.w3.org/1999/xhtml",r=/^\s*(?:text\/plain)\s*(?:$|;)/i,i=256,o=16,s=document.currentScript.getAttribute("src"),c=navigator.serviceWorker,d=document.createElementNS(a,"canvas"),l=document.createElementNS(a,"link"),u=d.getContext("2d");d.width=d.height=i,u.font="normal normal normal "+i+"px/"+i+"px sans-serif",u.textAlign="center",u.textBaseline="middle",l.rel="icon",l.type=t,l.setAttribute("sizes",i+"x"+i);var f=[].slice.call(document.getElementsByTagNameNS(a,"link"),0).filter(function(e){return"icon"===e.rel.toLowerCase()&&r.test(e.type)}).pop();if(f){var h=new XMLHttpRequest,m=f.href.trim().replace(/^data:(;base64)?,/,"data:text/plain;charset=utf-8$1,");h.open("GET",m),h.addEventListener("load",function(){if(h.readyState===h.DONE&&200===h.status){e(h.responseText,!1)}}),h.send()}return e}var p="favicon-cache";self.addEventListener("message",function(e){var n=e.data,a=new Request("/favicon.ico"),r=new Response(n,{headers:{"Content-Type":t,"Content-Length":String(n.byteLength)}});caches.open(p).then(function(e){e.put(a,r)})}),self.addEventListener("fetch",function(e){e.respondWith(caches.open(p).then(function(t){return t.match(e.request).then(function(t){return t||fetch(e.request)})}))})}(); -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 |Lorem ipsum dolor sit amet consectetuer adipscing elit…
17 | Test loading
18 | /favicon.ico with
19 | <img>
element.
20 |
Lorem ipsum dolor sit amet consectetuer adipscing elit…
12 | 13 |