From a3f607e5649be8fd4be2c7b7b6942e650e07b952 Mon Sep 17 00:00:00 2001 From: HyerinJeon Date: Wed, 13 Mar 2024 10:57:45 +0900 Subject: [PATCH] Design & Feat horizontal scroll in Main Page --- src/pages/Main.tsx | 75 +++++++++++++++++++++++++++----- src/styles/Main.styles.ts | 90 +++++++++++++++++++++++++-------------- src/utils/throttle.ts | 13 ++++++ 3 files changed, 135 insertions(+), 43 deletions(-) create mode 100644 src/utils/throttle.ts diff --git a/src/pages/Main.tsx b/src/pages/Main.tsx index 4b7ed88..8421ae1 100644 --- a/src/pages/Main.tsx +++ b/src/pages/Main.tsx @@ -1,26 +1,79 @@ import * as S from "../styles/Main.styles"; -import { useEffect } from "react"; +import { MouseEvent, useEffect, useRef, useState } from "react"; import { setVH } from "../utils/setVH"; +import { throttle } from "../utils/throttle"; export default function Main() { + const trainerList = [ + { id: "1", imgUrl: "", info: "설명" }, + { id: "2", imgUrl: "", info: "설명" }, + { id: "3", imgUrl: "", info: "" }, + { id: "4", imgUrl: "", info: "" }, + { id: "5", imgUrl: "", info: "" }, + { id: "6", imgUrl: "", info: "" }, + { id: "7", imgUrl: "", info: "" }, + ]; + + const [isDrag, setIsDrag] = useState(false); + const [startX, setStartX] = useState(0); + + const trainerScrollRef = useRef(null); + useEffect(() => { window.addEventListener("resize", setVH); setVH(); }, []); + const handleDragStart = (e: MouseEvent) => { + e.preventDefault(); + if (trainerScrollRef.current) { + setIsDrag(true); + setStartX(e.pageX + trainerScrollRef.current.scrollLeft); + } + }; + + const handleDragEnd = () => { + setIsDrag(false); + }; + + const handleDragMove = (e: MouseEvent) => { + if (isDrag && trainerScrollRef.current) { + const { scrollWidth, clientWidth, scrollLeft } = trainerScrollRef.current; + + trainerScrollRef.current.scrollLeft = startX - e.pageX; + + if (scrollLeft === 0) { + setStartX(e.pageX); + } else if (scrollWidth <= clientWidth + scrollLeft) { + setStartX(e.pageX + scrollLeft); + } + } + }; + + const throttleDragMove = throttle(handleDragMove, 50); + return ( - - - 이용 후기 - - - - 이번주 예약 내역 - - - + + 트레이너 소개 + + {trainerList.map((trainer) => ( + + + + + {trainer.info} + + ))} + + ); } diff --git a/src/styles/Main.styles.ts b/src/styles/Main.styles.ts index 04d9e18..667888e 100644 --- a/src/styles/Main.styles.ts +++ b/src/styles/Main.styles.ts @@ -4,67 +4,93 @@ import { breakPoints } from "./breakPoints"; export const Container = styled.div` display: flex; flex-direction: column; + justify-content: space-between; width: 100%; height: calc((var(--vh, 1vh) * 100) - 90px); `; export const Banner = styled.div` width: 100%; - height: 35%; + height: 40%; border: 1px solid gray; - - @media screen and (max-width: ${breakPoints.medium}px) { - height: 27%; - } `; -export const BottomContainer = styled.div` - position: absolute; - bottom: 0; +export const TrainerContainer = styled.div` display: flex; - justify-content: space-between; - align-items: center; + flex-direction: column; width: 100%; height: 50%; + gap: 5%; +`; + +export const Title = styled.div` + font-size: var(--font-size-300); + font-weight: 700; + height: 5%; + padding: 0 5px; @media screen and (max-width: ${breakPoints.medium}px) { - flex-direction: column; - justify-content: flex-start; - align-items: flex-start; - height: 57%; - gap: 20px; + font-size: var(--font-size-400); + } + + @media screen and (max-width: ${breakPoints.small}px) { + font-size: var(--font-size-500); } `; -export const HalfContainer = styled.div` +export const ContentsContainer = styled.div` display: flex; - flex-direction: column; - justify-content: flex-end; - width: 48%; - height: 100%; - gap: 20px; + align-items: center; + height: 90%; + padding: 10px; + gap: 15px; + border: 1px solid gray; + overflow-x: scroll; + scroll-behavior: smooth; + -webkit-overflow-scrolling: touch; - @media screen and (max-width: ${breakPoints.medium}px) { - width: 100%; - gap: 10px; + &::-webkit-scrollbar { + display: none; } `; -export const Title = styled.div` - font-size: var(--font-size-300); - font-weight: 700; +export const Grid = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + min-width: 400px; + height: 80%; + padding: 10px; + border: 1px solid gray; + border-radius: 10px; - @media screen and (max-width: ${breakPoints.medium}px) { - font-size: var(--font-size-400); + @media screen and (max-width: ${breakPoints.small}px) { + min-width: 300px; } +`; + +export const ImageContainer = styled.div` + width: 35%; + height: 50%; + border-radius: 50%; + overflow: hidden; + border: 1px solid gray; @media screen and (max-width: ${breakPoints.small}px) { - font-size: var(--font-size-500); + width: 25%; + height: 30%; } `; -export const ContentsContainer = styled.div` - width: 100%; +export const InfoContainer = styled.div` + width: 57%; height: 80%; + padding: 10px; + font-size: var(--font-size-400); border: 1px solid gray; + + @media screen and (max-width: ${breakPoints.small}px) { + width: 65%; + font-size: var(--font-size-500); + } `; diff --git a/src/utils/throttle.ts b/src/utils/throttle.ts new file mode 100644 index 0000000..fff406b --- /dev/null +++ b/src/utils/throttle.ts @@ -0,0 +1,13 @@ +export const throttle = (func: (args: any) => void, time: number) => { + let throttled: boolean = false; + + return (args: any) => { + if (!throttled) { + throttled = true; + setTimeout(() => { + func(args); + throttled = false; + }, time); + } + }; +};