-
Notifications
You must be signed in to change notification settings - Fork 523
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add warp animation component (#307)
* feat: add warp animation component * fix: added the warp grid lines * fix: update styles * fix: update mdx * fix: update code * fix: converted to tailwind * fix: converted more stuff to tailwind --------- Co-authored-by: Nguyen Phuc Loc <nguyenphucloc@Locs-MacBook-Pro.local> Co-authored-by: Arghya Das <arghyadasproject@gmail.com> Co-authored-by: Dillion Verma <hello@dillion.io>
- Loading branch information
1 parent
247750d
commit 792726a
Showing
9 changed files
with
991 additions
and
329 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
--- | ||
title: Warp Animation Container | ||
date: 2024-12-24 | ||
description: A container component that applies a warp animation effect to its children | ||
author: magicui | ||
published: true | ||
--- | ||
|
||
<ComponentPreview name="warp-animation-container-demo" /> | ||
|
||
## Installation | ||
|
||
<Tabs defaultValue="cli"> | ||
|
||
<TabsList> | ||
<TabsTrigger value="cli">CLI</TabsTrigger> | ||
<TabsTrigger value="manual">Manual</TabsTrigger> | ||
</TabsList> | ||
<TabsContent value="cli"> | ||
|
||
```bash | ||
npx shadcn@latest add "https://magicui.design/r/warp-animation-container" | ||
``` | ||
|
||
</TabsContent> | ||
|
||
<TabsContent value="manual"> | ||
|
||
<Steps> | ||
|
||
<Step>Copy and paste the following code into your project.</Step> | ||
|
||
<ComponentSource name="warp-animation-container" /> | ||
|
||
<Step>Update the import paths to match your project setup.</Step> | ||
|
||
</Steps> | ||
|
||
</TabsContent> | ||
|
||
</Tabs> | ||
|
||
## Props | ||
|
||
| Prop | Type | Description | Default | | ||
| ------------ | --------------- | ----------------------------------------------- | -------------------- | | ||
| children | React.ReactNode | The content to be put inside the warp animation | - | | ||
| perspective | number | The perspective of the warp animation | 100 | | ||
| beamsPerSide | number | The number of beams per side | 3 | | ||
| beamSize | number | The size of the beams | 5 | | ||
| beamDelayMax | number | The maximum delay of the beams | 3 | | ||
| beamDelayMin | number | The minimum delay of the beams | 0 | | ||
| beamDuration | number | The duration of the beams | 3 | | ||
| gridColor | string | The color of the grid lines | "hsl(var(--border))" | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"name": "warp-animation-container", | ||
"type": "registry:ui", | ||
"dependencies": [ | ||
"framer-motion" | ||
], | ||
"files": [ | ||
{ | ||
"path": "magicui/warp-animation-container.tsx", | ||
"content": "import { cn } from \"@/lib/utils\";\nimport { motion } from \"framer-motion\";\nimport React, { HTMLAttributes, useCallback, useMemo } from \"react\";\n\ninterface WarpAnimationContainerProps extends HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n perspective?: number;\n beamsPerSide?: number;\n beamSize?: number;\n beamDelayMax?: number;\n beamDelayMin?: number;\n beamDuration?: number;\n gridColor?: string;\n}\n\nconst Beam = ({\n width,\n x,\n delay,\n duration,\n}: {\n width: string | number;\n x: string | number;\n delay: number;\n duration: number;\n}) => {\n const hue = Math.floor(Math.random() * 360);\n const ar = Math.floor(Math.random() * 10) + 1;\n\n return (\n <motion.div\n className={`absolute top-0`}\n style={{\n left: x,\n width,\n aspectRatio: 1 / ar,\n background: `linear-gradient(hsl(${hue} 80% 60%), transparent)`,\n }}\n initial={{ y: \"100cqmax\", x: \"-50%\" }}\n animate={{ y: \"-100%\", x: \"-50%\" }}\n transition={{\n duration,\n delay,\n repeat: Infinity,\n ease: \"linear\",\n }}\n />\n );\n};\n\nconst WarpAnimationContainer: React.FC<WarpAnimationContainerProps> = ({\n children,\n perspective = 100,\n className,\n beamsPerSide = 3,\n beamSize = 5,\n beamDelayMax = 3,\n beamDelayMin = 0,\n beamDuration = 3,\n gridColor = \"hsl(var(--border))\",\n ...props\n}) => {\n const generateBeams = useCallback(() => {\n const beams = [];\n const cellsPerSide = Math.floor(100 / beamSize);\n const step = cellsPerSide / beamsPerSide;\n\n for (let i = 0; i < beamsPerSide; i++) {\n const x = Math.floor(i * step);\n const delay =\n Math.random() * (beamDelayMax - beamDelayMin) + beamDelayMin;\n beams.push({ x, delay });\n }\n return beams;\n }, [beamsPerSide, beamSize, beamDelayMax, beamDelayMin]);\n\n const topBeams = useMemo(() => generateBeams(), [generateBeams]);\n const rightBeams = useMemo(() => generateBeams(), [generateBeams]);\n const bottomBeams = useMemo(() => generateBeams(), [generateBeams]);\n const leftBeams = useMemo(() => generateBeams(), [generateBeams]);\n\n return (\n <div className={cn(\"relative rounded border p-20\", className)} {...props}>\n <div\n style={\n {\n \"--perspective\": `${perspective}px`,\n \"--grid-color\": gridColor,\n \"--beam-size\": `${beamSize}%`,\n } as React.CSSProperties\n }\n className={\n \"pointer-events-none absolute left-0 top-0 size-full overflow-hidden [clip-path:inset(0)] [container-type:size] [perspective:var(--perspective)] [transform-style:preserve-3d]\"\n }\n >\n <div\n style={{\n position: \"absolute\",\n transformStyle: \"preserve-3d\",\n containerType: \"inline-size\",\n width: \"100cqi\",\n height: \"100cqmax\",\n transformOrigin: \"50% 0%\",\n transform: \"rotateX(-90deg)\",\n backgroundSize: `var(--beam-size) var(--beam-size)`,\n background: `\n linear-gradient(var(--grid-color) 0 1px, transparent 1px var(--beam-size)) 50% -0.5px /\n var(--beam-size) var(--beam-size),\n linear-gradient(90deg, var(--grid-color) 0 1px, transparent 1px var(--beam-size))\n 50% 50% / var(--beam-size) var(--beam-size)\n `,\n }}\n >\n {topBeams.map((beam, index) => (\n <Beam\n key={`top-${index}`}\n width={`${beamSize}%`}\n x={`${beam.x * beamSize}%`}\n delay={beam.delay}\n duration={beamDuration}\n />\n ))}\n </div>\n <div\n style={{\n position: \"absolute\",\n transformStyle: \"preserve-3d\",\n containerType: \"inline-size\",\n width: \"100cqi\",\n height: \"100cqmax\",\n top: \"100%\",\n transformOrigin: \"50% 0%\",\n transform: \"rotateX(-90deg)\",\n backgroundSize: `var(--beam-size) var(--beam-size)`,\n background: `\n linear-gradient(var(--grid-color) 0 1px, transparent 1px var(--beam-size)) 50% -0.5px /\n var(--beam-size) var(--beam-size),\n linear-gradient(90deg, var(--grid-color) 0 1px, transparent 1px var(--beam-size))\n 50% 50% / var(--beam-size) var(--beam-size)\n `,\n }}\n >\n {bottomBeams.map((beam, index) => (\n <Beam\n key={`bottom-${index}`}\n width={`${beamSize}%`}\n x={`${beam.x * beamSize}%`}\n delay={beam.delay}\n duration={beamDuration}\n />\n ))}\n </div>\n <div\n style={{\n position: \"absolute\",\n transformStyle: \"preserve-3d\",\n containerType: \"inline-size\",\n width: \"100cqh\",\n height: \"100cqmax\",\n top: 0,\n left: 0,\n transformOrigin: \"0% 0%\",\n transform: \"rotate(90deg) rotateX(-90deg)\",\n backgroundSize: `var(--beam-size) var(--beam-size)`,\n background: `\n linear-gradient(var(--grid-color) 0 1px, transparent 1px var(--beam-size)) 50% -0.5px /\n var(--beam-size) var(--beam-size),\n linear-gradient(90deg, var(--grid-color) 0 1px, transparent 1px var(--beam-size))\n 50% 50% / var(--beam-size) var(--beam-size)\n `,\n }}\n >\n {leftBeams.map((beam, index) => (\n <Beam\n key={`left-${index}`}\n width={`${beamSize}%`}\n x={`${beam.x * beamSize}%`}\n delay={beam.delay}\n duration={beamDuration}\n />\n ))}\n </div>\n <div\n style={{\n position: \"absolute\",\n transformStyle: \"preserve-3d\",\n containerType: \"inline-size\",\n width: \"100cqh\",\n height: \"100cqmax\",\n top: 0,\n right: 0,\n transformOrigin: \"100% 0%\",\n transform: \"rotate(-90deg) rotateX(-90deg)\",\n backgroundSize: `var(--beam-size) var(--beam-size)`,\n background: `\n linear-gradient(var(--grid-color) 0 1px, transparent 1px var(--beam-size)) 50% -0.5px /\n var(--beam-size) var(--beam-size),\n linear-gradient(90deg, var(--grid-color) 0 1px, transparent 1px var(--beam-size))\n 50% 50% / var(--beam-size) var(--beam-size)\n `,\n }}\n >\n {rightBeams.map((beam, index) => (\n <Beam\n key={`right-${index}`}\n width={`${beamSize}%`}\n x={`${beam.x * beamSize}%`}\n delay={beam.delay}\n duration={beamDuration}\n />\n ))}\n </div>\n </div>\n <div className=\"relative\">{children}</div>\n </div>\n );\n};\n\nexport default WarpAnimationContainer;\n", | ||
"type": "registry:ui", | ||
"target": "" | ||
} | ||
] | ||
} |
26 changes: 26 additions & 0 deletions
26
registry/default/example/warp-animation-container-demo.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { | ||
Card, | ||
CardContent, | ||
CardDescription, | ||
CardTitle, | ||
} from "@/components/ui/card"; | ||
import WarpAnimationContainer from "@/components/magicui/warp-animation-container"; | ||
|
||
export default function ExampleComponentDemo() { | ||
return ( | ||
<div className="grid place-items-center"> | ||
<WarpAnimationContainer className="bg-background"> | ||
<Card className="w-80"> | ||
<CardContent className="flex flex-col gap-2 p-4"> | ||
<CardTitle>Congratulations on Your Promotion!</CardTitle> | ||
<CardDescription> | ||
Your hard work and dedication have paid off. We're thrilled | ||
to see you take this next step in your career. Keep up the | ||
fantastic work! | ||
</CardDescription> | ||
</CardContent> | ||
</Card> | ||
</WarpAnimationContainer> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import { cn } from "@/lib/utils"; | ||
import { motion } from "framer-motion"; | ||
import React, { HTMLAttributes, useCallback, useMemo } from "react"; | ||
|
||
interface WarpAnimationContainerProps extends HTMLAttributes<HTMLDivElement> { | ||
children: React.ReactNode; | ||
perspective?: number; | ||
beamsPerSide?: number; | ||
beamSize?: number; | ||
beamDelayMax?: number; | ||
beamDelayMin?: number; | ||
beamDuration?: number; | ||
gridColor?: string; | ||
} | ||
|
||
const Beam = ({ | ||
width, | ||
x, | ||
delay, | ||
duration, | ||
}: { | ||
width: string | number; | ||
x: string | number; | ||
delay: number; | ||
duration: number; | ||
}) => { | ||
const hue = Math.floor(Math.random() * 360); | ||
const ar = Math.floor(Math.random() * 10) + 1; | ||
|
||
return ( | ||
<motion.div | ||
style={ | ||
{ | ||
"--x": `${x}`, | ||
"--width": `${width}`, | ||
"--aspect-ratio": `${ar}`, | ||
"--background": `linear-gradient(hsl(${hue} 80% 60%), transparent)`, | ||
} as React.CSSProperties | ||
} | ||
className={`absolute left-[var(--x)] top-0 [aspect-ratio:1/var(--aspect-ratio)] [background:var(--background)] [width:var(--width)]`} | ||
initial={{ y: "100cqmax", x: "-50%" }} | ||
animate={{ y: "-100%", x: "-50%" }} | ||
transition={{ | ||
duration, | ||
delay, | ||
repeat: Infinity, | ||
ease: "linear", | ||
}} | ||
/> | ||
); | ||
}; | ||
|
||
const WarpAnimationContainer: React.FC<WarpAnimationContainerProps> = ({ | ||
children, | ||
perspective = 100, | ||
className, | ||
beamsPerSide = 3, | ||
beamSize = 5, | ||
beamDelayMax = 3, | ||
beamDelayMin = 0, | ||
beamDuration = 3, | ||
gridColor = "hsl(var(--border))", | ||
...props | ||
}) => { | ||
const generateBeams = useCallback(() => { | ||
const beams = []; | ||
const cellsPerSide = Math.floor(100 / beamSize); | ||
const step = cellsPerSide / beamsPerSide; | ||
|
||
for (let i = 0; i < beamsPerSide; i++) { | ||
const x = Math.floor(i * step); | ||
const delay = | ||
Math.random() * (beamDelayMax - beamDelayMin) + beamDelayMin; | ||
beams.push({ x, delay }); | ||
} | ||
return beams; | ||
}, [beamsPerSide, beamSize, beamDelayMax, beamDelayMin]); | ||
|
||
const topBeams = useMemo(() => generateBeams(), [generateBeams]); | ||
const rightBeams = useMemo(() => generateBeams(), [generateBeams]); | ||
const bottomBeams = useMemo(() => generateBeams(), [generateBeams]); | ||
const leftBeams = useMemo(() => generateBeams(), [generateBeams]); | ||
|
||
return ( | ||
<div className={cn("relative rounded border p-20", className)} {...props}> | ||
<div | ||
style={ | ||
{ | ||
"--perspective": `${perspective}px`, | ||
"--grid-color": gridColor, | ||
"--beam-size": `${beamSize}%`, | ||
} as React.CSSProperties | ||
} | ||
className={ | ||
"pointer-events-none absolute left-0 top-0 size-full overflow-hidden [clip-path:inset(0)] [container-type:size] [perspective:var(--perspective)] [transform-style:preserve-3d]" | ||
} | ||
> | ||
{/* top side */} | ||
<div className="absolute [transform-style:preserve-3d] [background-size:var(--beam-size)_var(--beam-size)] [background:linear-gradient(var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_-0.5px_/var(--beam-size)_var(--beam-size),linear-gradient(90deg,_var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_50%_/var(--beam-size)_var(--beam-size)] [container-type:inline-size] [height:100cqmax] [transform-origin:50%_0%] [transform:rotateX(-90deg)] [width:100cqi]"> | ||
{topBeams.map((beam, index) => ( | ||
<Beam | ||
key={`top-${index}`} | ||
width={`${beamSize}%`} | ||
x={`${beam.x * beamSize}%`} | ||
delay={beam.delay} | ||
duration={beamDuration} | ||
/> | ||
))} | ||
</div> | ||
{/* bottom side */} | ||
<div className="absolute top-full [transform-style:preserve-3d] [background-size:var(--beam-size)_var(--beam-size)] [background:linear-gradient(var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_-0.5px_/var(--beam-size)_var(--beam-size),linear-gradient(90deg,_var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_50%_/var(--beam-size)_var(--beam-size)] [container-type:inline-size] [height:100cqmax] [transform-origin:50%_0%] [transform:rotateX(-90deg)] [width:100cqi]"> | ||
{bottomBeams.map((beam, index) => ( | ||
<Beam | ||
key={`bottom-${index}`} | ||
width={`${beamSize}%`} | ||
x={`${beam.x * beamSize}%`} | ||
delay={beam.delay} | ||
duration={beamDuration} | ||
/> | ||
))} | ||
</div> | ||
{/* left side */} | ||
<div className="absolute left-0 top-0 [transform-style:preserve-3d] [background-size:var(--beam-size)_var(--beam-size)] [background:linear-gradient(var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_-0.5px_/var(--beam-size)_var(--beam-size),linear-gradient(90deg,_var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_50%_/var(--beam-size)_var(--beam-size)] [container-type:inline-size] [height:100cqmax] [transform-origin:0%_0%] [transform:rotate(90deg)_rotateX(-90deg)] [width:100cqh]"> | ||
{leftBeams.map((beam, index) => ( | ||
<Beam | ||
key={`left-${index}`} | ||
width={`${beamSize}%`} | ||
x={`${beam.x * beamSize}%`} | ||
delay={beam.delay} | ||
duration={beamDuration} | ||
/> | ||
))} | ||
</div> | ||
{/* right side */} | ||
<div className="absolute right-0 top-0 [transform-style:preserve-3d] [background-size:var(--beam-size)_var(--beam-size)] [background:linear-gradient(var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_-0.5px_/var(--beam-size)_var(--beam-size),linear-gradient(90deg,_var(--grid-color)_0_1px,_transparent_1px_var(--beam-size))_50%_50%_/var(--beam-size)_var(--beam-size)] [container-type:inline-size] [height:100cqmax] [width:100cqh] [transform-origin:100%_0%] [transform:rotate(-90deg)_rotateX(-90deg)]"> | ||
{rightBeams.map((beam, index) => ( | ||
<Beam | ||
key={`right-${index}`} | ||
width={`${beamSize}%`} | ||
x={`${beam.x * beamSize}%`} | ||
delay={beam.delay} | ||
duration={beamDuration} | ||
/> | ||
))} | ||
</div> | ||
</div> | ||
<div className="relative">{children}</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default WarpAnimationContainer; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters