Skip to content

Instantly share code, notes, and snippets.

@oliverthiele
Last active November 6, 2023 09:00
Show Gist options
  • Save oliverthiele/d54a64ac7b821c4840c6ec6614128bd3 to your computer and use it in GitHub Desktop.
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…
/**
* 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();
}
}
@oliverthiele
Copy link
Author

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".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment