Carousel (Images)
Scroll through a set of slides with lazy-loaded images: only visible and adjacent slides load.
Live Demo
Watch the Network tab: images only request when they enter the visible window or its adjacent buffer.
Source
HTML + JavaScript
<div data-component="my-carousel" data-cloak>
<button data-action="prev" data-bind-attr="{ disabled: !canPrev }">‹</button>
<div class="viewport" data-bind-style="{ width: viewportWidth }">
<div class="track" data-bind-style="{ transform: trackTransform }" data-list="enrichedItems">
<template>
<div class="slide">
<img data-show="loaded" data-bind-attr="{ src: url, alt: label }">
<div data-show="!loaded">Loading…</div>
<div data-bind="label"></div>
</div>
</template>
</div>
</div>
<button data-action="next" data-bind-attr="{ disabled: !canNext }">›</button>
</div>
<script>
wildflower.component('my-carousel', {
state: {
offset: 0,
visibleCount: 3,
slideWidth: 160,
gap: 12,
items: [
{ url: 'https://picsum.photos/id/10/320/240', label: 'Slide 1' },
{ url: 'https://picsum.photos/id/20/320/240', label: 'Slide 2' },
{ url: 'https://picsum.photos/id/30/320/240', label: 'Slide 3' },
{ url: 'https://picsum.photos/id/40/320/240', label: 'Slide 4' },
{ url: 'https://picsum.photos/id/50/320/240', label: 'Slide 5' },
{ url: 'https://picsum.photos/id/60/320/240', label: 'Slide 6' }
]
},
computed: {
maxOffset() { return Math.max(0, this.items.length - this.visibleCount); },
canPrev() { return this.offset > 0; },
canNext() { return this.offset < this.maxOffset; },
viewportWidth() {
const n = this.visibleCount;
return (this.slideWidth * n + this.gap * (n - 1)) + 'px';
},
trackTransform() {
return 'translateX(-' + (this.offset * (this.slideWidth + this.gap)) + 'px)';
},
// Mark each slide 'loaded' if it's in the viewport OR one slot beyond each edge.
// The src binding only resolves to a real URL when loaded is true, so off-screen
// images never request.
enrichedItems() {
const start = this.offset - 1;
const end = this.offset + this.visibleCount;
return this.items.map((item, i) => ({
...item,
loaded: i >= start && i <= end
}));
}
},
prev() { if (this.canPrev) this.offset--; },
next() { if (this.canNext) this.offset++; }
});
</script>
Key Points
enrichedItemsis a computed array that augments each item with aloadedflag derived fromoffset. When offset changes, the flags recompute and images render in/out reactivelydata-show="loaded"on the<img>keeps the element hidden until it's time to load; the placeholder fills the slot in the meantimedata-bind-attrbinds the imagesrcreactively; an image outside the loaded window never has a src set, so the browser never fetches it- The adjacent buffer (
offset - 1andoffset + visibleCount) pre-fetches one slide beyond each edge, so images are ready before they slide into view trackTransformis a computed CSS transform; WF's reactive style binding handles the slide animation without manual DOM writes- The viewport is pinned to an exact multiple of
slideWidth + gapso no partial slide peeks in