Web InnoventixFreeCode

Text Mask Effect

Original · free

A large word is clipped to show the photo through the letters.

byWeb InnoventixReact + Tailwind
textmaskimageeffect
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-text-mask.json
imgfx-text-mask.tsx
"use client";

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

export default function ImgfxTextMask() {
  const reduceMotion = useReducedMotion();
  const rootRef = useRef<HTMLDivElement>(null);
  const inView = useInView(rootRef, { once: true, margin: "-15%" });

  return (
    <section className="relative w-full overflow-hidden bg-neutral-50 py-24 sm:py-32 dark:bg-neutral-950">
      <style>{`
        @keyframes imgfxTextMask_drift {
          0%   { background-position: 20% 50%; }
          50%  { background-position: 80% 50%; }
          100% { background-position: 20% 50%; }
        }
        @keyframes imgfxTextMask_shine {
          0%   { transform: translateX(-120%) skewX(-12deg); opacity: 0; }
          18%  { opacity: 1; }
          40%  { transform: translateX(120%) skewX(-12deg); opacity: 0; }
          100% { transform: translateX(120%) skewX(-12deg); opacity: 0; }
        }
        .imgfxTextMask_word {
          background-image: url('/img/gallery/g07.webp');
          background-size: 140% auto;
          background-position: 20% 50%;
          background-repeat: no-repeat;
          -webkit-background-clip: text;
          background-clip: text;
          color: transparent;
          -webkit-text-fill-color: transparent;
          animation: imgfxTextMask_drift 18s ease-in-out infinite;
        }
        .imgfxTextMask_shine {
          animation: imgfxTextMask_shine 6s ease-in-out infinite;
          animation-delay: 1.2s;
        }
        @media (prefers-reduced-motion: reduce) {
          .imgfxTextMask_word { animation: none; }
          .imgfxTextMask_shine { animation: none; opacity: 0; }
        }
      `}</style>

      {/* hidden source image for accessibility / preloading */}
      {/* eslint-disable-next-line @next/next/no-img-element */}
      <img
        src="/img/gallery/g07.webp"
        alt="Mountain ridge at golden hour, seen through the cut-out lettering"
        loading="lazy"
        draggable={false}
        aria-hidden="true"
        className="pointer-events-none absolute h-px w-px opacity-0"
      />

      <div ref={rootRef} className="mx-auto max-w-6xl px-6">
        <motion.div
          initial={reduceMotion ? false : { opacity: 0, y: 16 }}
          animate={inView ? { opacity: 1, y: 0 } : undefined}
          transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
          className="flex flex-col items-center text-center"
        >
          <span className="mb-6 inline-flex items-center gap-2 rounded-full border border-neutral-300 bg-white/60 px-4 py-1.5 text-xs font-medium uppercase tracking-[0.2em] text-neutral-600 backdrop-blur dark:border-neutral-700 dark:bg-neutral-900/60 dark:text-neutral-400">
            <span className="h-1.5 w-1.5 rounded-full bg-indigo-500" />
            Text Mask
          </span>

          <h2 className="text-3xl font-semibold tracking-tight text-neutral-900 sm:text-4xl dark:text-neutral-50">
            The photo lives inside the letters
          </h2>

          <p className="mt-4 max-w-md text-base text-neutral-600 dark:text-neutral-400">
            One word, clipped to reveal the image drifting slowly behind it.
          </p>

          <motion.div
            initial={reduceMotion ? false : { opacity: 0, scale: 0.97 }}
            animate={inView ? { opacity: 1, scale: 1 } : undefined}
            transition={{ duration: 0.8, delay: 0.15, ease: [0.22, 1, 0.36, 1] }}
            className="relative mt-14 w-full select-none"
          >
            <span
              className="imgfxTextMask_word block text-center font-black leading-[0.82] tracking-tighter"
              style={{ fontSize: "clamp(4.5rem, 20vw, 15rem)" }}
              aria-hidden="true"
            >
              WANDER
            </span>

            {/* moving highlight sweep, masked to the same word shape */}
            <span
              className="imgfxTextMask_shine pointer-events-none absolute inset-y-0 left-0 w-1/3 bg-gradient-to-r from-transparent via-white/25 to-transparent dark:via-white/15"
              aria-hidden="true"
            />
          </motion.div>

          <p className="mt-12 text-sm text-neutral-500 dark:text-neutral-500">
            Hover-free, GPU-light — pure <code className="rounded bg-neutral-200 px-1.5 py-0.5 text-[0.8em] text-neutral-700 dark:bg-neutral-800 dark:text-neutral-300">background-clip: text</code>.
          </p>
        </motion.div>
      </div>
    </section>
  );
}

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 →