Web InnoventixFreeCode

Tape Corners Effect

Original · free

Washi-tape corners hold the photo, wobbling on hover.

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

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

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

  const shouldAnimate = !reduceMotion && inView;

  return (
    <section className="relative w-full overflow-hidden bg-neutral-50 px-6 py-24 text-neutral-900 sm:px-10 dark:bg-neutral-950 dark:text-neutral-50">
      <style>{`
        @keyframes twc-wobble-tl {
          0%   { transform: rotate(-46deg) translate3d(0,0,0); }
          25%  { transform: rotate(-42deg) translate3d(1px,-1px,0); }
          50%  { transform: rotate(-48deg) translate3d(-1px,1px,0); }
          75%  { transform: rotate(-44deg) translate3d(1px,0,0); }
          100% { transform: rotate(-46deg) translate3d(0,0,0); }
        }
        @keyframes twc-wobble-tr {
          0%   { transform: rotate(44deg) translate3d(0,0,0); }
          25%  { transform: rotate(48deg) translate3d(-1px,-1px,0); }
          50%  { transform: rotate(40deg) translate3d(1px,1px,0); }
          75%  { transform: rotate(46deg) translate3d(-1px,0,0); }
          100% { transform: rotate(44deg) translate3d(0,0,0); }
        }
        @keyframes twc-wobble-bl {
          0%   { transform: rotate(44deg) translate3d(0,0,0); }
          25%  { transform: rotate(40deg) translate3d(1px,1px,0); }
          50%  { transform: rotate(48deg) translate3d(-1px,-1px,0); }
          75%  { transform: rotate(42deg) translate3d(0,1px,0); }
          100% { transform: rotate(44deg) translate3d(0,0,0); }
        }
        @keyframes twc-wobble-br {
          0%   { transform: rotate(-46deg) translate3d(0,0,0); }
          25%  { transform: rotate(-50deg) translate3d(-1px,1px,0); }
          50%  { transform: rotate(-42deg) translate3d(1px,-1px,0); }
          75%  { transform: rotate(-48deg) translate3d(0,-1px,0); }
          100% { transform: rotate(-46deg) translate3d(0,0,0); }
        }
        @keyframes twc-settle {
          0%   { transform: translate3d(0,0,0) rotate(-0.6deg); }
          50%  { transform: translate3d(0,-3px,0) rotate(0.4deg); }
          100% { transform: translate3d(0,0,0) rotate(-0.6deg); }
        }

        .twc-frame { transform: rotate(-0.6deg); }
        .twc-tape { will-change: transform; }

        .twc-group:hover .twc-tl { animation: twc-wobble-tl 0.5s ease-in-out; }
        .twc-group:hover .twc-tr { animation: twc-wobble-tr 0.5s ease-in-out 0.04s; }
        .twc-group:hover .twc-bl { animation: twc-wobble-bl 0.5s ease-in-out 0.08s; }
        .twc-group:hover .twc-br { animation: twc-wobble-br 0.5s ease-in-out 0.12s; }
        .twc-group:hover .twc-frame { animation: twc-settle 0.55s ease-in-out; }

        @media (prefers-reduced-motion: reduce) {
          .twc-group:hover .twc-tl,
          .twc-group:hover .twc-tr,
          .twc-group:hover .twc-bl,
          .twc-group:hover .twc-br,
          .twc-group:hover .twc-frame { animation: none; }
        }
      `}</style>

      <div ref={ref} className="mx-auto flex max-w-3xl flex-col items-center">
        <motion.div
          initial={reduceMotion ? false : { opacity: 0, y: 16 }}
          animate={shouldAnimate ? { opacity: 1, y: 0 } : undefined}
          transition={{ duration: 0.5, ease: "easeOut" }}
          className="mb-10 text-center"
        >
          <p className="text-xs font-semibold uppercase tracking-[0.28em] text-rose-500 dark:text-rose-400">
            Washi Tape Corners
          </p>
          <h2 className="mt-3 text-3xl font-semibold tracking-tight sm:text-4xl">
            Taped to the wall, not framed
          </h2>
        </motion.div>

        <motion.div
          initial={reduceMotion ? false : { opacity: 0, y: 28, rotate: -1.5 }}
          animate={shouldAnimate ? { opacity: 1, y: 0, rotate: 0 } : undefined}
          transition={{ duration: 0.6, ease: "easeOut", delay: 0.08 }}
          className="twc-group relative"
        >
          <div className="twc-frame relative rounded-[2px] bg-white p-3 shadow-[0_18px_40px_-18px_rgba(0,0,0,0.45)] dark:bg-neutral-100 dark:shadow-[0_18px_50px_-16px_rgba(0,0,0,0.8)]">
            {/* eslint-disable-next-line @next/next/no-img-element */}
            <img
              src="/img/gallery/g12.webp"
              alt="A candid moment pinned up like a keepsake photo"
              loading="lazy"
              draggable={false}
              className="block h-auto w-full max-w-md select-none rounded-[1px] object-cover"
            />

            {/* top-left tape */}
            <span
              aria-hidden="true"
              className="twc-tape twc-tl absolute -left-6 -top-4 h-8 w-28 origin-center bg-amber-200/80 shadow-sm ring-1 ring-amber-300/40 backdrop-blur-[1px] [clip-path:polygon(4%_0,96%_6%,100%_94%,2%_100%)] dark:bg-amber-300/70"
            >
              <span className="absolute inset-0 bg-[repeating-linear-gradient(90deg,transparent_0,transparent_8px,rgba(255,255,255,0.35)_8px,rgba(255,255,255,0.35)_10px)]" />
            </span>

            {/* top-right tape */}
            <span
              aria-hidden="true"
              className="twc-tape twc-tr absolute -right-6 -top-4 h-8 w-28 origin-center bg-rose-200/80 shadow-sm ring-1 ring-rose-300/40 backdrop-blur-[1px] [clip-path:polygon(2%_6%,98%_0,96%_100%,0_92%)] dark:bg-rose-300/70"
            >
              <span className="absolute inset-0 bg-[repeating-linear-gradient(90deg,transparent_0,transparent_8px,rgba(255,255,255,0.35)_8px,rgba(255,255,255,0.35)_10px)]" />
            </span>

            {/* bottom-left tape */}
            <span
              aria-hidden="true"
              className="twc-tape twc-bl absolute -bottom-4 -left-6 h-8 w-28 origin-center bg-emerald-200/80 shadow-sm ring-1 ring-emerald-300/40 backdrop-blur-[1px] [clip-path:polygon(2%_0,98%_8%,96%_100%,4%_94%)] dark:bg-emerald-300/70"
            >
              <span className="absolute inset-0 bg-[repeating-linear-gradient(90deg,transparent_0,transparent_8px,rgba(255,255,255,0.35)_8px,rgba(255,255,255,0.35)_10px)]" />
            </span>

            {/* bottom-right tape */}
            <span
              aria-hidden="true"
              className="twc-tape twc-br absolute -bottom-4 -right-6 h-8 w-28 origin-center bg-violet-200/80 shadow-sm ring-1 ring-violet-300/40 backdrop-blur-[1px] [clip-path:polygon(4%_8%,96%_0,100%_92%,0_100%)] dark:bg-violet-300/70"
            >
              <span className="absolute inset-0 bg-[repeating-linear-gradient(90deg,transparent_0,transparent_8px,rgba(255,255,255,0.35)_8px,rgba(255,255,255,0.35)_10px)]" />
            </span>
          </div>
        </motion.div>

        <p className="mt-12 text-center text-sm text-neutral-500 dark:text-neutral-400">
          Hover the photo — the tape gives a little wobble, like it just caught the breeze.
        </p>
      </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 →