From 6494bb23147ec3cc6ec2d9530f4eb8444866cdff Mon Sep 17 00:00:00 2001 From: solo5star Date: Thu, 21 Sep 2023 19:43:55 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=EB=A8=BC=EC=A0=80=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=EB=90=98=EB=8A=94=20=EC=9D=B4=EB=AF=B8=EC=A7=80?= =?UTF-8?q?=EB=A5=BC=20=EB=A8=BC=EC=A0=80=20=EB=8B=A4=EC=9A=B4=EB=B0=9B?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/ScrollSnapContainer.tsx | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/client/src/components/ScrollSnapContainer.tsx b/client/src/components/ScrollSnapContainer.tsx index fbec07a1..3194c2b2 100644 --- a/client/src/components/ScrollSnapContainer.tsx +++ b/client/src/components/ScrollSnapContainer.tsx @@ -1,5 +1,5 @@ import type React from 'react'; -import type { HTMLAttributes, MouseEventHandler, TouchEventHandler, WheelEventHandler } from 'react'; +import type { HTMLAttributes, MouseEventHandler, PropsWithChildren, TouchEventHandler, WheelEventHandler } from 'react'; import { useEffect, useRef, useState } from 'react'; import styled from 'styled-components'; @@ -66,6 +66,29 @@ const SWIPE_FAST_SCROLL_DISTANCE_RATIO = 0.03; // 0 = 다음 아이템으로 완전히 넘어가야 스와이프 판정 가능 const SWIPE_WHEEL_SCROLL_VALID_RATIO = 0.1; +type ScrollSnapVirtualItemProps = PropsWithChildren<{ + // 이전 아이템인지, 현재 아이템인지, 이후 아이템인지 여부를 나타내는 숫자 + offset: -1 | 0 | 1; + // 0.0 ~ 1.0 + position: number; +}>; + +const ScrollSnapVirtualItem = (props: ScrollSnapVirtualItemProps) => { + const { offset, position, children } = props; + + return ( +
+ {children} +
+ ); +}; + type ScrollSnapVirtualItemsProps = { scrollPosition: number; items: Item[]; @@ -95,10 +118,16 @@ const ScrollSnapVirtualItems = (props: ScrollSnapVirtualItemsProps) style={{ width: '100%', height: '100%', - transform: `translateY(${-100 + -visiblePosition * 100}%)`, + display: 'grid', }} > - {visibleItems.map(({ item, index }) => itemRenderer(item, index))} + {([0, 1, -1] as const) + .map((visibleIndex) => ({ ...visibleItems[1 + visibleIndex], visibleIndex })) + .map(({ item, index, visibleIndex }) => ( + + {itemRenderer(item, index)} + + ))} ); }; From 6a2f71f2f120b39fae5650f245afa8ae183b0c88 Mon Sep 17 00:00:00 2001 From: solo5star Date: Thu, 21 Sep 2023 19:47:14 +0900 Subject: [PATCH 2/4] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20`useEffect`=20=EC=BD=94=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/CafeCard.tsx | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/client/src/components/CafeCard.tsx b/client/src/components/CafeCard.tsx index bfd80de7..27bfb4ba 100644 --- a/client/src/components/CafeCard.tsx +++ b/client/src/components/CafeCard.tsx @@ -1,4 +1,5 @@ -import { useEffect, useRef, useState } from 'react'; +import type { UIEventHandler } from 'react'; +import { useCallback, useState } from 'react'; import { styled } from 'styled-components'; import type { Cafe } from '../types'; import Image from '../utils/Image'; @@ -16,21 +17,12 @@ const CafeCard = (props: CardProps) => { const [isShowDetail, setIsShowDetail] = useState(false); const [currentImageIndex, setCurrentImageIndex] = useState(0); - const ref = useRef(null); - - useEffect(() => { - const handleScroll = () => { - if (ref.current) { - const { scrollLeft, clientWidth } = ref.current; - const index = Math.round(scrollLeft / clientWidth); - setCurrentImageIndex(index); - } - }; - - ref.current?.addEventListener('scroll', handleScroll); - return () => { - ref.current?.removeEventListener('scroll', handleScroll); - }; + const handleScroll: UIEventHandler = useCallback((event) => { + if (!(event.target instanceof HTMLDivElement)) return; + + const { scrollLeft, clientWidth } = event.target; + const index = Math.round(scrollLeft / clientWidth); + setCurrentImageIndex(index); }, []); return ( @@ -40,7 +32,7 @@ const CafeCard = (props: CardProps) => { {`${currentImageIndex + 1}`}/{cafe.images.length} - + {cafe.images.map((image, index) => ( ))} From a6edd2e3279fb008bb1dc7922b7dceb8e03fc09c Mon Sep 17 00:00:00 2001 From: solo5star Date: Thu, 21 Sep 2023 19:50:02 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EB=B3=B4=EA=B3=A0=EC=9E=88=EB=8A=94=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=EC=99=80=20=EA=B1=B0=EB=A6=AC=EA=B0=80=20?= =?UTF-8?q?=EB=A9=80=EB=A9=B4=20`loading=3D"lazy"`=20=EA=B0=80=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=EB=90=98=EB=8F=84=EB=A1=9D=20=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/CafeCard.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/src/components/CafeCard.tsx b/client/src/components/CafeCard.tsx index 27bfb4ba..df3c7ffc 100644 --- a/client/src/components/CafeCard.tsx +++ b/client/src/components/CafeCard.tsx @@ -34,7 +34,12 @@ const CafeCard = (props: CardProps) => { {cafe.images.map((image, index) => ( - + ))} From a4fef48fe5edf50aacfc004e4a2e21f3cf260904 Mon Sep 17 00:00:00 2001 From: solo5star Date: Thu, 21 Sep 2023 20:04:26 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C?= =?UTF-8?q?=20=EC=95=84=EC=9D=B4=ED=85=9C=EC=9D=B4=20=EB=A0=8C=EB=8D=94?= =?UTF-8?q?=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/components/ScrollSnapContainer.tsx | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/client/src/components/ScrollSnapContainer.tsx b/client/src/components/ScrollSnapContainer.tsx index 3194c2b2..a45eee87 100644 --- a/client/src/components/ScrollSnapContainer.tsx +++ b/client/src/components/ScrollSnapContainer.tsx @@ -93,10 +93,11 @@ type ScrollSnapVirtualItemsProps = { scrollPosition: number; items: Item[]; itemRenderer: (item: Item, index: number) => React.ReactNode; + enableRolling?: boolean; }; const ScrollSnapVirtualItems = (props: ScrollSnapVirtualItemsProps) => { - const { scrollPosition, items, itemRenderer } = props; + const { scrollPosition, items, itemRenderer, enableRolling } = props; // position of item, which user sees // always positive integer @@ -106,11 +107,17 @@ const ScrollSnapVirtualItems = (props: ScrollSnapVirtualItemsProps) const indexedItems = items.map((item, index) => ({ index, item })); // 현재 화면의 아이템 및 위, 아래의 아이템을 표시한다 - const visibleItems = [ - indexedItems[focusedIndex - 1] ?? indexedItems[indexedItems.length - 1], - indexedItems[focusedIndex], - indexedItems[focusedIndex + 1] ?? indexedItems[0], - ]; + const visibleItems = enableRolling + ? [ + indexedItems.at(focusedIndex - 1), // 현재에서 상단 (혹은 끝) 아이템 + indexedItems.at(focusedIndex), // 현재 아이템 + indexedItems.at((focusedIndex + 1) % indexedItems.length), // 현재에서 하단 (혹은 첫) 아이템 + ] + : [ + indexedItems[focusedIndex - 1], // 현재에서 상단 아이템 + indexedItems[focusedIndex], // 현재 아이템 + indexedItems[focusedIndex + 1], // 현재에서 하단 아이템 + ]; const visiblePosition = mod(scrollPosition, 1); return ( @@ -123,11 +130,15 @@ const ScrollSnapVirtualItems = (props: ScrollSnapVirtualItemsProps) > {([0, 1, -1] as const) .map((visibleIndex) => ({ ...visibleItems[1 + visibleIndex], visibleIndex })) - .map(({ item, index, visibleIndex }) => ( - - {itemRenderer(item, index)} - - ))} + .map(({ item, index, visibleIndex }) => + item && typeof index === 'number' ? ( + + {itemRenderer(item, index)} + + ) : ( + + ), + )} ); }; @@ -410,7 +421,7 @@ const ScrollSnapContainer = (props: ScrollSnapContainerProps) => { onMouseLeave={handleMouseLeave} onWheel={handleWheel} > - + ); };