Laadpuntjes en golfloader in context New
Loading states where they actually occur: three dots inside a search field while branches are looked up by postcode (aria-busy, result via aria-live), a typing indicator in a chat and a wave line with wavy text for fetching availability. Each with screen-reader text; with reduced motion only a calm pulse.
Typ om te zoeken
Sanne typt een bericht
Beschikbaarheid laden
How to use it
- Paste the HTML where the components should appear.
- Add the CSS to your stylesheet. All classes are prefixed, so nothing clashes with your own styles.
- Add the JavaScript and call init() with the element that contains the HTML.
<!-- Laadpuntjes en golfloader in context · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="lpg">
<div class="lpg__rij lpg__zoek"><div class="lpg__veld"><label class="lpg__sro" for="lpg-pc">Zoek een vestiging op postcode</label><input class="lpg__in" id="lpg-pc" type="text" inputmode="numeric" placeholder="Bijv. 3511" autocomplete="off" aria-describedby="lpg-sr"><span class="lpg__dots" aria-hidden="true"><i></i><i></i><i></i></span></div><p class="lpg__sr" id="lpg-sr" aria-live="polite">Typ om te zoeken</p></div>
<div class="lpg__rij lpg__chat"><span class="lpg__av" aria-hidden="true">SV</span><div class="lpg__bel" role="status"><span class="lpg__typ" aria-hidden="true"><i></i><i></i><i></i></span><span class="lpg__msg"></span><span class="lpg__sro">Sanne typt een bericht</span></div></div>
<div class="lpg__rij lpg__golf" role="status"><svg class="lpg__wave" viewBox="0 0 96 24" aria-hidden="true"><path d="M0 12q7.5-9 15 0t15 0 15 0 15 0 15 0 15 0 15 0 15 0"/></svg><span class="lpg__txt" aria-hidden="true"></span><span class="lpg__sro">Beschikbaarheid laden</span></div>
</div>
</div>/* Laadpuntjes en golfloader in context · NedDev · eigen werk. */
.lpg{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;gap:12px;padding:12px max(18px,calc(50% - 170px));font-family:'Switzer',system-ui,sans-serif;color:#0a0a0a;background:#f4f3f1;text-align:left}
.lpg *{box-sizing:border-box}
.lpg__sro{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.lpg__rij{display:flex;align-items:center;gap:10px;min-height:36px}
.lpg__veld{position:relative;flex:none;width:min(190px,52%)}
.lpg__in{width:100%;min-height:38px;padding:0 40px 0 11px;border:1px solid #0a0a0a;border-radius:4px;background:#fff;color:#0a0a0a;font:500 13px/1 'Switzer',system-ui,sans-serif}
.lpg__in::placeholder{color:#6a6a6a}
.lpg__in:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.lpg__dots{position:absolute;right:11px;top:50%;transform:translateY(-50%);display:none;align-items:center;gap:4px;color:#0a0a0a}
.lpg__veld.is-busy .lpg__dots{display:flex}
.lpg__dots i,.lpg__typ i{width:5px;height:5px;border-radius:50%;background:currentColor;animation:lpgHop 1s ease-in-out infinite}
.lpg__dots i:nth-child(2),.lpg__typ i:nth-child(2){animation-delay:.15s}
.lpg__dots i:nth-child(3),.lpg__typ i:nth-child(3){animation-delay:.3s}
@keyframes lpgHop{0%,60%,100%{transform:translateY(0);opacity:.45}30%{transform:translateY(-5px);opacity:1}}
.lpg__sr{margin:0;font-size:12.5px;color:#3a3a3a}
.lpg__av{width:30px;height:30px;flex:none;border-radius:50%;display:grid;place-items:center;background:#0a0a0a;color:#fff;font-size:12px;font-weight:600}
.lpg__bel{position:relative;display:flex;align-items:center;min-height:34px;padding:0 13px;background:#fff;border:1px solid rgba(10,10,10,.12);border-radius:4px 8px 8px 8px;font-size:13px}
.lpg__typ{display:flex;gap:4px;color:#6a6a6a}
.lpg__msg{display:none}
.lpg__bel.is-msg .lpg__typ{display:none}
.lpg__bel.is-msg .lpg__msg{display:inline}
.lpg__wave{width:96px;height:24px;flex:none;overflow:hidden}
.lpg__wave path{fill:none;stroke:#F5480F;stroke-width:2.2;stroke-linecap:round;animation:lpgGolf 1.1s linear infinite}
@keyframes lpgGolf{to{transform:translateX(-30px)}}
.lpg__txt{font-size:13px;font-weight:500;white-space:pre}
.lpg__txt span{display:inline-block;animation:lpgLetter 1.4s ease-in-out infinite}
@keyframes lpgLetter{0%,40%,100%{transform:translateY(0)}20%{transform:translateY(-3px);color:#c43a0b}}
@media (prefers-reduced-motion:reduce){
.lpg__dots i,.lpg__typ i{animation:lpgPuls 1.6s ease-in-out infinite}
.lpg__wave path,.lpg__txt span{animation:none}
.lpg__wave{animation:lpgPuls 1.6s ease-in-out infinite}
}
@keyframes lpgPuls{50%{opacity:.35}}// Laadpuntjes en golfloader in context · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w = root.querySelector('.lpg'), veld = w.querySelector('.lpg__veld'), inp = w.querySelector('.lpg__in'), sr = w.querySelector('.lpg__sr');
var V = [['35', 'Utrecht Centrum en Utrecht Overvecht'], ['30', 'Rotterdam Kralingen'], ['10', 'Amsterdam Oost'], ['56', 'Eindhoven Strijp'], ['97', 'Groningen Zuid']], tz = 0;
inp.addEventListener('input', function () {
clearTimeout(tz); var q = inp.value.replace(/\D/g, '');
if (!q) { veld.classList.remove('is-busy'); inp.removeAttribute('aria-busy'); sr.textContent = 'Typ om te zoeken'; return; }
veld.classList.add('is-busy'); inp.setAttribute('aria-busy', 'true'); sr.textContent = 'Zoeken…';
tz = setTimeout(function () {
veld.classList.remove('is-busy'); inp.removeAttribute('aria-busy');
var hit = V.filter(function (v) { return q.slice(0, 2) === v[0] || v[0].indexOf(q) === 0; })[0];
sr.textContent = hit ? 'Dichtstbij: ' + hit[1] : 'Geen vestiging binnen 25 km';
}, 900);
});
var txt = w.querySelector('.lpg__txt'), s = 'Beschikbaarheid laden';
txt.innerHTML = s.split('').map(function (c, i) { return '<span style="animation-delay:' + (i * 0.05).toFixed(2) + 's">' + (c === ' ' ? ' ' : c) + '</span>'; }).join('');
var bel = w.querySelector('.lpg__bel'), msg = bel.querySelector('.lpg__msg'), sro = bel.querySelector('.lpg__sro');
var M = ['Ik kijk het even voor u na.', 'Dinsdag om 10:30 kan de monteur komen.'], mi = 0, vis = true;
if ('IntersectionObserver' in window) new IntersectionObserver(function (e) { vis = e[0].isIntersecting; }).observe(w);
setInterval(function () {
if (!vis) return;
if (bel.classList.contains('is-msg')) { bel.classList.remove('is-msg'); sro.textContent = 'Sanne typt een bericht'; }
else { msg.textContent = M[mi++ % M.length]; sro.textContent = ''; bel.classList.add('is-msg'); }
}, 2600);
}
init(document.querySelector('.jouw-container'));
More components
Seen something you like?
Add it to your selection and send it over. We build your site around it, in your brand.
Get in touch →