Web InnoventixFreeCode

Tilt Glare Effect

Original · free

Image tilts in 3D with a moving glare on hover.

byWeb InnoventixReact + Tailwind
tiltglareimageeffect
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-tilt-glare.json
imgfx-tilt-glare.tsx
"use client";

import { useCallback, useRef, useState, type CSSProperties, type PointerEvent } from "react";

type TiltState = {
  rx: number;
  ry: number;
  gx: number;
  gy: number;
  active: boolean;
};

const MAX_TILT = 14;

export default function ImgfxTiltGlare() {
  const cardRef = useRef<HTMLDivElement>(null);
  const [tilt, setTilt] = useState<TiltState>({
    rx: 0,
    ry: 0,
    gx: 50,
    gy: 50,
    active: false,
  });

  const handleMove = useCallback((event: PointerEvent<HTMLDivElement>) => {
    const el = cardRef.current;
    if (!el) return;
    const rect = el.getBoundingClientRect();
    const px = (event.clientX - rect.left) / rect.width;
    const py = (event.clientY - rect.top) / rect.height;
    const clampedX = Math.min(Math.max(px, 0), 1);
    const clampedY = Math.min(Math.max(py, 0), 1);
    setTilt({
      rx: (0.5 - clampedY) * (MAX_TILT * 2),
      ry: (clampedX - 0.5) * (MAX_TILT * 2),
      gx: clampedX * 100,
      gy: clampedY * 100,
      active: true,
    });
  }, []);

  const handleEnter = useCallback(() => {
    setTilt((prev) => ({ ...prev, active: true }));
  }, []);

  const handleLeave = useCallback(() => {
    setTilt({ rx: 0, ry: 0, gx: 50, gy: 50, active: false });
  }, []);

  return (
    <section className="relative w-full overflow-hidden bg-slate-50 px-6 py-20 dark:bg-slate-950 sm:py-28">
      <style>{`
        .itg-scene {
          perspective: 1100px;
        }
        .itg-card {
          transform: rotateX(var(--itg-rx, 0deg)) rotateY(var(--itg-ry, 0deg)) scale(var(--itg-scale, 1));
          transition: transform 400ms cubic-bezier(0.22, 1, 0.36, 1);
          transform-style: preserve-3d;
          will-change: transform;
        }
        .itg-card.is-active {
          transition: transform 120ms ease-out;
        }
        .itg-glare {
          background: radial-gradient(
            circle at var(--itg-gx, 50%) var(--itg-gy, 50%),
            rgba(255, 255, 255, 0.55) 0%,
            rgba(255, 255, 255, 0.14) 26%,
            rgba(255, 255, 255, 0) 60%
          );
          opacity: 0;
          transition: opacity 400ms ease;
          mix-blend-mode: soft-light;
        }
        .itg-card.is-active .itg-glare {
          opacity: 1;
        }
        .itg-sheen {
          background: linear-gradient(
            105deg,
            rgba(255, 255, 255, 0) 40%,
            rgba(255, 255, 255, 0.32) 50%,
            rgba(255, 255, 255, 0) 60%
          );
          background-size: 220% 220%;
          background-position: calc(var(--itg-gx, 50%) * 1.4) center;
          opacity: 0;
          transition: opacity 400ms ease;
          mix-blend-mode: overlay;
        }
        .itg-card.is-active .itg-sheen {
          opacity: 1;
        }
        @media (prefers-reduced-motion: reduce) {
          .itg-card {
            transform: none !important;
            transition: none;
          }
          .itg-glare,
          .itg-sheen {
            opacity: 0 !important;
            transition: none;
          }
        }
      `}</style>

      <div className="mx-auto flex max-w-2xl flex-col items-center text-center">
        <p className="text-xs font-semibold uppercase tracking-[0.25em] text-indigo-600 dark:text-indigo-400">
          Image effect
        </p>
        <h2 className="mt-3 text-3xl font-bold tracking-tight text-slate-900 dark:text-slate-50 sm:text-4xl">
          3D Tilt &amp; Glare
        </h2>
        <p className="mt-3 text-sm text-slate-600 dark:text-slate-400">
          Move your pointer across the photo &mdash; it leans in 3D while a soft
          highlight tracks your cursor.
        </p>
      </div>

      <div className="itg-scene mt-14 flex justify-center">
        <div
          ref={cardRef}
          onPointerMove={handleMove}
          onPointerEnter={handleEnter}
          onPointerLeave={handleLeave}
          className={`itg-card group relative w-full max-w-md rounded-2xl${
            tilt.active ? " is-active" : ""
          }`}
          style={
            {
              "--itg-rx": `${tilt.rx}deg`,
              "--itg-ry": `${tilt.ry}deg`,
              "--itg-gx": `${tilt.gx}%`,
              "--itg-gy": `${tilt.gy}%`,
              "--itg-scale": tilt.active ? 1.03 : 1,
            } as CSSProperties
          }
        >
          <div className="relative overflow-hidden rounded-2xl bg-slate-200 shadow-xl shadow-slate-900/15 ring-1 ring-slate-900/10 dark:bg-slate-800 dark:shadow-black/40 dark:ring-white/10">
            {/* eslint-disable-next-line @next/next/no-img-element */}
            <img
              src="/img/gallery/g07.webp"
              alt="Sunlit architectural facade catching a moving highlight as it tilts"
              loading="lazy"
              draggable={false}
              className="block h-auto w-full select-none object-cover"
            />
            <div className="itg-glare pointer-events-none absolute inset-0 rounded-2xl" />
            <div className="itg-sheen pointer-events-none absolute inset-0 rounded-2xl" />
            <div className="pointer-events-none absolute inset-0 rounded-2xl ring-1 ring-inset ring-white/15" />
          </div>

          <div className="pointer-events-none mt-4 flex items-center justify-center gap-2 text-xs font-medium text-slate-500 dark:text-slate-400">
            <span className="inline-block h-1.5 w-1.5 rounded-full bg-indigo-500" />
            Hover to tilt
          </div>
        </div>
      </div>
    </section>
  );
}

Dependencies

None (React + Tailwind only).

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 →