Logo Draw Loader
Your logo’s outline draws itself over a faint copy of the logo, holds for a beat, then wipes away in the same direction, on repeat. A branded loader for splash screens. Works with any logo drawn with strokes.
Loading your workspace…
How to use it
- Paste the snippet just before
</head>. - Wrap your logo’s inline
<svg>in a<div data-logo-draw-loader>, or put the attribute on the<svg>itself. - Its shapes need a
stroke(and usuallyfill="none").
Options
Add any of these attributes to the same element.
data-speed | Time to draw each stroke. Default 1100 ms. |
data-stagger | Gap between strokes. Default 160 ms. |
data-hold | How long the finished logo stays. Default 500 ms. |
data-fill | Shapes with a fill flood in once drawn, and empty again before the wipe. |
data-ghost="false" | No faint copy underneath. |
It only draws while it’s on screen, and is marked as a progress bar labelled “Loading” (set your own aria-label). With reduced motion the logo simply shows.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/bundles/anime.umd.min.js"></script>
<script>
(function () {
// Logo Draw Loader: your SVG logo's outline draws itself over a faint copy
// of the logo, holds for a beat, then wipes away in the same direction, on
// repeat. Works with any logo drawn with strokes.
// It loops while it's on screen.
// Usage: <div data-logo-draw-loader aria-label="Loading">
// <svg viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="3">…your logo…</svg>
// </div>
// (or put data-logo-draw-loader on the <svg> itself)
// Optional: data-speed (ms to draw each stroke, default 1100), data-stagger
// (ms between strokes, default 160), data-hold (ms the full logo stays,
// default 500), data-fill (shapes with a fill flood in once drawn and empty
// again before the wipe), data-ghost="false" (no faint copy underneath).
var DEFAULTS = { speed: 1100, stagger: 160, hold: 500 };
var SHAPES = 'path, line, polyline, polygon, circle, ellipse, rect';
var css = '.ldl-ghost{opacity:.14}';
var style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
function build(el) {
var d = el.dataset;
var svg = el.tagName.toLowerCase() === 'svg' ? el : el.querySelector('svg');
if (!svg) return null;
if (!el.getAttribute('role')) el.setAttribute('role', 'progressbar');
if (!el.getAttribute('aria-label')) el.setAttribute('aria-label', 'Loading');
if (svg !== el) svg.setAttribute('aria-hidden', 'true');
var shapes = Array.from(svg.querySelectorAll(SHAPES)).filter(function (s) { return !s.closest('defs, clipPath, mask'); });
if (!shapes.length || window.matchMedia('(prefers-reduced-motion: reduce)').matches) return null;
if (d.ghost !== 'false') {
// A faint copy of every stroke underneath, so the logo's shape is
// always there to be traced over.
shapes.forEach(function (s) {
var g = s.cloneNode(false);
g.setAttribute('class', ((s.getAttribute('class') || '') + ' ldl-ghost').trim());
g.setAttribute('aria-hidden', 'true');
s.parentNode.insertBefore(g, s);
});
}
var fill = d.fill !== undefined && d.fill !== 'false';
var filled = fill ? shapes.filter(function (s) { var f = getComputedStyle(s).fill; return f && f !== 'none'; }) : [];
filled.forEach(function (s) { s.style.fillOpacity = 0; });
var lines = anime.svg.createDrawable(shapes);
anime.utils.set(lines, { draw: '0 0' });
var speed = +d.speed || DEFAULTS.speed;
var stagger = d.stagger !== undefined ? +d.stagger : DEFAULTS.stagger;
var hold = d.hold !== undefined ? +d.hold : DEFAULTS.hold;
var tl = anime.createTimeline({ loop: true, loopDelay: 250, autoplay: false });
tl.add(lines, { draw: ['0 0', '0 1'], duration: speed, ease: 'inOut(2.5)', delay: anime.stagger(stagger) });
if (filled.length) {
tl.add(filled, { fillOpacity: [0, 1], duration: 450, ease: 'out(2)', delay: anime.stagger(60) }, '-=150');
tl.add(filled, { fillOpacity: 0, duration: 350, ease: 'in(2)' }, '+=' + hold);
}
// Wipe off the way it was drawn, quicker, so it reads as one motion.
tl.add(lines, { draw: ['0 1', '1 1'], duration: speed * 0.75, ease: 'inOut(2.5)', delay: anime.stagger(stagger * 0.6) },
filled.length ? '+=0' : '+=' + hold);
return tl;
}
function init() {
document.querySelectorAll('[data-logo-draw-loader]').forEach(function (el) {
if (el._ldl) return;
el._ldl = true;
var tl = build(el);
if (!tl) return;
// Only draw while visible, to save battery.
new IntersectionObserver(function (entries) {
entries[0].isIntersecting ? tl.play() : tl.pause();
}).observe(el);
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
else init();
})();
</script><div data-logo-draw-loader aria-label="Loading">
<svg viewBox="0 0 64 64" fill="none" stroke="#6a0dad" stroke-width="3.5" stroke-linecap="round">
<circle cx="32" cy="32" r="27"/>
<path d="M19.5 44.5 L32 17 L44.5 44.5"/>
</svg>
</div>