Pixelated Image Effect

A cursor or hover-based image effect that pixelates, reveals, or distorts visuals for digital, retro, gaming, and experimental interfaces.

Published On: June 4, 2026
Last Updated: August 17, 2026
Pixelated Image Effect

Overview

Pixelated Image turns pointer movement into atmosphere: images break into pixelated texture.

Use it on creative homepages, portfolios, campaign pages, and brand experiments where the cursor can carry the visual language. Keep it off dashboards, checkout, forms, and utility-heavy product screens.

In production, the risk is restraint. Treat the cursor layer as throwaway: switch it off on touch, keep focus states visible, and let nothing about clicking depend on it. If the page falls apart once you remove the effect, the cursor was doing work it should not own.


Install Command

npx hyperiux add pixelated-image-effect

Usage Code

page.jsx
import PixelatedImageEffect from "@/components/effects/pixelated-image-effect";

export default function Page() {
  return (
    <>
      <PixelatedImageEffect
        src="https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/v-02.jpg"
        alt="Pixelated nature scene"
      />
    </>
  );
}

Component Code

index.jsx
// Built using Hyperiux Vault: https://vault.hyperiux.com
"use client";
import { useEffect, useId, useRef, useState } from "react";
function usePrefersReducedMotion() {
    const [prefersReducedMotion, setPrefersReducedMotion] = useState(false);
    useEffect(() => {
        const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
        const update = () => setPrefersReducedMotion(mediaQuery.matches);
        update();
        mediaQuery.addEventListener("change", update);
        return () => mediaQuery.removeEventListener("change", update);
    }, []);
    return prefersReducedMotion;
}
export function PixelateSvgFilter({ id: idProp, size: propSize = 16, crossLayers = false }) {
    const generatedId = `pixelate-filter-${useId().replace(/:/g, "")}`;
    const id = idProp ?? generatedId;
    const size = Math.max(2, propSize);
    return (<svg aria-hidden="true" className="pointer-events-none absolute h-0 w-0 overflow-hidden">
      <defs>
        <filter id={id} x="0" y="0" width="1" height="1">
          {/* Base pixelation */}
          <feConvolveMatrix kernelMatrix="1 1 1
 1 1 1
 1 1 1" result="AVG"/>

          <feFlood x="1" y="1" width="1" height="1"/>

          <feComposite operator="arithmetic" k1="0" k2="1" k3="0" k4="0" width={size} height={size}/>

          <feTile result="TILE"/>

          <feComposite in="AVG" in2="TILE" operator="in"/>

          <feMorphology operator="dilate" radius={size / 2} result="NORMAL"/>

          {crossLayers && (<>
              {/* Horizontal fallback */}
              <feConvolveMatrix kernelMatrix="1 1 1
 1 1 1
 1 1 1" result="AVG"/>
              <feFlood x="1" y="1" width="1" height="1"/>
              <feComposite in2="SourceGraphic" operator="arithmetic" k1="0" k2="1" k3="0" k4="0" width={size / 2} height={size}/>
              <feTile result="TILE"/>
              <feComposite in="AVG" in2="TILE" operator="in"/>
              <feMorphology operator="dilate" radius={size / 2} result="FALLBACKX"/>

              {/* Vertical fallback */}
              <feConvolveMatrix kernelMatrix="1 1 1
 1 1 1
 1 1 1" result="AVG"/>
              <feFlood x="1" y="1" width="1" height="1"/>
              <feComposite in2="SourceGraphic" operator="arithmetic" k1="0" k2="1" k3="0" k4="0" width={size} height={size / 2}/>
              <feTile result="TILE"/>
              <feComposite in="AVG" in2="TILE" operator="in"/>
              <feMorphology operator="dilate" radius={size / 2} result="FALLBACKY"/>

              <feMerge>
                <feMergeNode in="FALLBACKX"/>
                <feMergeNode in="FALLBACKY"/>
                <feMergeNode in="NORMAL"/>
              </feMerge>
            </>)}

          {!crossLayers && <feMergeNode in="NORMAL"/>}
        </filter>
      </defs>
    </svg>);
}
export default function PixelatedImageEffect({ src = "https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/v-02.jpg", pixelSize = 16, duration = 0.25, mouseReactivity = 1, imageClassName = "", }) {
    const imageRef = useRef(null);
    const isTouching = useRef(false);
    const animationRef = useRef(null);
    const [renderedPixelSize, setRenderedPixelSize] = useState(pixelSize);
    const [targetPixelSize, setTargetPixelSize] = useState(pixelSize);
    const pixelateFilterId = `pixelate-filter-${useId().replace(/:/g, "")}`;
    const prefersReducedMotion = usePrefersReducedMotion();
    const fallbackSrc = "https://pub-8abee449136941f5b0a1cd2c014534e9.r2.dev/vault-listing-images/assets-images/v-02.jpg";
    const resolvedSrc = typeof src === "string" && src.trim() ? src.trim() : fallbackSrc;
    const resolvedPixelSize = Math.min(Math.max(Number(pixelSize) || 16, 1), 64);
    const resolvedDuration = Math.max(Number(duration) || 0, 0);
    const resolvedMouseReactivity = Math.min(Math.max(Number(mouseReactivity) || 0, 0), 2);
    useEffect(() => {
        setTargetPixelSize(resolvedPixelSize);
    }, [resolvedPixelSize]);
    useEffect(() => {
        if (animationRef.current !== null)
            cancelAnimationFrame(animationRef.current);
        if (resolvedDuration === 0) {
            setRenderedPixelSize(targetPixelSize);
            return undefined;
        }
        const startValue = renderedPixelSize;
        const distance = targetPixelSize - startValue;
        const startTime = performance.now();
        const durationMs = resolvedDuration * 1000;
        const animate = (time) => {
            const progress = Math.min((time - startTime) / durationMs, 1);
            const eased = 1 - Math.pow(1 - progress, 3);
            setRenderedPixelSize(startValue + distance * eased);
            if (progress < 1) {
                animationRef.current = requestAnimationFrame(animate);
            }
        };
        animationRef.current = requestAnimationFrame(animate);
        return () => {
            if (animationRef.current !== null)
                cancelAnimationFrame(animationRef.current);
        };
    }, [resolvedDuration, targetPixelSize]);
    const updatePixel = (event) => {
        if (!imageRef.current)
            return;
        const rect = imageRef.current.getBoundingClientRect();
        const x = event.clientX - rect.left;
        const pointerPixelSize = Math.min(Math.max(x / 30, 1), 64);
        const nextPixelSize = resolvedPixelSize + (pointerPixelSize - resolvedPixelSize) * resolvedMouseReactivity;
        setTargetPixelSize(Math.min(Math.max(nextPixelSize, 1), 64));
    };
    const handlePointerDown = (event) => {
        isTouching.current = true;
        updatePixel(event);
    };
    const handlePointerMove = (event) => {
        if (event.pointerType === "touch" && !isTouching.current)
            return;
        updatePixel(event);
    };
    const handlePointerUp = () => {
        isTouching.current = false;
    };
    return (<div className="relative flex h-dvh w-dvw pt-6 flex-col gap-15 items-center justify-center">

      <h1 className="text-5xl text-center max-[1025px]:hidden w-[45%] mt-10">
        Move cursor left in the image block for a clear image, right for more pixelation.
      </h1>

      <h2 className="hidden max-[1025px]:block  w-[60%] text-center mx-auto text-2xl">
        Click left in image block for clear image, right for more pixelated

      </h2>

      <PixelateSvgFilter id={pixelateFilterId} size={renderedPixelSize} crossLayers/>

      <div ref={imageRef} className="relative h-[55vh] w-full max-md:max-w-[90%] overflow-hidden max-[1025px]:max-w-[90%] max-w-lg touch-none" style={{ filter: `url(#${pixelateFilterId})` }} onPointerDown={handlePointerDown} onPointerMove={handlePointerMove} onPointerUp={handlePointerUp} onPointerLeave={handlePointerUp}>
        {/* eslint-disable-next-line @next/next/no-img-element */}
        <img src={resolvedSrc} alt="" className={`object-cover absolute inset-0 ${imageClassName}`.trim()}/>
      </div>
      {prefersReducedMotion && (<div aria-live="polite" className="pointer-events-none fixed bottom-4 right-4 z-40 w-fit max-w-65 rounded-md border border-white/15 bg-white/5 p-3 text-center  backdrop-blur-sm max-[1025px]:hidden">
          <h2 className="text-sm leading-none text-white">
            The pixels keep shifting.
          </h2>
          <p className="mt-2 text-xs leading-5 text-white/65">
            Pixelated Image Effect changes pixelation based on cursor
            position in real time. Since the transition is driven entirely by
            motion, reduced motion can&apos;t be applied here.
          </p>
        </div>)}
    </div>);
}

Example Production Use Case

Use this as cursor-system implementation guidance. Verify the shipped component export, pointer-tracking model, coarse-pointer disablement, z-index behavior, focus safety, cleanup, and reduced-motion handling before relying on exact props, defaults, imports, or installation steps.


Best Used For

  • Digital, gaming, and retro-technical pages where pixel texture belongs to the visual system.
  • Portfolio previews where pixelation adds character without obscuring the final image.
  • Pixelated Image gives pointer movement a brand role without hiding core controls or content.

Not For

Not for dashboards, checkout, forms, dense product UIs, or any flow where precision beats atmosphere.


Performance Budget

Use one cursor layer, throttle movement with requestAnimationFrame, avoid full-screen filter effects, and disable Pixelated Image on coarse pointers.


Accessibility and Mobile

Switch it off for coarse pointers and leave touch native; anything revealed by the cursor needs a visible or tap-accessible alternative.


Common Mistakes

  • Letting Pixelated Image cover focus rings or clickable controls.
  • Forgetting to disable the effect on coarse pointers.
  • Hiding useful content behind mouse-only movement.

Changelog

v1.1.0

Jul 22, 2026
reduce-motion applied

Props

PropTypeDefaultDescription
pixelSizenumber16Baseline pixel size for the SVG pixelation filter.
durationnumber0.25Smoothing duration for pixel-size changes.
mouseReactivitynumber1Strength of cursor-driven pixelation changes.

Frequently Asked Questions

When should I use Pixelated Image?

Use it when a technical or editorial page needs images breaking into pixelated texture on hover — not on utility flows.

Request a Custom Cursor Animation

Need a custom effect? Tell us what to create.