Skip to main content

gentleduck/lazy

Lightweight React library for lazy-loading images and components using IntersectionObserver.

Philosophy

gentleduck/lazy wraps React's lazy() and Suspense with loading skeletons, error boundaries, and IntersectionObserver support. Components and images load when they enter the viewport.


Features

  • Lazy loading for components and images.
  • Configurable IntersectionObserver options.
  • ARIA roles, live regions, focus management.
  • Placeholder support while content loads.
  • Hooks for custom behavior.

Installation


npm install @gentleduck/lazy

npm install @gentleduck/lazy

Loading diagram...

Usage

1) Lazy Component

The DuckLazyComponent defers rendering until its children enter the viewport.

import { DuckLazyComponent } from '@gentleduck/lazy'
 
function MyComponent() {
  return (
    <DuckLazyComponent options={{ rootMargin: '100px', threshold: 0.25 }}>
      <div>Content that will be lazily loaded</div>
    </DuckLazyComponent>
  )
}
import { DuckLazyComponent } from '@gentleduck/lazy'
 
function MyComponent() {
  return (
    <DuckLazyComponent options={{ rootMargin: '100px', threshold: 0.25 }}>
      <div>Content that will be lazily loaded</div>
    </DuckLazyComponent>
  )
}

Props

PropTypeDefaultDescription
optionsIntersectionObserverInit-IntersectionObserver options (rootMargin, threshold)
childrenReact.ReactNode-The lazy-loaded content (required)

2) Lazy Image

The DuckLazyImage supports placeholders, accessibility attributes, and Next.js next/image.

import { DuckLazyImage } from '@gentleduck/lazy'
 
function MyImageComponent() {
  return (
    <DuckLazyImage
      src="https://example.com/image.jpg"
      placeholder="https://example.com/placeholder.jpg"
      alt="A description of the image"
      width={400}
      height={300}
      options={{ rootMargin: '100px', threshold: 0.25 }}
    />
  )
}
import { DuckLazyImage } from '@gentleduck/lazy'
 
function MyImageComponent() {
  return (
    <DuckLazyImage
      src="https://example.com/image.jpg"
      placeholder="https://example.com/placeholder.jpg"
      alt="A description of the image"
      width={400}
      height={300}
      options={{ rootMargin: '100px', threshold: 0.25 }}
    />
  )
}

Props

PropTypeDefaultDescription
srcstring(required)URL of the image
placeholderstring-Placeholder URL while loading
altstring(required)Accessible description
widthnumber200Image width
heightnumber200Image height
optionsIntersectionObserverInit{ rootMargin: '200px', threshold: 0.1 }IntersectionObserver options
nextImageboolean-Enables Next.js next/image optimization

3) useLazyLoad Hook

Attach lazy-loading behavior to any element.

import { useLazyLoad } from '@gentleduck/lazy'
 
function MyComponent() {
  const { isVisible, ComponentRef } = useLazyLoad({
    rootMargin: '100px',
    threshold: 0.25,
  })
 
  return (
    <div ref={ComponentRef}>
      {isVisible ? <div>Visible content</div> : <div>Loading...</div>}
    </div>
  )
}
import { useLazyLoad } from '@gentleduck/lazy'
 
function MyComponent() {
  const { isVisible, ComponentRef } = useLazyLoad({
    rootMargin: '100px',
    threshold: 0.25,
  })
 
  return (
    <div ref={ComponentRef}>
      {isVisible ? <div>Visible content</div> : <div>Loading...</div>}
    </div>
  )
}

Returns

NameTypeDescription
isVisiblebooleanWhether the element is visible in the viewport
ComponentRefReact.RefObject<HTMLDivElement | null>Ref to attach to the observed element

4) useLazyImage Hook

Specialized hook for images: manages visibility + load state.

import { useLazyImage } from '@gentleduck/lazy'
 
function LazyImage({ src, placeholder }) {
  const { isLoaded, imageRef } = useLazyImage(src, {
    rootMargin: '100px',
    threshold: 0.25,
  })
 
  return (
    <div ref={imageRef}>
      {!isLoaded && <img src={placeholder} alt="Placeholder" />}
      {isLoaded && <img src={src} alt="Main Image" />}
    </div>
  )
}
import { useLazyImage } from '@gentleduck/lazy'
 
function LazyImage({ src, placeholder }) {
  const { isLoaded, imageRef } = useLazyImage(src, {
    rootMargin: '100px',
    threshold: 0.25,
  })
 
  return (
    <div ref={imageRef}>
      {!isLoaded && <img src={placeholder} alt="Placeholder" />}
      {isLoaded && <img src={src} alt="Main Image" />}
    </div>
  )
}

Returns

NameTypeDescription
isLoadedbooleanWhether the image has finished loading
imageRefReact.RefObject<HTMLImageElement | null>Ref to attach to the <img> element

DuckLazyImage Component (Detailed)

Optimized lazy image loader with placeholder + accessibility.

import { DuckLazyImage } from '@gentleduck/lazy'
 
function MyImageComponent() {
  return (
    <DuckLazyImage
      src="https://example.com/image.jpg"
      placeholder="https://example.com/placeholder.jpg"
      alt="Mountain view"
      width={400}
      height={300}
      options={{ rootMargin: '100px', threshold: 0.25 }}
    />
  )
}
import { DuckLazyImage } from '@gentleduck/lazy'
 
function MyImageComponent() {
  return (
    <DuckLazyImage
      src="https://example.com/image.jpg"
      placeholder="https://example.com/placeholder.jpg"
      alt="Mountain view"
      width={400}
      height={300}
      options={{ rootMargin: '100px', threshold: 0.25 }}
    />
  )
}

Integration with Next.js

Enable Next.js image optimization with nextImage.

<DuckLazyImage
  nextImage
  src="https://example.com/image.jpg"
  placeholder="https://example.com/placeholder.jpg"
  alt="Next.js optimized image"
  width={400}
  height={300}
/>
<DuckLazyImage
  nextImage
  src="https://example.com/image.jpg"
  placeholder="https://example.com/placeholder.jpg"
  alt="Next.js optimized image"
  width={400}
  height={300}
/>

Benefits:

  • Built-in Next.js optimization
  • Lazy loading via next/image

Integration with React

Works as a drop-in replacement for <img> in plain React apps.

<DuckLazyImage
  src="https://example.com/image.jpg"
  placeholder="https://example.com/placeholder.jpg"
  alt="React lazy image"
  width={400}
  height={300}
/>
<DuckLazyImage
  src="https://example.com/image.jpg"
  placeholder="https://example.com/placeholder.jpg"
  alt="React lazy image"
  width={400}
  height={300}
/>

Accessibility Features

  • aria-live="polite" - announces loading state changes via an <output> element.
  • aria-hidden - hides placeholders from assistive technology.