Docs
Wave Reveal
Reveal letter or word one by one with a wave effect & optional blur effect.
Loading...
Installation
CLI
pnpm dlx shadcn@latest add https://animata.design/r/text/wave-reveal.json
Manual
Add to your CSS
@keyframes reveal-up {
0% { opacity: 0; transform: translateY(80%); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes reveal-down {
0% { opacity: 0; transform: translateY(-80%); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes content-blur {
0% { filter: blur(0.3rem); }
100% { filter: blur(0); }
}
@theme {
--ease-minor-spring: cubic-bezier(0.18, 0.89, 0.82, 1.04);
}Run the following command
It will create a new file called wave-reveal.tsx inside the components/animata/text directory.
touch components/animata/text/wave-reveal.tsxPaste the code
Open the newly created file and paste the following code:
import type { ReactNode } from "react";
import { cn } from "@/lib/utils";
interface WaveRevealProps {
/**
* The text to animate
*/
text: string;
/**
* Additional classes for the container
*/
className?: string;
/**
* The direction of the animation
* @default "down"
*/
direction?: "up" | "down";
/**
* The mode of the animation
* @default "letter"
*/
mode?: "letter" | "word";
/**
* Duration of the animation
* E.g. 2000ms
*/
duration?: string;
/**
* If true, the text will apply a blur effect as seen in WWDC.
*/
blur?: boolean;
letterClassName?: string;
/**
* Delay for each letter/word in ms
*/
delay?: number;
}
interface ReducedValue extends Pick<WaveRevealProps, "direction" | "mode"> {
nodes: ReactNode[];
offset: number;
duration: number | string;
delay: number;
blur?: boolean;
className?: string;
wordsLength: number;
textLength: number;
}
const Word = ({
isWordMode,
word,
index,
offset,
delay,
duration,
className,
}: Pick<ReducedValue, "delay" | "duration" | "offset"> & {
index: number;
className: string;
isWordMode: boolean;
word: string;
length: number;
}) => {
if (isWordMode) {
return word;
}
return (
<>
{word.split("").map((letter, letterIndex) => {
return (
<span
key={`${letter}_${letterIndex}_${index}`}
className={cn({
[className]: !isWordMode,
})}
style={{
animationDuration: `${duration}`,
animationDelay: createDelay({
index: letterIndex,
offset,
delay,
}),
}}
>
{letter === " " ? "\u00A0" : letter}
</span>
);
})}
</>
);
};
const createDelay = ({
offset,
index,
delay,
}: Pick<ReducedValue, "offset" | "delay"> & {
index: number;
}) => {
return `${delay + (index + offset) * 50}ms`;
};
const createAnimatedNodes = (args: ReducedValue, word: string, index: number): ReducedValue => {
const { nodes, offset, wordsLength, textLength, mode, direction, duration, delay, blur } = args;
const isWordMode = mode === "word";
const isUp = direction === "up";
const length = isWordMode ? wordsLength : textLength;
const isLast = index === length - 1;
const className = cn(
"inline-block opacity-0 transition ease-in-out fill-mode-forwards",
{
// Determine the animation direction
"animate-[reveal-down]": !isUp && !blur,
"animate-[reveal-up]": isUp && !blur,
"animate-[reveal-down,content-blur]": !isUp && blur,
"animate-[reveal-up,content-blur]": isUp && blur,
},
args.className,
);
const node = (
<span
key={`word_${index}`}
className={cn("contents", {
[className]: isWordMode,
})}
style={
isWordMode
? {
animationDuration: `${duration}`,
animationDelay: createDelay({
index,
offset,
delay,
}),
}
: undefined
}
>
<Word
isWordMode={isWordMode}
word={word}
index={index}
offset={offset}
duration={duration}
className={className}
length={length}
delay={delay}
/>
{!isLast && "\u00A0"}
</span>
);
return {
...args,
nodes: [...nodes, node],
offset: offset + (isWordMode ? 1 : word.length + 1),
};
};
export default function WaveReveal({
text,
direction = "down",
mode = "letter",
className,
duration = "2000ms",
delay = 0,
blur = true,
letterClassName,
}: WaveRevealProps) {
if (!text) {
return null;
}
const words = text.trim().split(/\s/);
const { nodes } = words.reduce<ReducedValue>(createAnimatedNodes, {
nodes: [],
offset: 0,
wordsLength: words.length,
textLength: text.length,
direction,
mode,
duration: duration ?? 60,
delay: delay ?? 0,
blur,
className: letterClassName,
});
return (
<div
className={cn(
"relative flex flex-wrap justify-center whitespace-pre px-2 text-4xl font-medium md:px-6 md:text-7xl",
className,
)}
>
{nodes}
<div className="sr-only">{text}</div>
</div>
);
}Credits
Built by hari