A sophisticated 3D flip card component that automatically cycles through different logos or content with smooth flip animations. Features realistic perspective effects, customizable timing, and seamless transitions perfect for showcasing brands, testimonials, or rotating content.
npm i motion react-icons tailwind-mergeLogoCardFlipper.tsx
import React, { ReactElement, useEffect, useRef, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { SiAmazon, SiGithub, SiGoogle, SiMeta, SiTwitch } from "react-icons/si";
import { twMerge } from "tailwind-merge";
export const DivOrigami = () => {
return (
<section className="flex h-72 flex-col items-center justify-center gap-12 bg-neutral-950 px-4 py-24 md:flex-row">
<LogoRolodex
items={[
<LogoItem key={1} className="bg-orange-300 text-neutral-900">
<SiAmazon />
</LogoItem>,
<LogoItem key={2} className="bg-green-300 text-neutral-900">
<SiGoogle />
</LogoItem>,
<LogoItem key={3} className="bg-blue-300 text-neutral-900">
<SiMeta />
</LogoItem>,
<LogoItem key={4} className="bg-white text-black">
<SiGithub />
</LogoItem>,
<LogoItem key={5} className="bg-purple-300 text-neutral-900">
<SiTwitch />
</LogoItem>,
]}
/>
</section>
);
};
const DELAY_IN_MS = 2500;
const TRANSITION_DURATION_IN_SECS = 1.5;
const LogoRolodex = ({ items }: { items: ReactElement[] }) => {
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
const [index, setIndex] = useState(0);
useEffect(() => {
intervalRef.current = setInterval(() => {
setIndex((pv) => pv + 1);
}, DELAY_IN_MS);
return () => {
clearInterval(intervalRef.current || undefined);
};
}, []);
return (
<div
style={{
transform: "rotateY(-20deg)",
transformStyle: "preserve-3d",
}}
className="relative z-0 h-44 w-60 shrink-0 rounded-xl border border-neutral-700 bg-neutral-800"
>
<AnimatePresence mode="sync">
<motion.div
style={{
y: "-50%",
x: "-50%",
clipPath: "polygon(0 0, 100% 0, 100% 50%, 0 50%)",
zIndex: -index,
backfaceVisibility: "hidden",
}}
key={index}
transition={{
duration: TRANSITION_DURATION_IN_SECS,
ease: "easeInOut",
}}
initial={{ rotateX: "0deg" }}
animate={{ rotateX: "0deg" }}
exit={{ rotateX: "-180deg" }}
className="absolute left-1/2 top-1/2"
>
{items[index % items.length]}
</motion.div>
<motion.div
style={{
y: "-50%",
x: "-50%",
clipPath: "polygon(0 50%, 100% 50%, 100% 100%, 0 100%)",
zIndex: index,
backfaceVisibility: "hidden",
}}
key={(index + 1) * 2}
initial={{ rotateX: "180deg" }}
animate={{ rotateX: "0deg" }}
exit={{ rotateX: "0deg" }}
transition={{
duration: TRANSITION_DURATION_IN_SECS,
ease: "easeInOut",
}}
className="absolute left-1/2 top-1/2"
>
{items[index % items.length]}
</motion.div>
</AnimatePresence>
<hr
style={{
transform: "translateZ(1px)",
}}
className="absolute left-0 right-0 top-1/2 z-[999999999] -translate-y-1/2 border-t-2 border-neutral-800"
/>
</div>
);
};
const LogoItem = ({
children,
className,
}: {
children: React.ReactNode;
className?: string;
}) => {
return (
<div
className={twMerge(
"grid h-36 w-52 place-content-center rounded-lg bg-neutral-700 text-6xl text-neutral-50",
className
)}
>
{children}
</div>
);
};You can customize the timing and animation by modifying these constants:
const DELAY_IN_MS = 3000; // Time between flips (3 seconds)
const TRANSITION_DURATION_IN_SECS = 2; // Duration of flip animation (2 seconds)Basic Logo Showcase:
<LogoRolodex
items={[
<LogoItem key={1} className="bg-red-500 text-white">
<SiNetflix />
</LogoItem>,
<LogoItem key={2} className="bg-blue-500 text-white">
<SiFacebook />
</LogoItem>,
]}
/>Custom Content:
<LogoRolodex
items={[
<LogoItem key={1} className="bg-gradient-to-r from-purple-400 to-pink-400">
<span className="text-2xl font-bold">Feature 1</span>
</LogoItem>,
<LogoItem key={2} className="bg-gradient-to-r from-green-400 to-blue-500">
<span className="text-2xl font-bold">Feature 2</span>
</LogoItem>,
]}
/>| Prop | Type | Default | Description |
|---|---|---|---|
items | ReactElement[] | — | Array of LogoItem components to cycle through |
| Prop | Type | Default | Description |
|---|---|---|---|
children | React.ReactNode | — | Content to display inside the card |
className | string | — | Additional CSS classes for custom styling |
The component uses a sophisticated clipping technique to create the flip effect:
clipPath: "polygon(0 0, 100% 0, 100% 50%, 0 50%)"clipPath: "polygon(0 50%, 100% 50%, 100% 100%, 0 100%)"transform: "rotateY(-20deg)" for depth