Last active
September 14, 2024 00:08
-
-
Save rossable/ddef353cf9a5cb6e1fb1bf6c18b0d654 to your computer and use it in GitHub Desktop.
Modular SVG loading spinner for dynamic content areas
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
/* 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