We bouwen een toegankelijke carousel die niet automatisch draait. Gebruikers hebben volledige controle (pijltjes, toetsenbord, swipe) en zien een status (“Dia X van N”).
Plaats dit blok waar je de carousel wil tonen.
<section aria-label="Projecten">
<div id="carouselProjecten"
class="carousel slide"
data-bs-ride="false"
data-bs-interval="false"
data-bs-touch="true"
data-bs-keyboard="true"
aria-roledescription="carousel"
aria-label="Projecten carousel">
<!-- Indicators (klikbare bullets) -->
<div class="carousel-indicators">
<button type="button" data-bs-target="#carouselProjecten" data-bs-slide-to="0" class="active"
aria-current="true" aria-label="Dia 1"></button>
<button type="button" data-bs-target="#carouselProjecten" data-bs-slide-to="1" aria-label="Dia 2"></button>
<button type="button" data-bs-target="#carouselProjecten" data-bs-slide-to="2" aria-label="Dia 3"></button>
</div>
<div class="carousel-inner">
<!-- Slide 1 -->
<div class="carousel-item active">
<picture>
<source srcset="assets/img/proj1-1280.webp" type="image/webp">
<img src="assets/img/proj1-1280.jpg" class="d-block w-100" width="1280" height="720"
alt="Posterontwerp 4GT — kleurrijke typografie">
</picture>
<div class="carousel-caption d-none d-md-block caption-glass">
<h3 class="h5 mb-1">Poster 4GT</h3>
<p class="mb-0">Kleurstudie en typografie-experiment.</p>
</div>
</div>
<!-- Slide 2 -->
<div class="carousel-item">
<picture>
<source srcset="assets/img/proj2-1280.webp" type="image/webp">
<img src="assets/img/proj2-1280.jpg" class="d-block w-100" width="1280" height="720"
alt="Packaging mock-up — doosje met patroon" loading="lazy">
</picture>
<div class="carousel-caption d-none d-md-block caption-glass">
<h3 class="h5 mb-1">Packaging</h3>
<p class="mb-0">Kartonnen doosje met eigen patroon.</p>
</div>
</div>
<!-- Slide 3 -->
<div class="carousel-item">
<picture>
<source srcset="assets/img/proj3-1280.webp" type="image/webp">
<img src="assets/img/proj3-1280.jpg" class="d-block w-100" width="1280" height="720"
alt="Website lay-out — landing met hero en kaarten" loading="lazy">
</picture>
<div class="carousel-caption d-none d-md-block caption-glass">
<h3 class="h5 mb-1">Web lay-out</h3>
<p class="mb-0">Hero + kaarten, mobile-first.</p>
</div>
</div>
</div>
<!-- Controls -->
<button class="carousel-control-prev" type="button" data-bs-target="#carouselProjecten" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Vorige</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#carouselProjecten" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Volgende</span>
</button>
</div>
<!-- Status + optionele Afspelen/Pauzeren -->
<div class="d-flex align-items-center justify-content-between mt-2">
<p id="carouselProjectenStatus" class="m-0 small visually-hidden" aria-live="polite">Dia 1 van 3</p>
<button id="carouselProjectenToggle" type="button" class="btn btn-outline-primary btn-sm">Afspelen</button>
</div>
</section>
Voeg toe aan assets/css/custom.css (kleine tweaks, netjes subtiel):
/* Carousel kleurt mee met je tokens */
#carouselProjecten{
--bs-carousel-control-color: #fff;
--bs-carousel-control-opacity: .85;
--bs-carousel-indicator-active-bg: var(--brand-primary);
--bs-carousel-indicator-hit-area-height: 2rem;
}
/* Glasachtige caption voor leesbaarheid op foto */
.caption-glass{
background: color-mix(in oklab, var(--brand-bg), black 25%);
color: #fff;
border-radius: .5rem;
padding: .5rem .75rem;
display: inline-block;
backdrop-filter: saturate(140%) blur(6px);
}
Klein script in assets/js/main.js om de status te tonen en optioneel te laten afspelen.
document.addEventListener('DOMContentLoaded', () => {
const el = document.getElementById('carouselProjecten');
if (!el || !window.bootstrap) return;
const carousel = new bootstrap.Carousel(el, {
interval: false, // geen autoplay
ride: false,
touch: true,
keyboard: true,
wrap: true
});
const items = el.querySelectorAll('.carousel-item');
const status = document.getElementById('carouselProjectenStatus');
const toggle = document.getElementById('carouselProjectenToggle');
const updateStatus = (index) => {
if (status) status.textContent = `Dia ${index + 1} van ${items.length}`;
};
// init
let current = [...items].findIndex(i => i.classList.contains('active'));
updateStatus(current >= 0 ? current : 0);
el.addEventListener('slid.bs.carousel', (e) => {
const i = [...items].indexOf(e.relatedTarget);
updateStatus(i);
});
// Afspelen / Pauzeren (optioneel)
let playing = false;
if (toggle) {
toggle.addEventListener('click', () => {
playing = !playing;
if (playing) {
carousel._config.interval = 4000; // interval aanzetten
carousel.cycle();
toggle.textContent = 'Pauzeren';
toggle.setAttribute('aria-pressed', 'true');
} else {
carousel.pause();
carousel._config.interval = false;
toggle.textContent = 'Afspelen';
toggle.setAttribute('aria-pressed', 'false');
}
});
}
});
<picture> + webp; geef width/height mee → stabiele lay-out.loading="lazy" op níet-eerste dia’s.data-bs-ride="false" en interval: false.aria-live status, controls met Visually hidden-tekst.