Type Writer
A cool typewriter effect where you input list of sentences or one and it will type it
tsx
1import { Typewriter } from "@/components/ui/type-writer";
2
3export function ComponentPreview() {
4 return (
5 <div className="w-full h-[calc(100vh-400px)] flex justify-center items-center">
6 <Typewriter texts={[
7 "What do you think ?",
8 "Is it good ?",
9 "then go vote for me 😂"
10 ]} />
11 </div>
12 );
13}Installation
Start by installing the required dependencies:
tsx
1npm install gsap @gsap/reactThen, copy the following component code into your project:
components/ui/stacked-cards.tsx
1'use client'
2import React, { useEffect, useRef } from 'react';
3import { gsap } from 'gsap';
4import { TextPlugin } from 'gsap/TextPlugin';
5import { cn } from '@/lib/utils';
6
7gsap.registerPlugin(TextPlugin);
8
9
10export interface TypewriterProps {
11 texts: string[];
12 className?: string;
13 textClassName?: string;
14 cursorClassName?: string;
15 cursorChar?: string;
16 typingSpeed?: number;
17 deletingSpeed?: number;
18 delayBetweenTexts?: number;
19 loop?: boolean;
20}
21
22
23export const Typewriter: React.FC<TypewriterProps> = ({
24 texts,
25 className,
26 textClassName,
27 cursorClassName,
28 cursorChar = '|',
29 typingSpeed = 0.05,
30 deletingSpeed = 0.03,
31 delayBetweenTexts = 1.5,
32 loop = true,
33}) => {
34 const textRef = useRef<HTMLSpanElement>(null);
35 const cursorRef = useRef<HTMLSpanElement>(null);
36
37 useEffect(() => {
38 if (!textRef.current || !cursorRef.current || texts.length === 0) return;
39
40 const cursorTween = gsap.to(cursorRef.current, {
41 opacity: 0,
42 ease: 'power2.inOut',
43 duration: 0.7,
44 repeat: -1,
45 yoyo: true,
46 });
47
48 const masterTimeline = gsap.timeline({
49 repeat: loop ? -1 : 0,
50 });
51
52 texts.forEach((text, i) => {
53 const textTimeline = gsap.timeline();
54
55 textTimeline.to(textRef.current, {
56 duration: text.length * typingSpeed,
57 text: {
58 value: text,
59 newClass: "is-typing"
60 },
61 ease: 'none',
62 })
63
64 .to(textRef.current, {
65 duration: text.length * deletingSpeed,
66 text: {
67 value: "",
68 newClass: "is-deleting"
69 },
70 ease: 'none',
71 delay: delayBetweenTexts,
72
73 onComplete: () => {
74 if (!loop && i === texts.length - 1) {
75 cursorTween.pause();
76 gsap.to(cursorRef.current, { opacity: 1, duration: 0.2 });
77 }
78 }
79 });
80
81 masterTimeline.add(textTimeline);
82 });
83
84 return () => {
85 masterTimeline.kill();
86 cursorTween.kill();
87 };
88
89 }, [texts, loop, typingSpeed, deletingSpeed, delayBetweenTexts]);
90
91 return (
92 <div
93 className={cn(
94 'inline-flex items-center justify-center p-6 sm:p-8 rounded-2xl border border-white/10 bg-black/20 backdrop-blur-lg shadow-2xl shadow-black/30',
95 className
96 )}
97 >
98 <span
99 ref={textRef}
100 className={cn(
101 'text-2xl md:text-4xl lg:text-5xl font-mono font-medium text-white tracking-wider',
102 textClassName
103 )}
104 />
105 <span
106 ref={cursorRef}
107 aria-hidden="true"
108 className={cn(
109 'text-2xl md:text-4xl lg:text-5xl font-mono font-medium text-cyan-300 ml-1',
110 cursorClassName
111 )}
112 >
113 {cursorChar}
114 </span>
115 </div>
116 );
117};