diff --git a/src/components/IntroArea/index.tsx b/src/components/IntroArea/index.tsx index e42062c0415b2f180747230c452f209459b34ae4..e3ccb5ccb39a74c4bebf41690ef3b56e7f4b0c6c 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 2da1f9cadec950476be0fcb38c99648014421957..cfe59aaa0d64148fc978ea6e65269c909cff884b 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 2d3d13d0c2ef6b3f2cfc4b68edf8f86b1cce7b2f..fbac8161113d36156024c37dfb810c7ca332224b 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 2ff722c7737baa2b3b56ad7524b86f777a2c4962..3f97fff5930b43e4f107708f0347419cbc0debc4 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 4ddfc79690bd600af227f25ca65c29eda3a04029..d72e32d1f57b1eb576d046f36942f53acb323ea2 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 a8932ffa58ff5a07354bb724bec68436a5ba8af1..da62fdaff77e6046afe04ead956f36762a2e4020 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 707f1335f180e0db63c7931c0f9bbd60abdae9d0..0a17d5d34c306c827041ca4ee794c7edf7f6ac58 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 136a623c3d9f3859bc2b54d33a99439ff11c811f..7fbc9f41ead243596019c5b9216c8fe08b5d4bb9 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