Sora UI

Pixel Image Loader

A canvas-based image that resolves from a blocky, pixelated placeholder up to full sharpness as it loads. React component.

Made by Axyl

Scroll the panel into view to watch the image resolve from a coarse pixel grid to full sharpness.

Installation

File Structure

pixel-image-loader.tsx
use-prefers-reduced-motion.tsx

Usage

import { PixelImageLoader } from '@/components/sora-ui/effects/pixel-image-loader';

export default function Gallery() {
  return (
    <PixelImageLoader
      alt="Mountain range at sunrise"
      className="w-72 rounded-lg"
      src="/images/mountains.jpg"
    />
  );
}

Start the reveal as soon as the image finishes loading, instead of waiting for it to scroll into view:

<PixelImageLoader alt="..." src="/images/mountains.jpg" trigger="immediate" />

Customize how coarse the placeholder starts and how fast it steps toward full resolution — each number is a percent of full resolution, and the sequence always lands on a sharp 100:

<PixelImageLoader
  alt="..."
  initialDelay={150}
  src="/images/mountains.jpg"
  stepDuration={60}
  steps={[3, 10, 25, 100]}
/>

On a slow connection, src can take a while to arrive — until it does, the canvas is empty. If src points at an image CDN that exposes resizing through the URL (Unsplash, imgix, Cloudinary, Contentful, Shopify, and similar), autoPlaceholder is on by default and fetches a tiny, cheap variant automatically to draw as the first pixelated frame — no setup needed:

<PixelImageLoader
  alt="Mountain range at sunrise"
  src="https://images.unsplash.com/photo-.......?q=80&w=1600"
/>
// requests a w=20&q=10 variant of the same URL first, in parallel

For static assets that aren't resizable this way (a plain /images/mountains.jpg), auto-detection has nothing to rewrite — pass a tiny base64 placeholder instead (a 16px-wide downscale of the same image works well). It's drawn, pixelated, the instant the component mounts, since a data URI needs no network round trip, and takes priority over autoPlaceholder:

<PixelImageLoader
  alt="Mountain range at sunrise"
  placeholder="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAA..."
  src="/images/mountains.jpg"
/>

Either way, the reveal continues from that placeholder frame once src finishes loading. Turn auto-detection off entirely with autoPlaceholder={false}.

Props

PropTypeDefault
src?
string
-
alt?
string
-
placeholder?
string
-
autoPlaceholder?
boolean
true
steps?
number[]
[2, 5, 6, 8, 100]
initialDelay?
number
300
stepDuration?
number
80
trigger?
"immediate" | "inView"
"inView"
aspect?
"square" | "video" | "auto"
"square"
className?
string
-

When to Use

Use PixelImageLoader for hero images, gallery grids, or cards where you want the loading state itself to feel intentional instead of a generic skeleton or blur-up placeholder. It works well over slow connections or with large source images, since the blocky steps read as progress rather than a stall — pair it with placeholder so there's something on screen from the first frame, not just a gray box while src is in flight.

For small thumbnails or above-the-fold images that load near-instantly, the effect has little room to play out — a plain image or Skeleton is simpler.

Accessibility

The canvas carries role="img" and aria-label set from the alt prop, so screen readers announce the image the same way an <img> would, regardless of which pixelation step is currently drawn. When prefers-reduced-motion: reduce is set, no intermediate steps are drawn at all — the component jumps straight to the final sharp frame once the image loads.

Built by Axyl. A motion-first component registry for React.

Last updated: 7/26/2026