Skip to content

Instantly share code, notes, and snippets.

@rossable
Last active September 14, 2024 00:08
Show Gist options
  • Save rossable/ddef353cf9a5cb6e1fb1bf6c18b0d654 to your computer and use it in GitHub Desktop.
Save rossable/ddef353cf9a5cb6e1fb1bf6c18b0d654 to your computer and use it in GitHub Desktop.
Modular SVG loading spinner for dynamic content areas
/* Patience JS
* Copyright 2016 Justin Ross https://www.linkedin.com/in/rossable/
* Last updated 9/2024
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// When needing to add a loading-spinner use "patience.spinIn(object-being-loaded);"
// After the object loads, stop the spinner with "patience.spinOut(object-being-loaded);"
// Accessibility note: Dynamically adds aria-live region to the object-being-loaded.
// the parent element gets styled with "position: relative;"
// customize the spinner
const spinnerColor = '#ff7900';
const areaMaskColor1 = 'rgba(100%,100%,100%,.9)';
const areaMaskColor2 = 'rgba(98%,98%,98%,.9)';
const patientSVGDesc = 'Please wait while the dynamic content is being loaded.';
// define the spinner
const patientSVG = '<svg role="img" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="40px" height="40px" viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve" aria-labelledby="patientSVGTitle patientSVGDesc"><title id="patientSVGTitle">Loading indicator</title><desc id="patientSVGDesc">'+patientSVGDesc+'</desc><g id="patientSVGspinner"><path fill="'+spinnerColor+'" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z"></path></g></svg>';
const patientStyles = '<style id="patience__styles" type="text/css" media="screen">.patinece__parent {position: relative;}@-webkit-keyframes spinner {0%{-webkit-transform:rotate(0deg);}25%{-webkit-transform:rotate(90deg);}50%{-webkit-transform:rotate(180deg);}75%{-webkit-transform:rotate(270deg);}100%{-webkit-transform:rotate(360deg);}}@keyframes spinner{0%{-ms-transform:rotate(0deg); transform:rotate(0deg);}25%{-ms-transform:rotate(90deg); transform:rotate(90deg);}50%{-ms-transform:rotate(180deg); transform:rotate(180deg);}75%{-ms-transform:rotate(270deg); transform:rotate(270deg);}100%{-ms-transform:rotate(360deg); transform:rotate(360deg);}}.loader {z-index:16777270;position:absolute;top:0;bottom:0;left:0;right:0;min-width:100%;min-height:100%;background: repeating-linear-gradient(-50deg,'+areaMaskColor1+','+areaMaskColor1+' 15px,'+areaMaskColor2+' 15px,'+areaMaskColor2+' 30px);display:grid;justify-content:center;align-content:center;}.loader svg path,.loader svg rect {fill:'+spinnerColor+';animation-duration:.7s;animation-iteration-count:infinite;animation-name:spinner;animation-timing-function:linear;-webkit-transform-origin:center center;-ms-transform-origin:center center;transform-origin:center center;}</style>';
class loadingSpinner {
constructor() {
const patientStylesID = document.getElementById('patience__styles');
const patinetHeader = document.querySelectorAll('head')[0];
// Inject CSS to HTML only once
if (!patientStylesID) patinetHeader.innerHTML += patientStyles;
}
spinIn = (loadingTarget) => {
let workingNode = loadingTarget[0];
let parent = workingNode.parentNode;
let parentID = parent.getAttribute('id') || 'loadingContainer';
let parentClass = parent.getAttribute('class');
let firstChild = parent.firstChild;
let spinner = document.createElement('div');
// Build the spinner node
spinner.setAttribute('class', 'loader');
spinner.setAttribute('title', 'loading');
spinner.setAttribute('aria-controls', parentID);
spinner.innerHTML = patientSVG;
// Add Accessibility to spinner parent node
parent.setAttribute('id', parentID);
if (parentClass) {
if (parentClass.includes('patinece__parent'));
else parent.setAttribute('class', parentClass + ' patinece__parent');
}
else parent.setAttribute('class', 'patinece__parent');
parent.setAttribute('role','region');
parent.setAttribute('aria-live','polite');
parent.setAttribute('aria-relevant','all');
parent.insertBefore(spinner,firstChild);
};
spinOut = (loadingTarget) => {
loadingTarget.siblings('.loader').remove();
};
}
let patience = new loadingSpinner();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment