Last active
November 6, 2023 09:00
-
-
Save oliverthiele/d54a64ac7b821c4840c6ec6614128bd3 to your computer and use it in GitHub Desktop.
This JavaScript allows multiple icons stored in a sprite file to be used in the web page via LocalStorage. On the first call or when the file revision is changed, the file is downloaded from the server and stored in LocalStorage. So that the DOM is not unnecessarily extended with too many icons, a check is first made to see which icons are prese…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Version 3.0.0 | |
* | |
* process.env constants are set via webpack (https://gist.github.com/oliverthiele/6298902d5192aa86f24682a3afbd0d9f) | |
*/ | |
const assetsUrl = process.env.TYPO3_ASSETS_URL; | |
const revision = process.env.SVG_REVISION; | |
export function localStorageSVGs() { | |
// OLD: After each change to the sprite file, update the UnixTimeStamp | |
// let revision = 1698763252; | |
// NEW: The revision is automatically set via webpack | |
// console.log('Rev LS Sprites ' + revision); | |
// OLD: let file = '/typo3conf/ext/ot_febuild/Resources/Public/Assets/Website/Icons/Sprites.svg'; | |
// NEW: The asset path is automatically set via webpack | |
let file = `${assetsUrl}Website/Icons/Sprites.svg`; | |
// All icons with class ot-localstorage-icon and all icons from data-always-add-sprite-to-dom | |
// e.G. <div class="ot-local-storage-svgs d-none" data-always-add-sprite-to-dom="svg-feather-clock svg-fas-circle"></div> | |
let iconElements; | |
let svgData = ''; | |
const alwaysAddIconIds = new Set(); | |
/** | |
* Collect all used icon IDs on the page. | |
* Each icon is evaluated individually, so that e.g. the title can be displayed barrier-free. | |
*/ | |
function getIconElements() { | |
iconElements = document.querySelectorAll('.ot-localstorage-icon'); | |
// console.log('Icons with .ot-localstorage-icon'); | |
// console.log(iconElements); | |
return iconElements.length; | |
} | |
/** | |
* Load SVG sprite data from localStorage or the server if available | |
*/ | |
function getSpriteData() { | |
if ( | |
parseInt(localStorage.getItem('inlineSVGrev')) !== revision || | |
localStorage.getItem('inlineSVGdata') === null | |
) { | |
// console.log('Update Sprites.svg from Server'); | |
loadSvgSprites(); | |
} else { | |
// console.log('Use Sprites.svg from Localstorage version ' + revision); | |
svgData = localStorage.getItem('inlineSVGdata'); | |
executeAfterSvgDataLoaded(); | |
} | |
} | |
/** | |
* Function to replace the original <svg> with <use> | |
*/ | |
function replaceSVGWithUse() { | |
iconElements.forEach((iconElement) => { | |
const iconId = iconElement.getAttribute('data-svg-identifier'); | |
if (iconId) { | |
const useElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); | |
const id = iconElement.id; | |
// Extract existing CSS classes and attributes | |
const existingClasses = iconElement.getAttribute('class'); | |
const titleElement = iconElement.querySelector('title'); | |
const title = titleElement ? titleElement.textContent : null; | |
// Extract the existing desc tag | |
const descElement = iconElement.querySelector('desc'); | |
const desc = descElement ? descElement.textContent : null; | |
const ariaHidden = iconElement.getAttribute('aria-hidden'); | |
const ariaLabel = iconElement.getAttribute('aria-label'); | |
const ariaDescription = iconElement.getAttribute('aria-description'); | |
let ariaLabelledBy = ''; | |
let useTitleAndDescTag = false; | |
if (id !== '') { | |
useElement.setAttribute('id', id); | |
if (!!ariaHidden === false) { | |
// Set title tag | |
if (title) { | |
const titleElement = document.createElementNS('http://www.w3.org/2000/svg', 'title'); | |
titleElement.textContent = title; | |
titleElement.id = 'title-' + id; | |
useElement.appendChild(titleElement); | |
ariaLabelledBy = 'title-' + id; | |
} | |
// Set aria tag | |
if (desc) { | |
const descriptionElement = document.createElementNS('http://www.w3.org/2000/svg', 'description'); | |
descriptionElement.textContent = desc; | |
descriptionElement.id = 'desc-' + id; | |
useElement.appendChild(descriptionElement); | |
ariaLabelledBy = ariaLabelledBy + ' desc-' + id; | |
} | |
if (ariaLabelledBy !== '') { | |
useElement.setAttribute('aria-labelledby', ariaLabelledBy); | |
useTitleAndDescTag = true; | |
} | |
} | |
} | |
// Set aria attributes, if aria-hidden is false and title tag is not used | |
if (!!ariaHidden === true) { | |
useElement.setAttribute('aria-hidden', ariaHidden); | |
useElement.setAttribute('focusable', 'false'); | |
} else { | |
if (useTitleAndDescTag === false) { | |
// Set aria-label | |
if (ariaLabel !== '' && ariaLabel !== null) { | |
useElement.setAttribute('aria-label', ariaLabel); | |
} | |
// Set aria-description | |
if (ariaDescription !== '' && ariaDescription !== null) { | |
useElement.setAttribute('aria-description', ariaDescription); | |
} | |
} | |
} | |
if (existingClasses) { | |
useElement.setAttribute('class', existingClasses); | |
} | |
// Putting role="img" on the svg element ensures it is identified as a graphic. | |
// useElement.setAttribute('role', 'img'); | |
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use'); | |
use.setAttribute('href', `#${iconId}`); | |
// useElement.classList.add('debug-localstorage-icon'); // Add classes | |
useElement.appendChild(use); | |
iconElement.parentNode.replaceChild(useElement, iconElement); | |
} | |
}); | |
} | |
function getIconsToAlwaysAddToTheDom() { | |
const elements = document.querySelectorAll('.ot-local-storage-svgs'); | |
elements.forEach(element => { | |
const icons = element.getAttribute('data-always-add-sprite-to-dom'); | |
if (icons) { | |
icons.split(' ') | |
.filter(icon => icon.trim() !== '') // Filtere leere Einträge | |
.forEach(icon => alwaysAddIconIds.add(icon)); | |
} | |
}); | |
// console.log('SVGs added in method getIconsToAlwaysAddToTheDom'); | |
// console.log(alwaysAddIconIds); | |
return alwaysAddIconIds.size; | |
} | |
/** | |
* Function to load the sprite file from the server | |
*/ | |
function loadSvgSprites() { | |
try { | |
const request = new XMLHttpRequest(); | |
request.open('GET', file, true); | |
request.setRequestHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); | |
request.setRequestHeader('Pragma', 'no-cache'); | |
request.setRequestHeader('Expires', '0'); | |
request.onload = function () { | |
if (request.status >= 200 && request.status < 400) { | |
svgData = request.responseText; | |
localStorage.setItem('inlineSVGdata', svgData); | |
localStorage.setItem('inlineSVGrev', `${revision}`); | |
executeAfterSvgDataLoaded(); | |
} | |
}; | |
request.send(); | |
} catch (e) { | |
console.error('Error loading the sprites file: ' + e.message); | |
} | |
} | |
/** | |
* Function to execute after SVG data is confirmed to be in local storage | |
*/ | |
function executeAfterSvgDataLoaded() { | |
// Call the function to automatically generate the container divs | |
generateContainerDivs(); | |
} | |
// Function for automatic generation of container divs | |
function generateContainerDivs() { | |
// console.log('Create the container div'); | |
// Create the container div | |
const container = document.createElement('div'); | |
const svgContainer = document.createElement('svg'); | |
svgContainer.classList.add('d-none'); | |
svgContainer.setAttribute('aria-hidden', 'true'); | |
const usedIconIds = new Set(); | |
iconElements.forEach((iconElement) => { | |
const iconId = iconElement.getAttribute('data-svg-identifier'); | |
if (iconId) { | |
usedIconIds.add(iconId); | |
} | |
}); | |
// Add icons that should always be added to the DOM | |
alwaysAddIconIds.forEach(iconId => usedIconIds.add(iconId)); | |
// Paste only the icons from the sprites file that will be used on the page | |
const svgData = localStorage.getItem('inlineSVGdata'); | |
const foundIconIds = new Set(); | |
if (svgData) { | |
const parser = new DOMParser(); | |
const doc = parser.parseFromString(svgData, 'image/svg+xml'); | |
const symbols = doc.querySelectorAll('symbol'); | |
symbols.forEach((symbol) => { | |
const iconId = symbol.getAttribute('id'); | |
if (usedIconIds.has(iconId)) { | |
const clonedSymbol = symbol.cloneNode(true); | |
foundIconIds.add(iconId); | |
svgContainer.appendChild(clonedSymbol); | |
} | |
}); | |
// Check for missing IDs | |
usedIconIds.forEach((id) => { | |
if (!foundIconIds.has(id)) { | |
console.warn(`Icon ID not found in ${file}: ${id}`); | |
} | |
}); | |
} | |
container.appendChild(svgContainer); | |
// Add the container div after the body tag | |
document.body.insertAdjacentElement('afterbegin', container); | |
// Call the function to embed the icons | |
replaceSVGWithUse(); | |
} | |
//----------------------- | |
// Use SVGs with class .ot-localstorage-icon. | |
const countIcons = getIconElements(); | |
// Use e.g. "<div class="ot-local-storage-svgs d-none" data-always-add-sprite-to-dom="svg-bt-check svg-fab-facebook-square"></div>" | |
// to add these icons always to the DOM. This is helpful when the icons are added later via JS. | |
const countAlwaysAddIconIds = getIconsToAlwaysAddToTheDom(); | |
// console.log(`count 1: ${countIcons} || count 2: ${countAlwaysAddIconIds}`) | |
if (countIcons > 0 || countAlwaysAddIconIds > 0) { | |
// Get the sprite data from the LocalStorage or from the server | |
getSpriteData(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This JavaScript allows multiple icons stored in a sprite file to be used in the web page via LocalStorage. On the first call or when the file revision is changed, the file is downloaded from the server and stored in LocalStorage.
So that the DOM is not unnecessarily extended with too many icons, a check is first made to see which icons are present with the CSS class .ot-localstorage-icon.
Also, the JS is able to modify the SVG so that properties like aria-label, title and description, ... can be used for accessible SVGs.
This kind of SVG impementation also allows the icons to be colored with "fill".