The road to freedom starts from here
Build
awesome
apps
with
Aceternity.
Installation
Install dependencies
npm i framer-motion clsx tailwind-merge
Add util file
lib/utils.ts
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Copy the source code
components/ui/typewriter-effect.tsx
"use client";
import { cn } from "@/lib/utils";
import { motion, stagger, useAnimate, useInView } from "framer-motion";
import { useEffect } from "react";
export const TypewriterEffect = ({
words,
className,
cursorClassName,
}: {
words: {
text: string;
className?: string;
}[];
className?: string;
cursorClassName?: string;
}) => {
// split text inside of words into array of characters
const wordsArray = words.map((word) => {
return {
...word,
text: word.text.split(""),
};
});
const [scope, animate] = useAnimate();
const isInView = useInView(scope);
useEffect(() => {
if (isInView) {
animate(
"span",
{
display: "inline-block",
opacity: 1,
width: "fit-content",
},
{
duration: 0.3,
delay: stagger(0.1),
ease: "easeInOut",
}
);
}
}, [isInView]);
const renderWords = () => {
return (
<motion.div ref={scope} className="inline">
{wordsArray.map((word, idx) => {
return (
<div key={`word-${idx}`} className="inline-block">
{word.text.map((char, index) => (
<motion.span
initial={{}}
key={`char-${index}`}
className={cn(
`dark:text-white text-black opacity-0 hidden`,
word.className
)}
>
{char}
</motion.span>
))}
</div>
);
})}
</motion.div>
);
};
return (
<div
className={cn(
"text-base sm:text-xl md:text-3xl lg:text-5xl font-bold text-center",
className
)}
>
{renderWords()}
<motion.span
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
}}
transition={{
duration: 0.8,
repeat: Infinity,
repeatType: "reverse",
}}
className={cn(
"inline-block rounded-sm w-[4px] h-4 md:h-6 lg:h-10 bg-blue-500",
cursorClassName
)}
></motion.span>
</div>
);
};
export const TypewriterEffectSmooth = ({
words,
className,
cursorClassName,
}: {
words: {
text: string;
className?: string;
}[];
className?: string;
cursorClassName?: string;
}) => {
// split text inside of words into array of characters
const wordsArray = words.map((word) => {
return {
...word,
text: word.text.split(""),
};
});
const renderWords = () => {
return (
<div>
{wordsArray.map((word, idx) => {
return (
<div key={`word-${idx}`} className="inline-block">
{word.text.map((char, index) => (
<span
key={`char-${index}`}
className={cn(`dark:text-white text-black `, word.className)}
>
{char}
</span>
))}
</div>
);
})}
</div>
);
};
return (
<div className={cn("flex space-x-1 my-6", className)}>
<motion.div
className="overflow-hidden pb-2"
initial={{
width: "0%",
}}
whileInView={{
width: "fit-content",
}}
transition={{
duration: 2,
ease: "linear",
delay: 1,
}}
>
<div
className="text-xs sm:text-base md:text-xl lg:text:3xl xl:text-5xl font-bold"
style={{
whiteSpace: "nowrap",
}}
>
{renderWords()}{" "}
</div>{" "}
</motion.div>
<motion.span
initial={{
opacity: 0,
}}
animate={{
opacity: 1,
}}
transition={{
duration: 0.8,
repeat: Infinity,
repeatType: "reverse",
}}
className={cn(
"block rounded-sm w-[4px] h-4 sm:h-6 xl:h-12 bg-blue-500",
cursorClassName
)}
></motion.span>
</div>
);
};
Example
A more janky animation to give a typing effect.
The road to freedom starts from here
Props
Prop name | Type | Description |
---|---|---|
className | string | The class name of the container component. |
words | Array<{text: string; className: string;}> | Every word with a className that you can define |
cursorClassName | string | The className of the cursor component |
Get beautiful, hand-crafted templates and components with Aceternity UI Pro
Professional, beautiful and elegant templates for your business. Get the best component packs and templates with Aceternity UI Pro.
This service exceeded our expectations, since not only was the development technically flawless, but Manu and his team also acted as strategic partners by encouraging us to add ...
Georg Weingartner
CMO at Renderwork