Web InnoventixFreeCode

Reveal Fade Effect

Original · free

Image fades and rises in as it scrolls into view.

byWeb InnoventixReact + Tailwind
revealfadeimageeffect
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/imgfx-reveal-fade.json
imgfx-reveal-fade.tsx
"use client";

import { useRef } from "react";
import { motion, useInView, useReducedMotion } from "motion/react";

type Shot = {
  src: string;
  alt: string;
  caption: string;
};

const shots: Shot[] = [
  {
    src: "/img/gallery/g07.webp",
    alt: "Morning light spilling across a quiet studio workspace",
    caption: "First light",
  },
  {
    src: "/img/gallery/g14.webp",
    alt: "Architectural detail of a concrete stairwell curving upward",
    caption: "The ascent",
  },
  {
    src: "/img/gallery/g22.webp",
    alt: "Low sun catching the edge of a coastal cliff walk",
    caption: "Golden hour",
  },
];

export default function ImgfxRevealFade() {
  const reduceMotion = useReducedMotion();

  return (
    <section className="relative w-full overflow-hidden bg-neutral-50 px-6 py-24 sm:py-32 dark:bg-neutral-950">
      <style>{`
        @keyframes imgfxrf-sheen {
          0% { transform: translateX(-120%) skewX(-12deg); opacity: 0; }
          40% { opacity: 0.55; }
          100% { transform: translateX(220%) skewX(-12deg); opacity: 0; }
        }
        @media (prefers-reduced-motion: reduce) {
          .imgfxrf-sheen { animation: none !important; opacity: 0 !important; }
        }
      `}</style>

      <div className="mx-auto max-w-5xl">
        <div className="mb-16 max-w-xl">
          <p className="text-xs font-semibold uppercase tracking-[0.2em] text-indigo-600 dark:text-indigo-400">
            Reveal · Fade &amp; Rise
          </p>
          <h2 className="mt-3 text-3xl font-semibold tracking-tight text-neutral-900 sm:text-4xl dark:text-neutral-50">
            Images that arrive as you reach them
          </h2>
          <p className="mt-4 text-sm leading-relaxed text-neutral-600 dark:text-neutral-400">
            Scroll down slowly — each frame lifts into place and settles the moment it enters view.
          </p>
        </div>

        <div className="flex flex-col gap-20 sm:gap-28">
          {shots.map((shot, i) => (
            <RevealFrame key={shot.src} shot={shot} index={i} reduceMotion={reduceMotion} />
          ))}
        </div>
      </div>
    </section>
  );
}

function RevealFrame({
  shot,
  index,
  reduceMotion,
}: {
  shot: Shot;
  index: number;
  reduceMotion: boolean | null;
}) {
  const ref = useRef<HTMLDivElement>(null);
  const inView = useInView(ref, { once: true, margin: "-15% 0px -15% 0px" });
  const align = index % 2 === 0;

  const hidden = reduceMotion
    ? { opacity: 1, y: 0, filter: "blur(0px)" }
    : { opacity: 0, y: 48, filter: "blur(8px)" };
  const shown = { opacity: 1, y: 0, filter: "blur(0px)" };

  return (
    <motion.div
      ref={ref}
      initial={hidden}
      animate={inView ? shown : hidden}
      transition={{ duration: 0.85, ease: [0.22, 1, 0.36, 1] }}
      className={`group relative w-full ${align ? "sm:pr-16" : "sm:ml-auto sm:pl-16"} sm:max-w-3xl`}
    >
      <div className="relative overflow-hidden rounded-2xl bg-neutral-200 shadow-xl shadow-neutral-900/10 ring-1 ring-neutral-900/5 dark:bg-neutral-800 dark:shadow-black/40 dark:ring-white/10">
        {/* eslint-disable-next-line @next/next/no-img-element */}
        <img
          src={shot.src}
          alt={shot.alt}
          loading="lazy"
          draggable={false}
          className="h-full w-full object-cover transition-transform duration-[1200ms] ease-out group-hover:scale-[1.04]"
        />

        <div
          className="imgfxrf-sheen pointer-events-none absolute inset-y-0 left-0 w-1/3 bg-gradient-to-r from-transparent via-white/60 to-transparent"
          style={
            reduceMotion
              ? undefined
              : {
                  animation: inView
                    ? `imgfxrf-sheen 1.1s ${0.35 + index * 0.05}s ease-out both`
                    : undefined,
                }
          }
          aria-hidden="true"
        />

        <div className="pointer-events-none absolute inset-x-0 bottom-0 flex items-end justify-between gap-4 bg-gradient-to-t from-black/55 to-transparent p-5">
          <span className="text-sm font-medium text-white">{shot.caption}</span>
          <span className="text-[11px] font-semibold uppercase tracking-widest text-white/70">
            {String(index + 1).padStart(2, "0")}
          </span>
        </div>
      </div>
    </motion.div>
  );
}

Dependencies

motion

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →