From e04ed80be59fa861855fb3d9471be9488caf25bf Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 22 May 2021 00:26:12 +0200 Subject: [PATCH] icon fonts support --- last-icon.js | 83 ++++++++++++++++++++++++++++++++++++++++-------- last-icon.min.js | 2 +- package.json | 2 +- readme.md | 62 ++++++++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 15 deletions(-) diff --git a/last-icon.js b/last-icon.js index 429f9ab..56ab919 100644 --- a/last-icon.js +++ b/last-icon.js @@ -14,10 +14,31 @@ const ALIASES = Object.assign( mi: "material", em: "emojicc", fl: "flags", - in: "iconoir", }, (window.LastIcon && window.LastIcon.aliases) || {} ); +const ENABLE_ICONS = (window.LastIcon && window.LastIcon.fonts) || []; +const FONT_ICONS = { + material: { + class: "material-icons-{type}", + types: { + baseline: "", + twotone: "two-tone", + }, + }, + boxicons: { + class: "bx {prefix}-{icon}", + }, + bootstrap: { + class: "bi-{icon}", + }, + fontawesome: { + class: "{prefix} fa-{icon}", + }, + iconoir: { + class: "iconoir-{icon}", + }, +}; const TYPES = Object.assign( { boxicons: "solid", @@ -34,6 +55,13 @@ const PREFIXES = Object.assign( regular: "bx", logos: "bxl", }, + fontawesome: { + solid: "fas", + regular: "far", + light: "fal", + duotone: "fad", + brands: "fab", + }, }, (window.LastIcon && window.LastIcon.prefixes) || {} ); @@ -46,7 +74,7 @@ const PATHS = Object.assign( boxicons: "https://cdn.jsdelivr.net/npm/boxicons@2.0.7/svg/{type}/{prefix}-{icon}.svg", cssgg: "https://cdn.jsdelivr.net/npm/css.gg@2.0.0/icons/svg/{icon}.svg", tabler: "https://cdn.jsdelivr.net/npm/@tabler/icons@1.41.2/icons/{icon}.svg", - // type: solid, regular, brands + // type: solid, regular, brands, light, duotone fontawesome: "https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.3/svgs/{type}/{icon}.svg", bytesize: "https://cdn.jsdelivr.net/npm/bytesize-icons@1.4.0/dist/icons/{icon}.svg", supertiny: "https://cdn.jsdelivr.net/npm/super-tiny-icons@0.4.0/images/svg/{icon}.svg", @@ -55,7 +83,6 @@ const PATHS = Object.assign( // type : 4x3 or 1x1 flags: "https://cdn.jsdelivr.net/npm/flag-svg-collection@1.1.0/flags/{type}/{icon}.svg", emojicc: "https://cdn.jsdelivr.net/npm/emoji-cc@1.0.1/svg/{icon}.svg", - iconoir: "https://cdn.jsdelivr.net/gh/lucaburgio/iconoir/icons/{icon}.svg" }, (window.LastIcon && window.LastIcon.paths) || {} ); @@ -80,7 +107,6 @@ class LastIcon extends HTMLElement { * @return {Promise} */ static getIconSvg(iconName, iconSet, iconType) { - let iconPrefix = (PREFIXES[iconSet] && PREFIXES[iconSet][iconType]) || null; let iconUrl = PATHS[iconSet]; let cacheKey = iconSet + "-" + iconName; if (iconType) { @@ -96,14 +122,7 @@ class LastIcon extends HTMLElement { }); } - // Replace placeholders - iconUrl = iconUrl.replace("{icon}", iconName); - if (iconType) { - iconUrl = iconUrl.replace("{type}", iconType); - } - if (iconPrefix) { - iconUrl = iconUrl.replace("{prefix}", iconPrefix); - } + iconUrl = LastIcon.replacePlaceholders(iconUrl, iconName, iconSet, iconType); // If we have it in cache if (iconUrl && CACHE[cacheKey]) { @@ -123,6 +142,28 @@ class LastIcon extends HTMLElement { return CACHE[cacheKey]; } + /** + * @param {string} value + * @param {string} iconName + * @param {string} iconSet + * @param {string} iconType + * @return {string} + */ + static replacePlaceholders(value, iconName, iconSet, iconType) { + let iconPrefix = (PREFIXES[iconSet] && PREFIXES[iconSet][iconType]) || null; + value = value.replace("{icon}", iconName); + if (iconType) { + value = value.replace("{type}", iconType); + } else { + // Maybe we want to remove the type like in material icons + value = value.replace("-{type}", ""); + } + if (iconPrefix) { + value = value.replace("{prefix}", iconPrefix); + } + return value; + } + /** * @param {object} inst * @param {string} iconName @@ -130,12 +171,28 @@ class LastIcon extends HTMLElement { * @param {string} iconType */ static refreshIcon(inst, iconName, iconSet, iconType) { + if (ENABLE_ICONS.includes(iconSet)) { + LastIcon.log("Using font for " + iconName); + let iconClass = FONT_ICONS[iconSet]["class"]; + let nameAsClass = iconClass.includes("{icon}"); + let fontType = iconType; + if (FONT_ICONS[iconSet]["types"] && iconType in FONT_ICONS[iconSet]["types"]) { + fontType = FONT_ICONS[iconSet]["types"][iconType]; + } + iconClass = LastIcon.replacePlaceholders(iconClass, iconName, iconSet, fontType); + if (nameAsClass) { + inst.innerHTML = ''; + } else { + inst.innerHTML = '' + iconName + ""; + } + return; + } LastIcon.getIconSvg(iconName, iconSet, iconType) .then((iconData) => { if (inst.stroke) { iconData = iconData.replace(/stroke-width="([0-9]*)"/, 'stroke-width="' + inst.stroke + '"'); } - if (FIX_FILL.indexOf(inst.set) !== -1) { + if (FIX_FILL.includes(inst.set)) { iconData = iconData.replace(/(/, '$1 fill="currentColor">'); } inst.innerHTML = iconData; diff --git a/last-icon.min.js b/last-icon.min.js index 9b00bd3..de74780 100644 --- a/last-icon.min.js +++ b/last-icon.min.js @@ -1 +1 @@ -const CACHE={},DEBUG=window.LastIcon&&window.LastIcon.debug||!1,PRELOAD=window.LastIconPreload||{},FIX_FILL=["material","boxicons","fontawesome"],ALIASES=Object.assign({bs:"bootstrap",bx:"boxicons",cs:"cssgg",gg:"cssgg",tb:"tabler",fa:"fontawesome",st:"supertiny",mi:"material",em:"emojicc",fl:"flags",in:"iconoir"},window.LastIcon&&window.LastIcon.aliases||{}),TYPES=Object.assign({boxicons:"solid",fontawesome:"solid",material:"baseline",flags:"4x3"},window.LastIcon&&window.LastIcon.types||{}),PREFIXES=Object.assign({boxicons:{solid:"bxs",regular:"bx",logos:"bxl"}},window.LastIcon&&window.LastIcon.prefixes||{}),DEFAULT_SET=window.LastIcon&&window.LastIcon.defaultSet||"tabler",DEFAULT_STROKE=window.LastIcon&&window.LastIcon.defaultStroke||2,PATHS=Object.assign({bootstrap:"https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/icons/{icon}.svg",boxicons:"https://cdn.jsdelivr.net/npm/boxicons@2.0.7/svg/{type}/{prefix}-{icon}.svg",cssgg:"https://cdn.jsdelivr.net/npm/css.gg@2.0.0/icons/svg/{icon}.svg",tabler:"https://cdn.jsdelivr.net/npm/@tabler/icons@1.41.2/icons/{icon}.svg",fontawesome:"https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.3/svgs/{type}/{icon}.svg",bytesize:"https://cdn.jsdelivr.net/npm/bytesize-icons@1.4.0/dist/icons/{icon}.svg",supertiny:"https://cdn.jsdelivr.net/npm/super-tiny-icons@0.4.0/images/svg/{icon}.svg",material:"https://cdn.jsdelivr.net/npm/@material-icons/svg@1.0.10/svg/{icon}/{type}.svg",flags:"https://cdn.jsdelivr.net/npm/flag-svg-collection@1.1.0/flags/{type}/{icon}.svg",emojicc:"https://cdn.jsdelivr.net/npm/emoji-cc@1.0.1/svg/{icon}.svg",iconoir:"https://cdn.jsdelivr.net/gh/lucaburgio/iconoir/icons/{icon}.svg"},window.LastIcon&&window.LastIcon.paths||{});class LastIcon extends HTMLElement{constructor(){super()}static log(t){DEBUG&&console.log("[l-i] "+t)}static getIconSvg(t,e,s){let n=PREFIXES[e]&&PREFIXES[e][s]||null,o=PATHS[e],i=e+"-"+t;return s&&(i+="-"+s),PRELOAD[i]?(LastIcon.log("Fetching "+i+" from preloaded cache"),PRELOAD[i]):o?(o=o.replace("{icon}",t),s&&(o=o.replace("{type}",s)),n&&(o=o.replace("{prefix}",n)),o&&CACHE[i]?(LastIcon.log("Fetching "+i+" from cache"),CACHE[i]):(LastIcon.log("Fetching "+i+" from url "+o),CACHE[i]=fetch(o).then(function(t){if(200===t.status)return t.text();throw Error(t.status)}),CACHE[i])):new Promise(()=>{console.error(`Icon set ${e} does not exists`)})}static refreshIcon(t,e,s,n){LastIcon.getIconSvg(e,s,n).then(e=>{t.stroke&&(e=e.replace(/stroke-width="([0-9]*)"/,'stroke-width="'+t.stroke+'"')),-1!==FIX_FILL.indexOf(t.set)&&(e=e.replace(/(/,'$1 fill="currentColor">')),t.innerHTML=e}).catch(s=>{t.innerHTML="⚠️",console.error(`Failed to load icon ${e} (error ${s})`)})}get type(){let t=this.getAttribute("type");return t=t||TYPES[this.set],t}get set(){let t=this.getAttribute("set")||DEFAULT_SET;return ALIASES[t]&&(t=ALIASES[t]),t}get stroke(){let t=this.getAttribute("stroke");return t||"tabler"!=this.set||(t=DEFAULT_STROKE),t}static get observedAttributes(){return["name"]}attributeChangedCallback(t,e,s){this.innerHTML="";let n=this.set,o=this.type;s&&LastIcon.refreshIcon(this,s,n,o)}connectedCallback(){}}customElements.define("l-i",LastIcon); \ No newline at end of file +const CACHE={},DEBUG=window.LastIcon&&window.LastIcon.debug||!1,PRELOAD=window.LastIconPreload||{},FIX_FILL=["material","boxicons","fontawesome"],ALIASES=Object.assign({bs:"bootstrap",bx:"boxicons",cs:"cssgg",gg:"cssgg",tb:"tabler",fa:"fontawesome",st:"supertiny",mi:"material",em:"emojicc",fl:"flags"},window.LastIcon&&window.LastIcon.aliases||{}),ENABLE_ICONS=window.LastIcon&&window.LastIcon.fonts||[],FONT_ICONS={material:{class:"material-icons-{type}",types:{baseline:"",twotone:"two-tone"}},boxicons:{class:"bx {prefix}-{icon}"},bootstrap:{class:"bi-{icon}"},fontawesome:{class:"{prefix} fa-{icon}"},iconoir:{class:"iconoir-{icon}"}},TYPES=Object.assign({boxicons:"solid",fontawesome:"solid",material:"baseline",flags:"4x3"},window.LastIcon&&window.LastIcon.types||{}),PREFIXES=Object.assign({boxicons:{solid:"bxs",regular:"bx",logos:"bxl"},fontawesome:{solid:"fas",regular:"far",light:"fal",duotone:"fad",brands:"fab"}},window.LastIcon&&window.LastIcon.prefixes||{}),DEFAULT_SET=window.LastIcon&&window.LastIcon.defaultSet||"tabler",DEFAULT_STROKE=window.LastIcon&&window.LastIcon.defaultStroke||2,PATHS=Object.assign({bootstrap:"https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/icons/{icon}.svg",boxicons:"https://cdn.jsdelivr.net/npm/boxicons@2.0.7/svg/{type}/{prefix}-{icon}.svg",cssgg:"https://cdn.jsdelivr.net/npm/css.gg@2.0.0/icons/svg/{icon}.svg",tabler:"https://cdn.jsdelivr.net/npm/@tabler/icons@1.41.2/icons/{icon}.svg",fontawesome:"https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.15.3/svgs/{type}/{icon}.svg",bytesize:"https://cdn.jsdelivr.net/npm/bytesize-icons@1.4.0/dist/icons/{icon}.svg",supertiny:"https://cdn.jsdelivr.net/npm/super-tiny-icons@0.4.0/images/svg/{icon}.svg",material:"https://cdn.jsdelivr.net/npm/@material-icons/svg@1.0.10/svg/{icon}/{type}.svg",flags:"https://cdn.jsdelivr.net/npm/flag-svg-collection@1.1.0/flags/{type}/{icon}.svg",emojicc:"https://cdn.jsdelivr.net/npm/emoji-cc@1.0.1/svg/{icon}.svg"},window.LastIcon&&window.LastIcon.paths||{});class LastIcon extends HTMLElement{constructor(){super()}static log(t){DEBUG&&console.log("[l-i] "+t)}static getIconSvg(t,e,s){let o=PATHS[e],n=e+"-"+t;return s&&(n+="-"+s),PRELOAD[n]?(LastIcon.log("Fetching "+n+" from preloaded cache"),PRELOAD[n]):o?(o=LastIcon.replacePlaceholders(o,t,e,s),o&&CACHE[n]?(LastIcon.log("Fetching "+n+" from cache"),CACHE[n]):(LastIcon.log("Fetching "+n+" from url "+o),CACHE[n]=fetch(o).then(function(t){if(200===t.status)return t.text();throw Error(t.status)}),CACHE[n])):new Promise(()=>{console.error(`Icon set ${e} does not exists`)})}static replacePlaceholders(t,e,s,o){let n=PREFIXES[s]&&PREFIXES[s][o]||null;return t=t.replace("{icon}",e),t=o?t.replace("{type}",o):t.replace("-{type}",""),n&&(t=t.replace("{prefix}",n)),t}static refreshIcon(t,e,s,o){if(ENABLE_ICONS.includes(s)){LastIcon.log("Using font for "+e);let n=FONT_ICONS[s].class,i=n.includes("{icon}"),c=o;return FONT_ICONS[s].types&&o in FONT_ICONS[s].types&&(c=FONT_ICONS[s].types[o]),n=LastIcon.replacePlaceholders(n,e,s,c),void(t.innerHTML=i?'':''+e+"")}LastIcon.getIconSvg(e,s,o).then(e=>{t.stroke&&(e=e.replace(/stroke-width="([0-9]*)"/,'stroke-width="'+t.stroke+'"')),FIX_FILL.includes(t.set)&&(e=e.replace(/(/,'$1 fill="currentColor">')),t.innerHTML=e}).catch(s=>{t.innerHTML="⚠️",console.error(`Failed to load icon ${e} (error ${s})`)})}get type(){let t=this.getAttribute("type");return t=t||TYPES[this.set],t}get set(){let t=this.getAttribute("set")||DEFAULT_SET;return ALIASES[t]&&(t=ALIASES[t]),t}get stroke(){let t=this.getAttribute("stroke");return t||"tabler"!=this.set||(t=DEFAULT_STROKE),t}static get observedAttributes(){return["name"]}attributeChangedCallback(t,e,s){this.innerHTML="";let o=this.set,n=this.type;s&&LastIcon.refreshIcon(this,s,o,n)}connectedCallback(){}}customElements.define("l-i",LastIcon); \ No newline at end of file diff --git a/package.json b/package.json index 72ba0e7..35256e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "last-icon", - "version": "1.1.0", + "version": "1.2.0", "description": "One custom icon element to rule them all", "main": "last-icon.js", "scripts": { diff --git a/readme.md b/readme.md index ccd5bf8..ab3f67b 100644 --- a/readme.md +++ b/readme.md @@ -135,6 +135,68 @@ window.LastIconPreload["material-account_box-twotone"] = LastIconPreloader("http Thanks to the debug flag, it's easy to find the cache key and the matching url. +## Using fonts + +If you find yourself preloading a lot of stuff... it might actually be easier to use the icon font instead. Indeed, it +is fully cached by the browser and will not have any display glitch. Obviously, the downside is that you have +to load the whole font, but it's cached after the first load. The advantage of using LastIcon over regular icons +is that is allows you to switch easily between one way or the other. + +First of all, load your fonts + +```html + + +``` + +And after that, use the font config to tell Last Icon to use the font over the svg icons + +```js +window.LastIcon = { + debug: true, + types: { + material: "twotone", + }, + fonts: ["material"] +}; +``` + +And then, update your styles: + +```css +l-i { + --size: 1em; + display: inline-flex; + width: var(--size); + height: var(--size); + vertical-align: middle; + + svg { + display: block; + width: 100%; + height: 100%; + } + i { + font-size: var(--size) !important; + color: currentColor; + } +} +p, +button, +a, +span { + l-i { + vertical-align: -0.125em; + } +} + +.material-icons-two-tone { + background-color: currentColor; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} +``` + ## Demo See demo.html or the following pen https://codepen.io/lekoalabe/pen/eYvdjqY