From fa4798d857c63e8726bf8bf4ef2ab8cc5cec6f6e Mon Sep 17 00:00:00 2001 From: nate2402 <nate2402@ajou.ac.kr> Date: Thu, 8 May 2025 03:16:49 +0900 Subject: [PATCH] feat: update book title & content not be shorten --- src/components/IntroArea/index.tsx | 15 +++---- src/components/IntroArea/style.ts | 32 +++++++++++---- src/components/ScrollArea/index.tsx | 64 +++++++++++++++++++++++++---- src/components/ScrollArea/style.ts | 24 ++++++++--- src/components/TextArea/index.tsx | 19 +++++++-- src/components/TextArea/style.ts | 9 ++-- src/hooks/useBookInfo.ts | 8 ++++ src/hooks/useScrollState.ts | 13 +++--- 8 files changed, 141 insertions(+), 43 deletions(-) diff --git a/src/components/IntroArea/index.tsx b/src/components/IntroArea/index.tsx index e42062c..e3ccb5c 100644 --- a/src/components/IntroArea/index.tsx +++ b/src/components/IntroArea/index.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from "react"; +import { useState, useEffect, useRef, forwardRef, RefObject } from "react"; // style import * as S from "./style"; @@ -8,16 +8,17 @@ import { BookInfo } from "@/types"; // interfaces type IntroAreaProps = { - topRate: number, + scroll_rate: number, info: BookInfo | null } -const IntroArea: React.FC<IntroAreaProps> = ({ topRate, info }) => { +const IntroArea = forwardRef<HTMLElement, IntroAreaProps>(({ scroll_rate, info }, ref) => { + if (!info) return <></>; - return <S.IntroAreaWrap style={{ - opacity: topRate / 100, - transform: `translateY(calc(-100% + ${topRate*6/10}%))` + return <S.IntroAreaWrap ref={ref} style={{ + opacity: `${scroll_rate/100}`, + top: `calc(${-2 * (100-scroll_rate) / 100}rem)` }}> <h3>오늘의 책</h3> <S.BookTitleLoc> @@ -29,6 +30,6 @@ const IntroArea: React.FC<IntroAreaProps> = ({ topRate, info }) => { <span>{info.code}</span> </S.BookLocSpec> </S.IntroAreaWrap> -}; +}); export default IntroArea \ No newline at end of file diff --git a/src/components/IntroArea/style.ts b/src/components/IntroArea/style.ts index 2da1f9c..cfe59aa 100644 --- a/src/components/IntroArea/style.ts +++ b/src/components/IntroArea/style.ts @@ -1,6 +1,8 @@ import styled from "styled-components"; export const IntroAreaWrap = styled.section` + width: 100%; + display: flex; flex-direction: column; align-items: flex-start; @@ -9,9 +11,9 @@ export const IntroAreaWrap = styled.section` font-family: var(--font-ajou), sans-serif; - position: sticky; - top: 25%; - transform: translateY(-50%); + position: absolute; + left: 50%; + transform: translate(-50%); z-index: 100; `; @@ -27,9 +29,10 @@ export const BookTitleLoc = styled.section` font-size: 2rem; font-weight: 700; color: #1361A7; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + /* white-space: nowrap; */ + word-break: keep-all; + /* overflow: hidden; */ + /* text-overflow: ellipsis; */ width: 100%; } @@ -37,11 +40,22 @@ export const BookTitleLoc = styled.section` font-size: 1.25rem; font-weight: 300; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + /* white-space: nowrap; */ + word-break: keep-all; + /* overflow: hidden; */ + /* text-overflow: ellipsis; */ width: 100%; } + + @media (max-height: 760px) { + & h2 { + font-size: 1.5rem !important; + } + + & h3 { + font-size: 1rem !important; + } + } `; export const BookLocSpec = styled.section` diff --git a/src/components/ScrollArea/index.tsx b/src/components/ScrollArea/index.tsx index 2d3d13d..fbac816 100644 --- a/src/components/ScrollArea/index.tsx +++ b/src/components/ScrollArea/index.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef, HTMLAttributes } from "react"; +import { useState, useEffect, useRef, HTMLAttributes, useMemo } from "react"; // style import * as S from "./style"; @@ -18,21 +18,67 @@ type ScrollAreaProps = { } & Omit<HTMLAttributes<HTMLTableSectionElement>, "onScroll">; const ScrollArea: React.FC<ScrollAreaProps> = ({ book_info, onScroll, ...props }) => { + + const cover_ref = useRef<HTMLTableSectionElement>(null); + const introarea_ref = useRef<HTMLTableSectionElement>(null); + const textarea_ref = useRef<HTMLTableSectionElement>(null); - const [scroll, handleScroll] = useScrollState(); + const [total_scroll, setTotalScroll] = useState(0); + const [scroll_rate, scroll_overflow, handleScrollRate] = useScrollState(); useEffect(() => { - onScroll(scroll); - }, [scroll]); + // console.log("scroll", scroll); + onScroll(scroll_rate); + }, [scroll_rate]); + + useEffect(() => { + if (scroll_overflow <= 0) return; + if (!cover_ref?.current) return; + cover_ref.current.scrollTop = scroll_overflow; + }, [scroll_overflow]); + + // window resize 이벤트에 대응하는 useEffect + useEffect(() => { + const handleResize = () => { + if (textarea_ref.current && cover_ref.current) { + const infoarea_height = introarea_ref.current?.clientHeight || 0; + const cover_height = cover_ref.current.clientHeight; + const textarea_height = textarea_ref.current.clientHeight; + + const calced_total_scroll = Math.max( + (cover_height / 2) + infoarea_height + (textarea_height*3/2), + cover_height * 1.5 + ); + + setTotalScroll(calced_total_scroll); + } + }; + + handleResize(); + + window.addEventListener('resize', handleResize); + return () => { + window.removeEventListener('resize', handleResize); + }; + }, [book_info, scroll_rate]); return <S.ScrollAreaWrap - onScroll={handleScroll} - style={{marginTop: `-${Math.min(scroll/2, 30)}%`}} + onScroll={handleScrollRate} + style={{marginTop: `-${Math.min(scroll_rate/2, 30)}%`}} {...props} > - <IntroArea topRate={scroll} info={book_info}/> - <TextArea topRate={scroll} sentence={book_info?.sentence || ""}/> - <S.FakeScrollArea/> + <S.ScrollCover ref={cover_ref}> + <IntroArea ref={introarea_ref} + scroll_rate={scroll_rate} + info={book_info} + /> + <TextArea ref={textarea_ref} + top_margin={introarea_ref.current?.clientHeight || 0} + scroll_rate={scroll_rate} + sentence={book_info?.sentence || ""} + /> + </S.ScrollCover> + <S.FakeScrollArea style={{ height: `${total_scroll}px` }}/> </S.ScrollAreaWrap> }; diff --git a/src/components/ScrollArea/style.ts b/src/components/ScrollArea/style.ts index 2ff722c..3f97fff 100644 --- a/src/components/ScrollArea/style.ts +++ b/src/components/ScrollArea/style.ts @@ -2,6 +2,7 @@ import styled from "styled-components"; export const ScrollAreaWrap = styled.section` width: 100%; + position: relative; flex: 1; height: auto; @@ -17,16 +18,29 @@ export const ScrollAreaWrap = styled.section` transform: scale(1); } - &::-webkit-scrollbar { - display: none; - } - @media (min-height: 760px) { margin-top: 0 !important; } `; +export const ScrollCover = styled.section` + width: 100%; + height: 100%; + + position: sticky; + top: 0; + left: 0; + + overflow-y: hidden; + + + &::-webkit-scrollbar { + display: none; + } +`; + export const FakeScrollArea = styled.div` width: 100%; - height: calc(150%); + height: 150%; + z-index: 10000; `; \ No newline at end of file diff --git a/src/components/TextArea/index.tsx b/src/components/TextArea/index.tsx index 4ddfc79..d72e32d 100644 --- a/src/components/TextArea/index.tsx +++ b/src/components/TextArea/index.tsx @@ -1,15 +1,26 @@ 'use client'; +import { forwardRef } from "react"; import * as S from "./style"; -export default function TextArea({ topRate, sentence }: { topRate: number, sentence: string }) { +type TextAreaProps = { + top_margin: number, + scroll_rate: number, + sentence: string, +}; + +const TextArea = forwardRef<HTMLElement, TextAreaProps>(({ top_margin, scroll_rate, sentence }, ref) => { return ( - <S.TextContainer style={{ - transform: `translateY(calc(-50% + ${topRate*6/10}%))` || undefined + <S.TextContainer ref={ref} style={{ + // top: `calc(${ (top_margin + 10) * scroll_rate / 100 }px + ${50*(100-(scroll_rate/2))/100}%)` + top: `calc(${ (top_margin + 36) * scroll_rate / 100 }px + ${50*(100-(scroll_rate))/100}%)`, + transform: `translate(-50%, ${-50 * (100-scroll_rate) / 100}%)` }}> <S.OpenQuota src="/quota_open.png" alt="Open Quota" /> <S.Content>{sentence}</S.Content> <S.CloseQuota src="/quota_close.png" alt="Close Quota" /> </S.TextContainer> ); -} \ No newline at end of file +}); + +export default TextArea; \ No newline at end of file diff --git a/src/components/TextArea/style.ts b/src/components/TextArea/style.ts index a8932ff..da62fda 100644 --- a/src/components/TextArea/style.ts +++ b/src/components/TextArea/style.ts @@ -3,15 +3,16 @@ import styled from "styled-components"; export const TextContainer = styled.section` min-width: 2rem; min-height: 2rem; + width: 100%; display: flex; justify-content: center; align-items: center; - position: sticky; + position: absolute; top: 50%; left: 50%; - transform: translate(0, -50%); + transform: translate(-50%, -50%); `; export const Content = styled.span` @@ -23,10 +24,10 @@ export const Content = styled.span` line-height: 1.5; color: #333; - display: -webkit-box; + /* display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; - overflow: hidden; + overflow: hidden; */ `; export const OpenQuota = styled.img` diff --git a/src/hooks/useBookInfo.ts b/src/hooks/useBookInfo.ts index 707f133..0a17d5d 100644 --- a/src/hooks/useBookInfo.ts +++ b/src/hooks/useBookInfo.ts @@ -25,6 +25,14 @@ export const useBookInfo = (): [boolean, string | null, BookInfo | null] => { useEffect(() => { fetchSentence(); + // setBookInfo({ + // "sentence": "베스트셀러 범죄소설가가 되었을 때 소소한 위험 요소가 있다면 어딜 가나 이런 질문을 받는다는 것이다.베스트셀러 범죄소설가가 되었을 때 소소한 위험 요소가 있다면 어딜 가나 이런 질문을 받는다는 것이다.", + // "title": "겨우살이 살인사건겨우살이 살인사건겨우살이 살인사건겨우살이 살인사건겨우살이 살인사건겨우살이 살인사건", + // "author": "James, P. D", + // "location": "1층.큐레이션", + // "code": "823.914 J28mK이" + // }) + // setLoading(false); }, []); return [loading, error, book_info]; diff --git a/src/hooks/useScrollState.ts b/src/hooks/useScrollState.ts index 136a623..7fbc9f4 100644 --- a/src/hooks/useScrollState.ts +++ b/src/hooks/useScrollState.ts @@ -1,12 +1,15 @@ import { useState } from "react"; -export const useScrollState = (): [number, (e: React.UIEvent<HTMLElement>) => void] => { - const [scroll, setScroll] = useState(0); +export const useScrollState = (): [number, number, (e: React.UIEvent<HTMLElement>) => void] => { + const [scroll_overflow, setScrollOverflow] = useState(0); + const [scroll_percent, setScrollPercent] = useState(0); const handleScroll = (e: React.UIEvent<HTMLElement>) => { - const percent = (e.currentTarget.scrollTop / (e.currentTarget.scrollHeight - e.currentTarget.clientHeight)) * 100; - setScroll(percent); + const full_scroll = e.currentTarget.clientHeight * 1.5; + const percent = (e.currentTarget.scrollTop / full_scroll) * 100; + setScrollPercent(Math.min(percent, 100)); + setScrollOverflow(Math.max(0, e.currentTarget.scrollTop - full_scroll)); } - return [scroll, handleScroll]; + return [scroll_percent, scroll_overflow, handleScroll]; } \ No newline at end of file -- GitLab