Loading
Spinner/loader with HTML, CSS and SVG
The idea is to duplicate content, fill it with flare color, position it absolutely to main content and hide unneccesary stuff with aria-hidden="true"
.
I use symbol
s to reduce HTML size by reusing SVGs insude use
tags.
Tweak spinner animations inside <svg>
and colors inside CSS!
Sadly, @property
have no good support in Firefox (nightly build only) and Safari (supported since 27 Mar '23). So you can try against FF and Safari CSS properties with @supports
at-rule and simply animate bacgkround-color for text span.
<svg class="visuallyhidden"> <symbol id="icon-loader" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <path style="stroke: var(--stroke);" opacity="1" d="M13.5352 6.46475L15.3031 4.69678" stroke-width="1.3" stroke-linecap="square" stroke-linejoin="round"> <animate attributeName="opacity" values="1; 1; 1; 0.43; 0.1; 0; 0.1; 0.43; 1" keyTimes="0; 0.125; 0.25; 0.375; 0.5; 0.625; 0.75; 0.875; 1" dur="2000ms" calcMode="linear" repeatCount="indefinite"></animate> </path> <path style="stroke: var(--stroke);" opacity="0.43" d="M17.5 10H15" stroke-width="1.3" stroke-linecap="square" stroke-linejoin="round"> <animate attributeName="opacity" values="0.43; 1; 1; 1; 0.43; 0.1; 0; 0.1; 0.43" keyTimes="0; 0.125; 0.25; 0.375; 0.5; 0.625; 0.75; 0.875; 1" dur="2000ms" calcMode="linear" repeatCount="indefinite"></animate> </path> <path style="stroke: var(--stroke);" opacity="0.1" d="M15.3031 15.3031L13.5352 13.5352" stroke-width="1.3" stroke-linecap="square" stroke-linejoin="round"> <animate attributeName="opacity" values="0.1; 0.43; 1; 1; 1; 0.43; 0.1; 0; 0.1" keyTimes="0; 0.125; 0.25; 0.375; 0.5; 0.625; 0.75; 0.875; 1" dur="2000ms" calcMode="linear" repeatCount="indefinite"></animate> </path> <path style="stroke: var(--stroke);" opacity="0" d="M10 17.5V15" stroke-width="1.3" stroke-linecap="square" stroke-linejoin="round"> <animate attributeName="opacity" values="0; 0.1; 0.43; 1; 1; 1; 0.43; 0.1; 0" keyTimes="0; 0.125; 0.25; 0.375; 0.5; 0.625; 0.75; 0.875; 1" dur="2000ms" calcMode="linear" repeatCount="indefinite"></animate> </path> <path style="stroke: var(--stroke);" opacity="0.1" d="M4.69727 15.3031L6.46523 13.5352" stroke-width="1.3" stroke-linecap="square" stroke-linejoin="round"> <animate attributeName="opacity" values="0.1; 0; 0.1; 0.43; 1; 1; 1; 0.43; 0.1" keyTimes="0; 0.125; 0.25; 0.375; 0.5; 0.625; 0.75; 0.875; 1" dur="2000ms" calcMode="linear" repeatCount="indefinite"></animate> </path> <path style="stroke: var(--stroke);" opacity="0.43" d="M2.5 10H5" stroke-width="1.3" stroke-linecap="square" stroke-linejoin="round"> <animate attributeName="opacity" values="0.43; 0.1; 0; 0.1; 0.43; 1; 1; 1; 0.43" keyTimes="0; 0.125; 0.25; 0.375; 0.5; 0.625; 0.75; 0.875; 1" dur="2000ms" calcMode="linear" repeatCount="indefinite"></animate> </path> <path style="stroke: var(--stroke);" opacity="1" d="M4.69727 4.69678L6.46523 6.46475" stroke-width="1.3" stroke-linecap="square" stroke-linejoin="round"> <animate attributeName="opacity" values="1; 0.43; 0.1; 0; 0.1; 0.43; 1; 1; 1" keyTimes="0; 0.125; 0.25; 0.375; 0.5; 0.625; 0.75; 0.875; 1" dur="2000ms" calcMode="linear" repeatCount="indefinite"></animate> </path> <path style="stroke: var(--stroke);" opacity="1" d="M10 2.5V5" stroke-width="1.3" stroke-linecap="square" stroke-linejoin="round"> <animate attributeName="opacity" values="1; 1; 0.43; 0.1; 0; 0.1; 0.43; 1; 1" keyTimes="0; 0.125; 0.25; 0.375; 0.5; 0.625; 0.75; 0.875; 1" dur="2000ms" calcMode="linear" repeatCount="indefinite"></animate> </path> </symbol> </svg> <span class="loading"> <span class="loading__text">Loading</span> <svg class="loading__icon"> <use href="#icon-loader" /> </svg> <span class="loading__fake" aria-hidden="true"> <span class="loading__text">Loading</span> <svg class="loading__icon"> <use href="#icon-loader" /> </svg> </span> </span>
.loading { --color: 219deg 100% 56%; --flare-color: 186deg 90% 54%; display: inline-block; position: relative; width: max-content; height: max-content; --stroke: hsl(var(--color) / 1); color: var(--stroke); font-size: 4rem; font-weight: 500; &__icon { display: inline-block; width: 1em; aspect-ratio: 1; vertical-align: text-bottom; } &__fake { all: inherit; position: absolute; top: 0; left: 0; --stroke: hsl(var(--flare-color) / 1); color: var(--stroke); user-select: none; pointer-events: none; animation: inProgressFlare 6s ease-in 500ms infinite normal both; -webkit-mask-image: linear-gradient( 54deg, rgb(0, 0, 0, 0) var(--loading-flare-left-trail, -30%), rgb(0, 0, 0, 1) var(--loading-flare-left, -25%), rgb(0, 0, 0, 1) var(--loading-flare-right, -5%), rgb(0, 0, 0, 0) var(--loading-flare-right-trail, 0%), ); mask-image: linear-gradient( 54deg, rgb(0, 0, 0, 0) var(--loading-flare-left-trail, -30%), rgb(0, 0, 0, 1) var(--loading-flare-left, -25%), rgb(0, 0, 0, 1) var(--loading-flare-right, -5%), rgb(0, 0, 0, 0) var(--loading-flare-right-trail, 0%), ); } } @keyframes inProgressFlare { 0% { --loading-flare-left-trail: -30%; --loading-flare-left: -25%; --loading-flare-right: -5%; --loading-flare-right-trail: 0%; } 50%, 100% { --loading-flare-left-trail: 100%; --loading-flare-left: 105%; --loading-flare-right: 125%; --loading-flare-right-trail: 130%; } } @property --loading-flare-left-trail { syntax: "<percentage>"; inherits: false; initial-value: -30%; } @property --loading-flare-left { syntax: "<percentage>"; inherits: false; initial-value: -25%; } @property --loading-flare-right { syntax: "<percentage>"; inherits: false; initial-value: -5%; } @property --loading-flare-right-trail { syntax: "<percentage>"; inherits: false; initial-value: 0%; } @property --loading-dash-opacity { syntax: "<number>"; inherits: true; initial-value: 1; }