From 0898f6d52b3376a3570232c8c2467a8218a40fc0 Mon Sep 17 00:00:00 2001 From: nate2402 <nate2402@ajou.ac.kr> Date: Tue, 8 Apr 2025 02:32:58 +0900 Subject: [PATCH] feat: add animate & transitions --- src/app/api/sentence/route.ts | 2 +- src/app/page.tsx | 14 +++++++--- src/components/ScrollArea/index.tsx | 7 ++--- src/components/ScrollArea/style.ts | 13 ++++++++++ src/hooks/useDelayState.ts | 11 ++++++++ src/style/App.style.ts | 40 +++++++++++++++++++++++++++++ src/style/App.transition.ts | 26 +++++++++++++++++++ 7 files changed, 106 insertions(+), 7 deletions(-) create mode 100644 src/hooks/useDelayState.ts create mode 100644 src/style/App.transition.ts diff --git a/src/app/api/sentence/route.ts b/src/app/api/sentence/route.ts index 428981e..2824adf 100644 --- a/src/app/api/sentence/route.ts +++ b/src/app/api/sentence/route.ts @@ -8,7 +8,7 @@ export async function GET() { } catch (error) { console.error('Error in sentence API:', error); return NextResponse.json( - { error: '문장을 불러오는데 실패했습니다.' }, + { result: false,error: '문장을 불러오는데 실패했습니다.' }, { status: 500 } ); } diff --git a/src/app/page.tsx b/src/app/page.tsx index fbb825f..1f0a691 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -6,6 +6,7 @@ import * as S from '@/style/App.style'; // hooks import { useBookInfo } from "@/hooks/useBookInfo"; +import { useDelayState } from "@/hooks/useDelayState"; // components import ScrollArea from "@/components/ScrollArea"; @@ -16,6 +17,7 @@ import LinkButton from "@/components/LinkButton"; export default function Home() { const [loading, error, book_info] = useBookInfo(); + const delay_loading = useDelayState(loading, 200); const [scroll, setScroll] = useState(0); const button_link = useMemo(() => { @@ -27,9 +29,15 @@ export default function Home() { <S.StyledSection> <S.TopLogo src="/ajoulib_logo_4x.png" alt="AjouLib Logo" /> <PageTitleArea scroll={scroll}/> - <ScrollArea book_info={book_info} onScroll={setScroll}/> - <LinkButton scroll={scroll} href={button_link}/> - <StyleElements scroll={scroll}/> + <S.LoadingCharacterContainer className={!loading ? "fading" : ""}> + <S.LoadingCharacter src="/ajoulib_reading_chito.png" alt="AjouLib Chito"/> + </S.LoadingCharacterContainer> + <ScrollArea + book_info={!delay_loading ? book_info : null} onScroll={setScroll} + className={!loading ? "visibling" : ""} + /> + <LinkButton scroll={scroll} href={button_link}/> + <StyleElements scroll={scroll}/> </S.StyledSection> </S.AppSection> </S.GlobalSection>; diff --git a/src/components/ScrollArea/index.tsx b/src/components/ScrollArea/index.tsx index 27fccbc..2d3d13d 100644 --- a/src/components/ScrollArea/index.tsx +++ b/src/components/ScrollArea/index.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, HTMLAttributes } from "react"; // style import * as S from "./style"; @@ -15,9 +15,9 @@ import TextArea from "@/components/TextArea"; type ScrollAreaProps = { book_info: BookInfo | null; onScroll: (v: number) => void; -} +} & Omit<HTMLAttributes<HTMLTableSectionElement>, "onScroll">; -const ScrollArea: React.FC<ScrollAreaProps> = ({ book_info, onScroll }) => { +const ScrollArea: React.FC<ScrollAreaProps> = ({ book_info, onScroll, ...props }) => { const [scroll, handleScroll] = useScrollState(); @@ -28,6 +28,7 @@ const ScrollArea: React.FC<ScrollAreaProps> = ({ book_info, onScroll }) => { return <S.ScrollAreaWrap onScroll={handleScroll} style={{marginTop: `-${Math.min(scroll/2, 30)}%`}} + {...props} > <IntroArea topRate={scroll} info={book_info}/> <TextArea topRate={scroll} sentence={book_info?.sentence || ""}/> diff --git a/src/components/ScrollArea/style.ts b/src/components/ScrollArea/style.ts index 3da2246..2ff722c 100644 --- a/src/components/ScrollArea/style.ts +++ b/src/components/ScrollArea/style.ts @@ -8,6 +8,19 @@ export const ScrollAreaWrap = styled.section` overflow-y: auto; + opacity: 0; + transform: scale(0.9); + transition: opacity .2s cubic-bezier(0, 1, 1, 1); + + &.visibling { + opacity: 1; + transform: scale(1); + } + + &::-webkit-scrollbar { + display: none; + } + @media (min-height: 760px) { margin-top: 0 !important; } diff --git a/src/hooks/useDelayState.ts b/src/hooks/useDelayState.ts new file mode 100644 index 0000000..0d30ad6 --- /dev/null +++ b/src/hooks/useDelayState.ts @@ -0,0 +1,11 @@ +import { useEffect } from "react"; + +import { useState } from "react"; + +export const useDelayState = (initialState: any, delay: number) => { + const [state, setState] = useState(initialState); + useEffect(() => { + setTimeout(() => setState(initialState), delay); + }, [initialState, delay]); + return state; +}; \ No newline at end of file diff --git a/src/style/App.style.ts b/src/style/App.style.ts index eeb6fc6..0f4c236 100644 --- a/src/style/App.style.ts +++ b/src/style/App.style.ts @@ -1,4 +1,5 @@ import styled from "styled-components"; +import { breathe, breathe_out } from "./App.transition"; export const GlobalSection = styled.section` width: 100vw; @@ -24,6 +25,45 @@ export const AppSection = styled.section` top: 50%; left: 50%; transform: translate(-50%, -50%); + + overflow: hidden; + + @media screen and (max-width: 650px) { + width: 100%; + height: 100%; + border-radius: 0; + } +`; + +export const LoadingCharacterContainer = styled.section` + width: 12rem; + height: 12rem; + + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + opacity: 1; + transition: opacity .2s cubic-bezier(0, 1, 1, 1); + + &.fading { + opacity: 0; + } +`; + +export const LoadingCharacter = styled.img` + width: 12rem; + + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + animation: ${breathe} 1.5s cubic-bezier(0, 1, 1, 1) infinite; + transition: opacity 2s ease-in-out, animation 0.2s ease-out; + + z-index: 1; `; export const StyledSection = styled.section` diff --git a/src/style/App.transition.ts b/src/style/App.transition.ts new file mode 100644 index 0000000..45537da --- /dev/null +++ b/src/style/App.transition.ts @@ -0,0 +1,26 @@ +import { keyframes } from "styled-components"; + +export const breathe = keyframes` + 0% { + transform: translate(-50%, -50%) scale(0.9); + opacity: 0.3; + } + 50% { + transform: translate(-50%, -50%) scale(1); + opacity: 1; + } + 100% { + transform: translate(-50%, -50%) scale(0.9); + opacity: 0.3; + } +`; + +export const breathe_out = keyframes` + 99% { + transform: translate(-50%, -50%) scale(0.9); + opacity: 0; + } + 100% { + display: none; + } +`; \ No newline at end of file -- GitLab