webdevpuneet.com

Lazy-Loaded Carousel — HTML CSS JS Snippet

Lazy-Loaded Carousel · Carousels · Plain HTML, CSS & JS · Live preview

Copy & paste

Source Code

<div class="llc-wrap">
  <div class="llc-track" id="llcTrack">
    <div class="llc-slide" data-bg="#6366f1,#4338ca" data-icon="🎧"><div class="llc-placeholder"></div></div>
    <div class="llc-slide" data-bg="#0ea5e9,#0369a1" data-icon="📷"><div class="llc-placeholder"></div></div>
    <div class="llc-slide" data-bg="#ec4899,#9d174d" data-icon="⌚"><div class="llc-placeholder"></div></div>
    <div class="llc-slide" data-bg="#10b981,#047857" data-icon="🎮"><div class="llc-placeholder"></div></div>
    <div class="llc-slide" data-bg="#f59e0b,#b45309" data-icon="🔊"><div class="llc-placeholder"></div></div>
  </div>
  <div class="llc-controls">
    <button class="llc-btn" id="llcPrev" aria-label="Previous">‹</button>
    <div class="llc-dots" id="llcDots"></div>
    <button class="llc-btn" id="llcNext" aria-label="Next">›</button>
  </div>
  <p class="llc-log" id="llcLog">Loaded: none yet</p>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Slides only load their real content when genuinely about to be needed, not all upfront on page load
IntersectionObserver with an expanded rootMargin pre-loads the active slide's immediate neighbors ahead of time
A load-tracking map guarantees each slide is only ever loaded once, no matter how many times it's revisited
A visible placeholder-to-content swap and status log make the lazy-loading behavior observable, not just theoretical
The pattern generalizes directly to real <img> elements — swap the simulated delay for a real load event
Standard next/prev/dot navigation, unaffected by which slides have or haven't finished loading

About this UI Snippet

Lazy-Loaded Carousel — Loading Just Ahead of the Click, Not All Upfront

Screenshot of the Lazy-Loaded Carousel snippet rendered live

A carousel with ten heavy images usually loads all ten the instant the page loads, even though a visitor might only ever see the first two. This snippet defers that work: every slide starts as a plain gray placeholder, and its real content only loads the moment that slide is genuinely about to matter — either because it's the currently active one, or because it's sitting just outside the viewport where a viewer is about to scroll or click to it.

A generous rootMargin turns "about to be needed" into a real signal

The IntersectionObserver here uses rootMargin: '0px 100% 0px 100%' — expanding its effective viewport by a full container-width on both the left and right. That means a slide is reported as "intersecting" not just when it's literally on screen, but when it's within one carousel-width of being on screen — exactly capturing "the neighbor you're about to navigate to," so its load kicks off *before* you click next, not after.

A load-tracking map instead of re-fetching on every visit

loaded is a plain object keyed by slide index — ensureLoaded() checks it first and does nothing if that index has already started loading, even if the observer fires again for the same slide later (e.g. scrolling back and forth). That guard is what makes this safe to call from both the observer callback *and* directly from render() on every navigation, without ever double-fetching the same slide's content.

Simulated latency, standing in for a real fetch

fakeLoad() returns a Promise that resolves after a short randomized delay — a stand-in for a real <img>'s load event or a fetch call, deliberately visible enough that the placeholder-to-content swap reads as genuine asynchronous loading, not an instant synchronous render.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSThe first slide shows a brief gray placeholder, then loads in with its icon and color.
  2. 2
    Watch the log lineIt reports exactly which slide just finished loading, in real time.
  3. 3
    Click nextThe slide you land on is often already loaded, since its neighbor status triggered a pre-load earlier.
  4. 4
    Click through all fiveNotice each one only ever loads once, even if you navigate back and forth repeatedly.
  5. 5
    Swap in real imagesReplace fakeLoad's setTimeout with an actual <img> element whose src is set only inside ensureLoaded, resolving on its load event.

Real-world uses

Common Use Cases

Image-heavy product or portfolio carousels
Avoid loading every high-resolution image upfront when a visitor may only ever see the first couple.
Long testimonial or content carousels
Defer loading avatar images, embeds, or rich content until a slide is actually about to be seen.
Performance-sensitive landing pages
Cut initial page weight significantly on pages where a carousel is not the very first thing visible.
Learning the IntersectionObserver pre-load pattern
A clear example of rootMargin used to trigger work slightly before an element is actually visible.

Got questions?

Frequently Asked Questions

Replace the body of fakeLoad with code that creates or updates a real <img> element's src attribute and resolves the returned Promise on that image's load (and error) event, instead of a setTimeout — everything else in the pattern (the observer, the loaded map, ensureLoaded) works unchanged.

The default would only trigger a load once a slide is already visible — by the time a user clicks next, there'd be a visible loading delay. Expanding the observed area by a full container-width in each direction (100%) means a slide starts loading while it's still just off-screen, so it's often already loaded by the time it becomes active.

The loaded object is checked at the very top of ensureLoaded and set to true before the async load even starts — every subsequent call for that same index, whether from the observer firing again or from render(), sees it's already been claimed and does nothing further.

Yes — set root to the carousel's own scrollable ancestor (if the carousel sits inside its own scroll container) instead of null (the viewport) so the intersection calculation is relative to that container rather than the whole page.

Arrows and dots are real labeled buttons operable via Left/Right arrow keys; for real images, ensure meaningful alt text is set at the same time the src is set, so a screen reader announces accurate content once loading completes.