Web InnoventixFreeCode

Frame Corners Effect

Original · free

Animated corner brackets draw in around the image on hover.

byWeb InnoventixReact + Tailwind
framecornersimageeffect
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-frame-corners.json
imgfx-frame-corners.tsx
"use client";

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

export default function ImgfxFrameCorners() {
  const reduce = useReducedMotion();
  const ref = useRef<HTMLDivElement>(null);
  const inView = useInView(ref, { once: true, amount: 0.35 });

  return (
    <section className="relative w-full overflow-hidden bg-neutral-50 py-24 px-6 text-neutral-900 dark:bg-neutral-950 dark:text-neutral-100 sm:py-28">
      <style>{`
        @keyframes ifc-rise {
          from { opacity: 0; transform: translateY(24px); }
          to   { opacity: 1; transform: translateY(0); }
        }
        .ifc-frame .ifc-h,
        .ifc-frame .ifc-v {
          transition: transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);
        }
        .ifc-frame .ifc-h { transform: scaleX(0); }
        .ifc-frame .ifc-v { transform: scaleY(0); }
        .ifc-frame:hover .ifc-h,
        .ifc-frame:focus-visible .ifc-h { transform: scaleX(1); }
        .ifc-frame:hover .ifc-v,
        .ifc-frame:focus-visible .ifc-v { transform: scaleY(1); }
        .ifc-frame .ifc-photo {
          transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1),
            filter 0.55s ease;
          filter: saturate(0.9);
        }
        .ifc-frame:hover .ifc-photo,
        .ifc-frame:focus-visible .ifc-photo {
          transform: scale(1.035);
          filter: saturate(1.05);
        }
        .ifc-frame .ifc-tag {
          transition: opacity 0.45s ease 0.15s, transform 0.45s ease 0.15s;
          opacity: 0;
          transform: translateY(6px);
        }
        .ifc-frame:hover .ifc-tag,
        .ifc-frame:focus-visible .ifc-tag {
          opacity: 1;
          transform: translateY(0);
        }
        @media (prefers-reduced-motion: reduce) {
          .ifc-frame .ifc-h,
          .ifc-frame .ifc-v,
          .ifc-frame .ifc-photo,
          .ifc-frame .ifc-tag {
            transition: none !important;
            transform: none !important;
            opacity: 1 !important;
          }
        }
      `}</style>

      <motion.div
        ref={ref}
        initial={reduce ? false : { opacity: 0, y: 24 }}
        animate={inView && !reduce ? { opacity: 1, y: 0 } : undefined}
        transition={{ duration: 0.6, ease: [0.22, 1, 0.36, 1] }}
        className="mx-auto max-w-3xl"
      >
        <p className="text-xs font-semibold uppercase tracking-[0.25em] text-indigo-600 dark:text-indigo-400">
          Frame · Corners
        </p>
        <h2 className="mt-3 text-3xl font-semibold tracking-tight sm:text-4xl">
          Brackets that snap to attention
        </h2>
        <p className="mt-3 max-w-xl text-sm leading-relaxed text-neutral-600 dark:text-neutral-400">
          A precise corner frame draws itself in around the photo, the way a
          viewfinder locks onto its subject.
        </p>

        <div
          tabIndex={0}
          role="img"
          aria-label="Architectural facade at golden hour, framed by animated corner brackets on hover"
          className="ifc-frame group relative mt-10 aspect-[4/3] w-full cursor-pointer overflow-hidden rounded-xl bg-neutral-200 outline-none ring-offset-2 ring-offset-neutral-50 focus-visible:ring-2 focus-visible:ring-indigo-500 dark:bg-neutral-800 dark:ring-offset-neutral-950"
        >
          {/* eslint-disable-next-line @next/next/no-img-element */}
          <img
            src="/img/gallery/g07.webp"
            alt="Architectural facade catching warm directional light"
            loading="lazy"
            draggable={false}
            className="ifc-photo h-full w-full object-cover"
          />

          <span
            aria-hidden="true"
            className="pointer-events-none absolute inset-0 bg-gradient-to-t from-neutral-950/45 via-transparent to-transparent"
          />

          {/* corner brackets */}
          <span aria-hidden="true" className="pointer-events-none absolute inset-4 sm:inset-5">
            {/* top-left */}
            <span className="ifc-h absolute left-0 top-0 h-[2.5px] w-9 origin-left rounded-full bg-indigo-500 dark:bg-indigo-400" />
            <span className="ifc-v absolute left-0 top-0 h-9 w-[2.5px] origin-top rounded-full bg-indigo-500 dark:bg-indigo-400" />
            {/* top-right */}
            <span
              className="ifc-h absolute right-0 top-0 h-[2.5px] w-9 origin-right rounded-full bg-indigo-500 dark:bg-indigo-400"
              style={{ transitionDelay: "60ms" }}
            />
            <span
              className="ifc-v absolute right-0 top-0 h-9 w-[2.5px] origin-top rounded-full bg-indigo-500 dark:bg-indigo-400"
              style={{ transitionDelay: "60ms" }}
            />
            {/* bottom-left */}
            <span
              className="ifc-h absolute bottom-0 left-0 h-[2.5px] w-9 origin-left rounded-full bg-indigo-500 dark:bg-indigo-400"
              style={{ transitionDelay: "120ms" }}
            />
            <span
              className="ifc-v absolute bottom-0 left-0 h-9 w-[2.5px] origin-bottom rounded-full bg-indigo-500 dark:bg-indigo-400"
              style={{ transitionDelay: "120ms" }}
            />
            {/* bottom-right */}
            <span
              className="ifc-h absolute bottom-0 right-0 h-[2.5px] w-9 origin-right rounded-full bg-indigo-500 dark:bg-indigo-400"
              style={{ transitionDelay: "180ms" }}
            />
            <span
              className="ifc-v absolute bottom-0 right-0 h-9 w-[2.5px] origin-bottom rounded-full bg-indigo-500 dark:bg-indigo-400"
              style={{ transitionDelay: "180ms" }}
            />
          </span>

          <span className="ifc-tag pointer-events-none absolute bottom-5 left-5 flex items-center gap-2 text-xs font-medium tracking-wide text-white">
            <span className="h-1.5 w-1.5 rounded-full bg-indigo-400" />
            Locked · f/2.8
          </span>
        </div>

        <p className="mt-5 text-center text-xs text-neutral-500 dark:text-neutral-500">
          Hover or focus the photo — the corner frame draws itself in.
        </p>
      </motion.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 →