Oneindig scrollen met laadstand New
A project list that loads more as soon as you reach the bottom: skeleton rows while loading, a counter via aria-live, role="feed" with aria-busy and position and total per item. The Load more projects button stays for keyboard users and as fallback, and the end shows a clear final line with back to top. For projects, blogs and news with a finite list.
Projecten van Hendriks Bouw
6 van 24
Dit waren alle 24 projecten.
How to use it
- Paste the HTML where the scroll effects 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.
<!-- Oneindig scrollen met laadstand · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="ois">
<div class="ois__kop"><h3 class="ois__t">Projecten van Hendriks Bouw</h3><p class="ois__n" aria-live="polite">6 van 24</p></div>
<div class="ois__lijst" tabindex="0" aria-label="Projecten, scroll voor meer">
<div class="ois__feed" role="feed" aria-busy="false" aria-label="Projecten"></div>
<div class="ois__skel" hidden aria-hidden="true"><div class="ois__sk"><i></i><b></b></div><div class="ois__sk"><i></i><b></b></div></div>
<button class="ois__meer" type="button">Meer projecten laden</button>
<p class="ois__eind" hidden>Dit waren alle 24 projecten. <button class="ois__top" type="button">Terug naar boven</button></p>
</div>
</div>
</div>/* Oneindig scrollen met laadstand · NedDev · eigen werk. */
.ois{position:absolute;inset:0;display:flex;flex-direction:column;font-family:'Switzer',system-ui,sans-serif;color:#0a0a0a;background:#fff;text-align:left}
.ois *{box-sizing:border-box}
.ois__kop{display:flex;align-items:baseline;justify-content:space-between;gap:8px;padding:10px 14px 8px;border-bottom:1px solid rgba(10,10,10,.12)}
.ois__t{margin:0;font-size:14.5px;font-weight:700;letter-spacing:-.01em}
.ois__n{margin:0;font-size:12px;color:#4a4a4a;font-variant-numeric:tabular-nums;white-space:nowrap}
.ois__lijst{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:4px 14px 12px;scrollbar-width:thin;scrollbar-color:#0a0a0a transparent}
.ois__lijst:focus-visible{outline:2px solid #F5480F;outline-offset:-2px}
.ois__it{display:grid;grid-template-columns:52px 1fr auto;gap:10px;align-items:center;padding:7px 0;border-bottom:1px solid rgba(10,10,10,.08);border-radius:4px;animation:oisIn .4s ease both}
.ois__it:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
@keyframes oisIn{from{opacity:0;transform:translateY(6px)}}
.ois__im{width:52px;height:38px;border-radius:4px;object-fit:cover;display:block;background:#e6e3de}
.ois__it h4{margin:0;font-size:13px;font-weight:600;line-height:1.25}
.ois__it p{margin:1px 0 0;font-size:12px;color:#4a4a4a}
.ois__pr{font-size:12.5px;font-weight:600;font-variant-numeric:tabular-nums;white-space:nowrap}
.ois__sk{display:grid;grid-template-columns:52px 1fr;gap:10px;align-items:center;padding:7px 0}
.ois__sk i,.ois__sk b{display:block;border-radius:4px;background:linear-gradient(90deg,#ece9e4 30%,#f6f5f2 50%,#ece9e4 70%) 0 0/300% 100%;animation:oisSk 1.2s linear infinite}
.ois__sk i{height:38px}.ois__sk b{height:12px;width:70%}
@keyframes oisSk{to{background-position:-150% 0}}
.ois__skel[hidden]{display:none}
.ois__meer,.ois__top{min-height:32px;margin-top:10px;padding:0 12px;border:1px solid #0a0a0a;border-radius:4px;background:#fff;color:#0a0a0a;font:600 12.5px/1 'Switzer',system-ui,sans-serif;cursor:pointer}
.ois__meer:hover,.ois__top:hover{background:#0a0a0a;color:#fff}
.ois__meer:focus-visible,.ois__top:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.ois__meer[hidden]{display:none}
.ois__eind{margin:12px 0 0;font-size:12.5px;color:#3a3a3a}
.ois__top{margin:6px 0 0;display:block}
@media (prefers-reduced-motion:reduce){.ois__it{animation:none}.ois__sk i,.ois__sk b{animation:none}}// Oneindig scrollen met laadstand · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w = root.querySelector('.ois'), lijst = w.querySelector('.ois__lijst'), feed = w.querySelector('.ois__feed');
var skel = w.querySelector('.ois__skel'), meer = w.querySelector('.ois__meer'), eind = w.querySelector('.ois__eind'), n = w.querySelector('.ois__n');
var media = (function () { var s = document.createElement('a'); s.href = 'https://neddev.com/lab-media/'; return s.href; })();
var T = [['Badkamer', 'linkvoorbeeld-hover-1'], ['Hybride warmtepomp', 'linkvoorbeeld-hover-2'], ['Praktijkruimte', 'linkvoorbeeld-hover-3'], ['Receptie en wachtkamer', 'snap-onepager-1']];
var P = ['Nijmegen', 'Lent', 'Wijchen', 'Arnhem', 'Elst', 'Beuningen', 'Malden', 'Groesbeek', 'Druten', 'Huissen', 'Bemmel', 'Ubbergen'];
var MND = ['oktober', 'september', 'augustus', 'juli', 'juni', 'mei', 'april', 'maart', 'februari', 'januari'];
var TOT = 24, PER = 6, done = 0, busy = false;
function eur(v) { return '€ ' + String(v).replace(/\B(?=(\d{3})+(?!\d))/g, '.'); }
function add() {
var frag = document.createDocumentFragment();
for (var k = 0; k < PER && done < TOT; k++, done++) {
var t = T[done % T.length], a = document.createElement('article');
a.className = 'ois__it'; a.tabIndex = 0; a.setAttribute('aria-posinset', done + 1); a.setAttribute('aria-setsize', TOT);
var titel = t[0] + ', ' + P[done % P.length];
a.setAttribute('aria-label', titel);
a.innerHTML = '<img class="ois__im" src="' + media + t[1] + '.webp" alt="" width="800" height="436" loading="lazy"><div><h4>' + titel + '</h4><p>Opgeleverd ' + MND[Math.floor(done / 3) % MND.length] + ' 2026</p></div><span class="ois__pr">' + eur(8400 + ((done * 3779) % 31) * 600) + '</span>';
frag.appendChild(a);
}
feed.appendChild(frag);
n.textContent = done + ' van ' + TOT;
}
function load() {
if (busy || done >= TOT) return; busy = true;
feed.setAttribute('aria-busy', 'true'); skel.hidden = false; meer.hidden = true;
setTimeout(function () {
add(); skel.hidden = true; feed.setAttribute('aria-busy', 'false'); busy = false;
if (done >= TOT) { eind.hidden = false; } else { meer.hidden = false; }
}, 850);
}
add();
meer.addEventListener('click', load);
w.querySelector('.ois__top').addEventListener('click', function () { lijst.scrollTop = 0; feed.firstChild.focus({ preventScroll: true }); });
feed.addEventListener('keydown', function (e) {
var it = e.target.closest('.ois__it'); if (!it) return;
if (e.key === 'PageDown' && it.nextElementSibling) { e.preventDefault(); it.nextElementSibling.focus(); }
if (e.key === 'PageUp' && it.previousElementSibling) { e.preventDefault(); it.previousElementSibling.focus(); }
});
if ('IntersectionObserver' in window) new IntersectionObserver(function (e) { if (e[0].isIntersecting) load(); }, { root: lijst, rootMargin: '0px 0px 80px 0px' }).observe(meer);
}
init(document.querySelector('.jouw-container'));
More scroll effects
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 →