mazey-lazy-load-images
    Preparing search index...

    mazey-lazy-load-images

    NPM version License

    Render responsive, lazy-loaded image collections with titles, optional descriptions, CSS multi-column layouts, placeholders, visible error feedback, fallback images, and manual retries.

    npm install mazey-lazy-load-images
    

    Add a target element to the page:

    <div id="gallery"></div>
    

    Use mountLazyImageGallery() to render the gallery without creating a React component:

    import { mountLazyImageGallery } from "mazey-lazy-load-images";

    const gallery = mountLazyImageGallery("#gallery", {
    items: [
    {
    title: "Travel notes",
    description: "Images from the latest trip.",
    images: [
    {
    src: "https://example.com/harbor.jpg",
    alt: "Boats in the harbor at sunset",
    },
    ],
    },
    ],
    });

    The mount function accepts an Element or CSS selector. It throws when the selector is empty, invalid, or unmatched.

    The returned controller provides update(nextProps) and destroy(). update() replaces the complete gallery prop object, so include any callbacks and nondefault options that must remain active. destroy() is idempotent, but calling update() after destruction throws an error.

    Use LazyImageGallery directly when your application owns the React tree:

    import { LazyImageGallery } from "mazey-lazy-load-images";
    import type { GalleryItem } from "mazey-lazy-load-images";

    const items: GalleryItem[] = [
    {
    id: "travel",
    title: "Travel notes",
    description: "Images from the latest trip.",
    images: [
    "https://example.com/decorative.jpg",
    {
    id: "harbor",
    src: "https://example.com/harbor.jpg",
    alt: "Boats in the harbor at sunset",
    width: 1200,
    height: 800,
    placeholderSrc: "https://example.com/harbor-placeholder.jpg",
    fallbackSrc: "https://example.com/image-unavailable.jpg",
    },
    ],
    },
    ];

    export function Gallery() {
    return (
    <LazyImageGallery
    items={items}
    minColumnWidth="260px"
    gap="20px"
    onImageError={({ image, attempt, stage }) => {
    console.error(image.src, attempt, stage);
    }}
    />
    );
    }

    A string image URL is treated as decorative and receives alt="". Use an image object with alt for informative images. Provide width and height when possible so the gallery can reserve the correct aspect ratio before the image loads.

    For a bundler-free browser page, provide React 19 through an import map and load the package as an ES module:

    <div id="gallery"></div>

    <script type="importmap">
    {
    "imports": {
    "react": "https://esm.sh/react@19.2.8",
    "react/jsx-runtime": "https://esm.sh/react@19.2.8/jsx-runtime",
    "react-dom": "https://esm.sh/react-dom@19.2.8?external=react",
    "react-dom/client": "https://esm.sh/react-dom@19.2.8/client?external=react",
    "mazey-lazy-load-images": "https://esm.sh/mazey-lazy-load-images@2?external=react,react-dom"
    }
    }
    </script>

    <script type="module">
    import { mountLazyImageGallery } from "mazey-lazy-load-images";

    mountLazyImageGallery("#gallery", {
    items: [
    {
    title: "Browser example",
    images: ["https://example.com/image.jpg"],
    },
    ],
    });
    </script>

    Each images entry accepts a URL string or an object with these fields:

    • src: Final image URL. Required for object entries.
    • alt: Alternative text. Defaults to an empty string.
    • id: Stable key for updates and reordering.
    • width and height: Intrinsic dimensions used to reserve space.
    • srcSet and sizes: Responsive image attributes.
    • placeholderSrc: Optional blurred image shown during the final request.
    • fallbackSrc: Optional image shown behind the failure message.
    • fetchPriority: Browser fetch priority: high, low, or auto.

    Gallery-level defaultPlaceholderSrc and defaultFallbackSrc values apply when an image does not define its own value. Keep placeholder files small because each placeholder triggers a separate image request.

    LazyImageGallery accepts these behavior and presentation props:

    • rootMargin: Intersection Observer preload margin. Defaults to 300px 0px.
    • minColumnWidth: Preferred CSS column width. Defaults to 240px.
    • gap: Space between image tiles. Defaults to 16px.
    • defaultAspectRatio: Reserved ratio when dimensions are absent. Defaults to 4 / 3.
    • labels: Overrides the loading, error, and retry text.
    • className and style: Extend the gallery root.
    • unstyled: Omits the built-in stylesheet.
    • onImageLoad and onImageError: Receive the normalized image, item and image indexes, attempt number, and source or fallback stage.
    • onImageClick: Optional callback for clicks on a successfully loaded source image. Receives its normalized image, item and image indexes, and the React mouse event.

    The component creates one IntersectionObserver per gallery. When Intersection Observer is unavailable, it assigns all final image sources immediately and retains native loading="lazy" behavior.

    Pass onImageClick when your application needs to respond to a loaded image being clicked:

    <LazyImageGallery
    items={items}
    onImageClick={({ image, itemIndex, imageIndex }, event) => {
    console.log(
    image.src,
    itemIndex,
    imageIndex,
    event.currentTarget.currentSrc,
    );
    }}
    />

    The callback does not run for images that are still loading or have failed, placeholders, fallbacks, or Retry buttons. It does not include the load/error callback's attempt or stage fields. mountLazyImageGallery() accepts the same prop; include it again in update(nextProps) to keep it active.

    This is a pointer-click hook, not an accessible activation control. The gallery does not add button semantics, keyboard handling, focusability, a pointer cursor, or a default action. If clicking an image opens a detail view, provide a separate keyboard-operable, visibly focusable control with a meaningful accessible name in your application.

    The React 19 component inserts and deduplicates its namespaced stylesheet. Set unstyled to true to provide all styles yourself.

    Override the built-in CSS custom properties from className or style:

    .photo-library {
    --mlli-column-width: 280px;
    --mlli-gap: 24px;
    --mlli-radius: 12px;
    --mlli-background: #e2e8f0;
    --mlli-foreground: #0f172a;
    --mlli-muted: #475569;
    --mlli-error-background: #fff1f2;
    --mlli-error-foreground: #9f1239;
    --mlli-button-background: #0f172a;
    --mlli-button-foreground: #ffffff;
    }

    The built-in animation respects prefers-reduced-motion.

    LazyImageGallery does not access browser globals during module import or server rendering. Server output contains the collection structure, reserved tiles, and default stylesheet. Image observation and source assignment begin after the component mounts in a browser.

    mountLazyImageGallery() is browser-only and throws when no browser document is available.

    Version 2 removes lazyLoadImages({ images, container, defaultImg }). Replace the old group fields and imperative call:

    lazyLoadImages({
    images: [{ name: "Example", img: ["/one.jpg"] }],
    container: "#gallery",
    defaultImg: "/placeholder.jpg",
    });

    with the v2 mount API:

    mountLazyImageGallery("#gallery", {
    items: [
    {
    title: "Example",
    images: ["/one.jpg"],
    },
    ],
    defaultPlaceholderSrc: "/placeholder.jpg",
    });

    Version 2 also removes Mazey, global scroll and resize listeners, document-wide image queries, innerHTML rendering, and the boolean initialization result. Keep the returned controller and call destroy() when another system removes the mounted page region.

    The package targets current Chrome, Edge, Firefox, and Safari releases. CSS multi-columns provide the waterfall layout. Browsers without Intersection Observer load the collection through the native image loading behavior.

    The package does not fetch item data, paginate collections, implement infinite scrolling, or virtualize the DOM.

    npm install
    npm run typecheck
    npm run lint
    npm test
    npm run build
    npm run docs

    Run the complete local verification pipeline with:

    npm run preview
    npm pack --dry-run