Skip to content

Commit 8728991

Browse files
committed
feat: 타이틀 text 애니메이션 추가
1 parent 29edc7c commit 8728991

File tree

3 files changed

+141
-3
lines changed

3 files changed

+141
-3
lines changed

src/components/Home/Home.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useEffect } from "react";
33
import styles from "@/components/Home/Home.module.scss";
44
import { AppBridgeMessageType } from "@/components/provider/AppBridgeProvider/AppBridgeMessage.types";
55
import { useAppBridge } from "@/components/provider/AppBridgeProvider/AppBridgeProvider";
6+
import AnimationText from "@/components/ui/AnimationText/AnimationText";
67
import IconButton from "@/components/ui/IconButton/IconButton";
78
import Text from "@/components/ui/Text/Text";
89

@@ -27,9 +28,7 @@ const Home = () => {
2728
<div className={styles.Home}>
2829
<div className={styles.HomeTop}>
2930
<div className={styles.HomeTitle}>
30-
<Text variant="titleLg" color="gradient" align="center" as="h1">
31-
{`영수증으로\nAI 음식 리뷰 남겨요`}
32-
</Text>
31+
<AnimationText />
3332
<Text variant="bodyLg" color="secondary" align="center">
3433
손쉬운 음식 리뷰 작성
3534
</Text>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import React, { useEffect, useState } from "react";
2+
3+
import classNames from "classnames";
4+
5+
import styles from "@/components/ui/Text/Text.module.scss";
6+
7+
interface Letter {
8+
char: string;
9+
id: number;
10+
}
11+
12+
const AnimationText: React.FC = () => {
13+
const [firstLineLetters, setFirstLineLetters] = useState<Letter[]>([]);
14+
const [secondLineLetters, setSecondLineLetters] = useState<Letter[]>([]);
15+
const [hasAnimated, setHasAnimated] = useState<boolean>(false);
16+
17+
useEffect(() => {
18+
if (hasAnimated) return;
19+
setHasAnimated(true);
20+
21+
const firstLine = "영수증으로";
22+
const firstLetters = firstLine.split("").map((char, index) => ({
23+
char,
24+
id: index,
25+
}));
26+
setFirstLineLetters(firstLetters);
27+
28+
const firstLineDelay = firstLine.length * 20 + 100;
29+
setTimeout(() => {
30+
const secondLine = "AI 음식 리뷰 남겨요";
31+
const secondLetters = [...secondLine].map((char, index) => ({
32+
// split("") 대신 [...string] 사용
33+
char,
34+
id: index,
35+
}));
36+
setSecondLineLetters(secondLetters);
37+
}, firstLineDelay);
38+
}, [hasAnimated]);
39+
40+
return (
41+
<div className={styles.TitleWrapper}>
42+
<h1 className={classNames(styles.word)}>
43+
{firstLineLetters.map((letter, index) => (
44+
<span
45+
key={letter.id}
46+
className={classNames(
47+
styles.letter,
48+
styles.Text,
49+
styles["color-gradient"],
50+
styles[`variant-titleLg`],
51+
styles[`align-center`],
52+
letter.char === " " && styles.space,
53+
)}
54+
style={{
55+
animationDelay: `${index * 50}ms`,
56+
animationName: letter.char === " " ? "none" : styles.textBlur,
57+
animationDuration: "0.5s",
58+
animationFillMode: "forwards",
59+
animationTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
60+
}}
61+
>
62+
{letter.char}
63+
</span>
64+
))}
65+
</h1>
66+
<h1 className={styles.word}>
67+
{secondLineLetters.map((letter, index) => (
68+
<span
69+
key={letter.id}
70+
className={classNames(
71+
styles.letter,
72+
styles.Text,
73+
styles["color-gradient"],
74+
styles[`variant-titleLg`],
75+
styles[`align-center`],
76+
letter.char === " " && styles.space,
77+
)}
78+
style={{
79+
animationDelay: `${index * 20}ms`,
80+
animationName: letter.char === " " ? "none" : styles.textBlur,
81+
animationDuration: "0.5s",
82+
animationFillMode: "forwards",
83+
animationTimingFunction: "cubic-bezier(0.4, 0, 0.2, 1)",
84+
}}
85+
>
86+
{letter.char}
87+
</span>
88+
))}
89+
</h1>
90+
</div>
91+
);
92+
};
93+
94+
export default AnimationText;

src/components/ui/Text/Text.module.scss

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,48 @@
111111
justify-content: center;
112112
}
113113
}
114+
115+
.TitleWrapper {
116+
height: 5.25rem;
117+
}
118+
119+
.word {
120+
display: flex;
121+
justify-content: center;
122+
align-items: center;
123+
text-align: center;
124+
125+
.letter {
126+
display: inline-block;
127+
opacity: 0;
128+
filter: blur(8px);
129+
transform: translateY(20px);
130+
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
131+
132+
&.appear {
133+
opacity: 1;
134+
filter: blur(0);
135+
transform: translateY(0);
136+
}
137+
}
138+
}
139+
140+
@keyframes textBlur {
141+
0% {
142+
opacity: 0;
143+
filter: blur(8px);
144+
transform: translateY(20px);
145+
}
146+
147+
100% {
148+
opacity: 1;
149+
filter: blur(0);
150+
transform: translateY(0);
151+
}
152+
}
153+
154+
.space {
155+
width: 0.4rem;
156+
opacity: 1;
157+
visibility: visible;
158+
}

0 commit comments

Comments
 (0)