Skip to content

Commit 74d6ffc

Browse files
authored
Merge pull request #72 from Nexters/feat/mobile-web-animation
feat: 모바일 웹 페이지 애니메이션 수정
2 parents dee5123 + 5113b1a commit 74d6ffc

File tree

5 files changed

+90
-15
lines changed

5 files changed

+90
-15
lines changed

apps/tuk-web/next.config.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import type { NextConfig } from "next";
1+
import type { NextConfig } from 'next';
22

3-
const nextConfig: NextConfig = {
4-
/* config options here */
5-
};
3+
const nextConfig: NextConfig = {};
64

75
export default nextConfig;

apps/tuk-web/src/app/invite/gathering/[gatheringId]/src/components/InviteGatheringContent.tsx

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import { useSuspenseQuery } from '@tanstack/react-query';
44
import { useEffect, useState } from 'react';
55

66
import { gatheringAPIService } from '@/app/invite/gathering/[gatheringId]/src/service';
7+
import { QuoteIcon } from '@/app/invite/meet/[meetId]/src/components/InviteProposal';
78
import { CardFrame } from '@/app/proposal/[proposalId]/detail/components/GatheringProposalContent';
89
import AppInstallBanner from '@/shared/components/AppInstallBanner';
10+
import { useSplashGate } from '@/shared/components/SplashGate';
911
import { useParam } from '@/shared/hooks/useParam';
1012
import { cn } from '@/shared/lib';
1113

@@ -20,7 +22,10 @@ const InviteGatheringContent = () => {
2022
queryFn: () => gatheringAPIService.getGatheringName(gatheringId),
2123
});
2224

25+
const { done: splashDone } = useSplashGate();
26+
2327
const [showBanner, setShowBanner] = useState(true);
28+
const [animate, setAnimate] = useState(false);
2429

2530
const handleCloseBanner = () => {
2631
localStorage.setItem(BANNER_KEY, Date.now().toString());
@@ -41,6 +46,18 @@ const InviteGatheringContent = () => {
4146
}
4247
}, []);
4348

49+
useEffect(() => {
50+
if (!splashDone) return;
51+
let raf = 0;
52+
const t = setTimeout(() => {
53+
raf = requestAnimationFrame(() => setAnimate(true));
54+
}, 150);
55+
return () => {
56+
clearTimeout(t);
57+
cancelAnimationFrame(raf);
58+
};
59+
}, [splashDone]);
60+
4461
return (
4562
<>
4663
{showBanner && <AppInstallBanner onClose={handleCloseBanner} />}
@@ -57,17 +74,30 @@ const InviteGatheringContent = () => {
5774
</h2>
5875

5976
<div className="relative mt-[56px] flex flex-col items-center justify-center">
60-
<div className="h-[320px] w-[278px] rounded-[10px] bg-gray-50" />
77+
<div
78+
className={cn(
79+
'h-[290px] w-[278px] translate-y-[10px] rounded-[10px] bg-gray-50 pt-4',
80+
animate && 'invite-float'
81+
)}
82+
>
83+
<div className="flex flex-col items-center">
84+
<QuoteIcon />
85+
</div>
86+
</div>
6187

62-
<div className="pointer-events-none absolute bottom-[-80px] left-1/2 h-[421px] w-[408px] -translate-x-1/2">
88+
<div className="pointer-events-none absolute bottom-[-90px] left-1/2 h-[421px] w-[408px] -translate-x-1/2">
6389
<div className="relative size-full">
6490
<div className="absolute inset-0 z-0">
6591
<CardFrame />
6692
</div>
6793

6894
{proposalDetail.data.gatheringName && (
69-
<div className="serif-body-16-M absolute left-1/2 top-[180px] z-[1] -translate-x-1/2 text-center text-gray-900">
70-
{proposalDetail.data.gatheringName}
95+
<div className="absolute left-1/2 top-[180px] z-[1] flex -translate-x-1/2 flex-col gap-2.5">
96+
<div className="serif-body-14-R text-gray-500">연락이 뜸해진</div>
97+
98+
<div className="serif-body-16-M text-center text-gray-900">
99+
{proposalDetail.data.gatheringName}
100+
</div>
71101
</div>
72102
)}
73103
</div>

apps/tuk-web/src/app/invite/meet/[meetId]/src/components/InviteProposalContent.tsx

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ const InviteProposalContent = () => {
1919

2020
const { done: splashDone } = useSplashGate();
2121

22-
const [slideDown, setSlideDown] = useState(false);
23-
2422
const [showBanner, setShowBanner] = useState(false);
2523

24+
const [slideStage, setSlideStage] = useState<0 | 1 | 2>(0);
25+
2626
const {
2727
data: proposalDetail,
2828
isLoading,
@@ -54,11 +54,28 @@ const InviteProposalContent = () => {
5454

5555
useEffect(() => {
5656
if (splashDone && !isLoading && !isError) {
57-
const id = requestAnimationFrame(() => setSlideDown(true));
58-
return () => cancelAnimationFrame(id);
57+
let rafId = 0;
58+
let tId: ReturnType<typeof setTimeout> | null = null;
59+
60+
rafId = requestAnimationFrame(() => {
61+
setSlideStage(1);
62+
tId = setTimeout(() => setSlideStage(2), 1000);
63+
});
64+
65+
return () => {
66+
cancelAnimationFrame(rafId);
67+
if (tId) clearTimeout(tId);
68+
};
5969
}
6070
}, [splashDone, isLoading, isError]);
6171

72+
const slideClass =
73+
slideStage === 0
74+
? 'translate-y-0 duration-0'
75+
: slideStage === 1
76+
? 'translate-y-[92px] duration-500'
77+
: 'translate-y-56 duration-500';
78+
6279
return (
6380
<>
6481
{showBanner && <AppInstallBanner onClose={handleCloseBanner} />}
@@ -81,8 +98,8 @@ const InviteProposalContent = () => {
8198

8299
<div
83100
className={cn(
84-
'ease-outmotion-reduce:transition-none absolute bottom-[-80px] left-1/2 h-[421px] w-[408px] -translate-x-1/2 transition-transform duration-1000',
85-
slideDown ? 'translate-y-56' : 'translate-y-0'
101+
'absolute bottom-[-80px] left-1/2 h-[421px] w-[408px] -translate-x-1/2 transition-transform ease-out motion-reduce:transition-none',
102+
slideClass
86103
)}
87104
style={{ willChange: 'transform', transitionDelay: '150ms' }}
88105
>

apps/tuk-web/src/app/proposal/[proposalId]/detail/components/GatheringProposal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { QueryErrorResetBoundary } from '@tanstack/react-query';
44
import React, { Suspense } from 'react';
55
import { ErrorBoundary } from 'react-error-boundary';
66

7-
import SkeletonGuard from '@/shared/components/SkeletonGuard';
87
import GatheringProposalContent from '@/app/proposal/[proposalId]/detail/components/GatheringProposalContent';
98
import GatheringProposalErrorFallback from '@/app/proposal/[proposalId]/detail/components/GatheringProposalErrorFallback';
109
import GatheringProposalSkeleton from '@/app/proposal/[proposalId]/detail/components/GatheringProposalSkeleton';
10+
import SkeletonGuard from '@/shared/components/SkeletonGuard';
1111

1212
const GatheringProposal = () => {
1313
return (

apps/tuk-web/src/styles/globals.css

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,36 @@
135135
display: none;
136136
}
137137

138+
@keyframes invite-bounce-float {
139+
0% {
140+
transform: translateY(30px);
141+
}
142+
22% {
143+
transform: translateY(-38px);
144+
}
145+
30% {
146+
transform: translateY(-30px);
147+
}
148+
55% {
149+
transform: translateY(-30px);
150+
}
151+
100% {
152+
transform: translateY(30px);
153+
}
154+
}
155+
156+
.invite-float {
157+
animation: invite-bounce-float 3s ease-in-out infinite both;
158+
will-change: transform;
159+
}
160+
161+
@media (prefers-reduced-motion: reduce) {
162+
.invite-float {
163+
animation: none !important;
164+
transform: translateY(10px);
165+
}
166+
}
167+
138168
html,
139169
body {
140170
max-width: 37.5rem;

0 commit comments

Comments
 (0)