[React] ์บ๋ก์ (Carousel) ์๋ ๋ฐฉ์ ์ดํด๋ณด๊ธฐ
์บ๋ก์ ๊ตฌ์กฐ
๋ง์ ์น์ฌ์ดํธ์์ ์ฌ๋ฌ ์ด๋ฏธ์ง๋ฅผ ์ฌ๋ผ์ด๋ ํ์์ผ๋ก ๋ณด์ฌ์ฃผ๊ธฐ ์ํด ์ฌ์ฉํ๋ ์บ๋ก์ ๋ทฐ์ด๋ ์๊ฐ๋ณด๋ค ๊ฐ๋จํ๊ฒ ๊ตฌํํ ์ ์๋ค. ์บ๋ก์ ๋ทฐ์ด์ DOM ๊ตฌ์กฐ๋ ๋๋ต ์๋์ ๊ฐ๋ค.
<div> <!-- ์บ๋ก์
์์ดํ
Wrapper -->
<div> <!-- ์บ๋ก์
์์ดํ
Parent -->
<div /> <!-- ์บ๋ก์
์์ดํ
A -->
<div /> <!-- ์บ๋ก์
์์ดํ
B -->
<div /> <!-- ์บ๋ก์
์์ดํ
C -->
</div>
</div>
- ์บ๋ก์
์์ดํ
Wrapper : ๋์นจ ์์ญ ์จ๊น ์ฒ๋ฆฌ
overflow: hidden; width: 100%; height: 100%;
- ์บ๋ก์
์์ดํ
Parent : ์ฌ๋ฌ๊ฐ์ ์บ๋ก์
์์ดํ
์ ๊ฐ์ธ๋ ๋ถ๋ชจ โก๏ธ
- ์บ๋ก์ ์์ดํ ๋ค์ ์ํ ์์์ ์ํด Flexbox ๋ ์ด์์ ์ ์ฉ
- ์คํฌ๋กค๋ฐ ์จ๊น ์ฒ๋ฆฌ
- ์ ํ ํจ๊ณผ(transition)
- ๋ค์ ๋ฒํผ์ ๋๋ฅผ ๋๋ง๋ค ์ข์ธก์ผ๋ก ์ด๋
transform: translateX(-100%|-200%|...)
- ์บ๋ก์
์์ดํ
: 1๊ฐ ์์ดํ
๋ง ๋ณด์ด๋๋ก ์ฒ๋ฆฌ
width: 100%; flex-grow: 1; flex-shrink: 0;
๊ทธ๋ฆผ์ผ๋ก ํํํ๋ฉด ์๋ ๊ฐ์ ๊ตฌ์กฐ๊ฐ ๋๋ค. ๊ฐ ์บ๋ก์
์์ดํ
์ ํญ์ด 100%
(Parent ์์ ๋๋น)์ด๋ฏ๋ก ํ๋ฉด์ 1๊ฐ ์์ดํ
๋ง ๋ณด์ธ๋ค(Item A). Parent ์์์ translateX
๊ฐ์ -100%
๋ก ์ค์ ํ๋ฉด ์์ ๋๋น ๋งํผ ์ข์ธก์ผ๋ก ์ด๋ํด์ ๋๋ฒ์งธ ์์ดํ
(Item B)์ด ํ๋ฉด์ ๋ณด์ธ๋ค. ์ฌ๊ธฐ์ transition: transform 250ms linear;
๊ฐ์ ์์ฑ์ ์ฃผ๋ฉด ์บ๋ก์
์์ดํ
์ด ์์ง์ด๋ ๋ฏํ ํจ๊ณผ๋ฅผ ์ค ์ ์๋ค.
transform: translateX(-0%)
: Item A ํ๋ฉด์ ํ์transform: translateX(-100%)
: Item B ํ๋ฉด์ ํ์transform: translateX(-200%)
: Item C ํ๋ฉด์ ํ์
React์์ ๊ตฌํ
React์์ ํ์ฌ ์์ดํ
์ ๋ํ Index๋ฅผ ์ํ๋ก ๋๊ณ (currentIndex
) 100์ ๊ณฑํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ ์ ์๋ค. Index ์ํ๋ Next ๋ฒํผ์ ๋๋ฅด๋ฉด 1์ฉ ๋์ด๋๊ณ , Prev ๋ฒํผ์ ๋๋ฅด๋ฉด 1์ฉ ๊ฐ์ํ๋ค.
const [currentIndex, setCurrentIndex] = useState(0);
// Item A : Index 0 -> translateX(-0%);
// Item B : Index 1 -> translateX(-100%);
// Item C : Index 2 -> translateX(-200%);
// ...
return (
<div // ์บ๋ก์
์์ดํ
Parent
className="flex no-scrollbar transition-all ease-linear"
style={{
transform: `translateX(-${currentIndex * 100}%)`,
transitionDuration: `${speed}ms`,
}}
>
{children} {/* ์บ๋ก์
์์ดํ
*/}
</div>
);
์์ดํ width์ translateX ๊ด๊ณ โก๏ธ
ํ๋ฉด์ ํ์๋๋ ์์ดํ ๊ฐ์๋ฅผ ๋ณ๊ฒฝํ๊ณ ์ถ์ผ๋ฉด ์์ดํ ์ ํญ์ ์์ ํ๋ค. ์ฌ๋ผ์ด๋ ํ ๋์ ์์ดํ ๊ฐ์๋ฅผ ๋ณ๊ฒฝํ๊ณ ์ถ์ผ๋ฉด translateX ๋ถ๋ถ์์ ํ์ฌ ์ธ๋ฑ์ค ๋ค์ ๊ณฑํ๋ ์ซ์๋ฅผ ์์ ํ๋ฉด ๋๋ค.
์์ดํ
ํญ์ 50%
๋ก ์ค์ ํ๊ณ translateX์ 100
์ ๊ณฑํ๋ค๋ฉด 2๊ฐ ์์ดํ
์ด ์ฌ๋ผ์ด๋๋๋ค. ์ด์ฒ๋ผ ์์ดํ
ํญ๊ณผ translateX๋ฅผ ์กฐ์ ํด์ ํ๋ก์ ํธ์์ ์๊ตฌํ๋ ๋ฐฉ์์ ๋ง์ถฐ ์์ฑํ ์ ์๋ค.
- ์์ดํ
width 100%
|translateX(-${currentIndex * 100}%)
- ํ๋ฉด ํ์ ์์ดํ ๊ฐ์ : 1๊ฐ
- ์ฌ๋ผ์ด๋ ์์ดํ ๊ฐ์ : 1๊ฐ
- ์์ดํ
width 50%
|translateX(-${currentIndex * 50}%)
- ํ๋ฉด ํ์ ์์ดํ ๊ฐ์ : 2๊ฐ
- ์ฌ๋ผ์ด๋ ์์ดํ ๊ฐ์ : 1๊ฐ
- ์์ดํ
width 50%
|translateX(-${currentIndex * 100}%)
- ํ๋ฉด ํ์ ์์ดํ ๊ฐ์ : 2๊ฐ
- ์ฌ๋ผ์ด๋ ์์ดํ ๊ฐ์ : 2๊ฐ
๋ง์ฐ์ค / ํฐ์น ๋๋๊ทธ ์ฌ๋ผ์ด๋
export type CoordinateKeys = 'clientX' | 'clientY' | 'pageX' | 'pageY';
export type SwipeEvent<T = HTMLDivElement> = TouchEvent<T> | MouseEvent<T>;
const getCoordinates = (
event: SwipeEvent,
): { [key in CoordinateKeys]: number } => {
const swipeEvent = 'touches' in event ? event.touches[0] : event;
const { clientX, clientY, pageX, pageY } = swipeEvent;
return { clientX, clientY, pageX, pageY };
};
๋ง์ฐ์ค / ํฐ์น ๋๋๊ทธ๋ก ์บ๋ก์ ์์ดํ ์ ์ข / ์ฐ๋ก ์์ง์ด๊ณ ,
์ผ์ ๊ตฌ๊ฐ ์ด์ ์ด๋ํ ํ ๋๋๊ทธ๋ฅผ ์ข ๋ฃ ํ์ ๋ ์ด์ / ๋ค์ ์์ดํ ์ผ๋ก ์ด๋ํ๋ค
์ํ ๊ตฌ๋ถ
- ๋ฆฌ๋ ๋๋ง์ ์ํฅ์ ์ฃผ๋ ์ํ —
useState
- (number)
diff
:์ค์์ดํ ์์ clientX - ์ค์์ดํ ์ข ๋ฃ clientX
๊ฒฐ๊ณผ๊ฐ - (number)
currentIndex
: ํ์ฌ ํ๋ฉด์ ํ์ํ๊ณ ์๋ ์์ดํ ์ธ๋ฑ์ค
- (number)
- ๋ฆฌ๋ ๋๋ง์ ์ํฅ์ ์ฃผ์ง ์๋ ์ํ —
useRef
๋ฑ์ผ๋ก ๊ด๋ฆฌ- (null | number)
swipeStartPos
: ์ค์์ดํ ์์clientX
- (boolean)
isActiveTransition
: ์ ํ ํจ๊ณผ ON / OFF ์ฌ๋ถ - (boolean)
isOnTransitionEvent
: ์ ํ ์ด๋ฒคํธ ์งํ์ค ์ฌ๋ถ
- (null | number)
์ด๋ฒคํธ ํธ๋ค๋ฌ ํบ์๋ณด๊ธฐ
๐ก ์ฐธ๊ณ ์ฌํญ
clientX
: ๋ธ๋ผ์ฐ์ ํ๋ฉด ๊ธฐ์ค ๊ฐ์ฅ ์ผ์ชฝ์์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ์ง์ ๊น์ง ๊ฑฐ๋ฆฌ- ์ ํ ์ด๋ฒคํธ (์ ํ ์ด๋ฒคํธ ์์ / ์ข
๋ฃ์
isOnTransitionEvent
๋ด๋ถ ์ํ ๋ณ๊ฒฝ)- ์ ํ ํจ๊ณผ ์์ ์ด๋ฒคํธ :
transitionstart
(React ์ดํธ๋ฆฌ๋ทฐํธ : ์์) - ์ ํ ํจ๊ณผ ์ข
๋ฃ ์ด๋ฒคํธ :
transitionend
(React ์ดํธ๋ฆฌ๋ทฐํธ :onTransitionEnd
)
- ์ ํ ํจ๊ณผ ์์ ์ด๋ฒคํธ :
์ค์์ดํ ์์ ํธ๋ค๋ฌ
- ์ด๋ฒคํธ ์ ํ :
onTouchStart
/onMouseDown
- ์คํ ์กฐ๊ฑด : ์ ํ ์ด๋ฒคํธ ์งํ์ค์ด ์๋ —
isOnTransitionEvent === false
- ํธ๋ค๋ฌ ์ก์
- ์ด๋ฒคํธ ๊ฐ์ฒด์์ ํ์ฌ
clientX
์ขํ ํ๋ swipeStartPos
์ํ์clientX
์ขํ ์ ์ฅ- ์ ํ ํจ๊ณผ OFF (์์ดํ ๋๋๊ทธ์ ์ง์ฐ์ด ์์ด์ผ ํ๋ฏ๋ก)
- ์ด๋ฒคํธ ๊ฐ์ฒด์์ ํ์ฌ
์ค์์ดํ ์งํ ํธ๋ค๋ฌ
- ์ด๋ฒคํธ ์ ํ :
onTouchMove
/onMouseMove
- ์คํ ์กฐ๊ฑด :
swipeStartPos
์ํ ๊ฐ์ด ์กด์ฌํจ - ํธ๋ค๋ฌ ์ก์
- ์ด๋ฒคํธ ๊ฐ์ฒด์์ ํ์ฌ
clientX
์ขํ ํ๋ swipeStartPos - clientX
๊ฒฐ๊ณผ๊ฐdiff
์ํ์ ์ ์ฅ- ์ผ์ชฝ์ผ๋ก ๋๋๊ทธํ๋ฉด
+
๊ฐ (์บ๋ก์ ํธ๋์ ์ข์ธก์ผ๋ก ์ด๋) - ์ค๋ฅธ์ชฝ์ผ๋ก ๋๋๊ทธํ๋ฉด
-
๊ฐ (์บ๋ก์ ํธ๋์ ์ฐ์ธก์ผ๋ก ์ด๋)
- ์ผ์ชฝ์ผ๋ก ๋๋๊ทธํ๋ฉด
- ์ด๋ฒคํธ ๊ฐ์ฒด์์ ํ์ฌ
์ค์์ดํ ์ข ๋ฃ ํธ๋ค๋ฌ
- ์ด๋ฒคํธ ์ ํ :
onTouchEnd
/onMouseUp
/onMouseLeave
- ํธ๋ค๋ฌ ์ก์
- ์ด์ / ๋ค์ ์์ดํ
์ด๋ ์ฌ๋ถ ํ๋จ (
diff
์ ๋น๊ตํ๋ ๊ฐ์ด ํด์๋ก ์ค์์ดํ ๋ฏผ๊ฐ๋ ํ๋ฝ)diff > 10
: ๋ค์(์ฐ์ธก) ์์ดํ ์ผ๋ก ์ด๋diff < -10
: ์ด์ (์ข์ธก) ์์ดํ ์ผ๋ก ์ด๋
- ์ธ๋ฑ์ค ๋ณ๊ฒฝ ํจ์
moveToNextIndex
์คํ (๋ฐฉํฅ ์ ๋ณด'next' | 'prev'
์ธ์๋ก ์ ๋ฌ) swipeStartPos
์ํ ์ด๊ธฐํ(null
)diff
์ํ ์ด๊ธฐํ(0
)
- ์ด์ / ๋ค์ ์์ดํ
์ด๋ ์ฌ๋ถ ํ๋จ (
์ธ๋ฑ์ค ๋ณ๊ฒฝ ํจ์
๐ก ์ธ๋ฑ์ค ๋ณ๊ฒฝ ํจ์๋ ์ข/์ฐ ๋ฒํผ, ํ๋จ Dot์ ํด๋ฆญํ์ ๋๋ ํธ์ถ๋๋ค
moveToNextIndex(type: 'prev' | 'next', waitTransition?: boolean) : void
- ์คํ ์กฐ๊ฑด : ์ ํ ์ด๋ฒคํธ ์งํ์ค์ด ์๋ —
isOnTransitionEvent === false
- ํจ์ ์ก์
- ์ ํ ํจ๊ณผ ON (์ด๋์ ์ ํ ํจ๊ณผ๊ฐ ์ ์ฉ๋ผ์ผ ํ๋ฏ๋ก)
currentIndex
์ํ ๋ณ๊ฒฝ- ์ด์ ์์ดํ
์ผ๋ก ์ด๋(
type === 'prev'
) :currentIndex - 1
- ๋ค์ ์์ดํ
์ผ๋ก ์ด๋(
type === 'next'
) :currentIndex + 1
- ์ด์ ์์ดํ
์ผ๋ก ์ด๋(
currentIndex
, diff
์ํ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์ค์์ดํ ์ฌ์ด์ฆ(์บ๋ก์
์์ดํ
์ x์ถ ์ด๋ ๋ฒ์)๋ฅผ ๊ณ์ฐํ๊ณ ๊ฒฐ๊ณผ๊ฐ์ ์บ๋ก์
์์ดํ
์๋ฆฌ๋จผํธ์ transform
์คํ์ผ ์์ฑ(calc ํจ์)
์ ์ ๋ฌํ๋ค.
์ค์์ดํ๊ฐ ์งํ์ค(๋๋๊ทธ)์ผ ๋ diff
์ํ๊ฐ ๊ณ์ ๋ณ๊ฒฝ๋ผ์ ์บ๋ก์
์์ดํ
์ด ์ข/์ฐ๋ก ์์ง์ธ๋ค. ์ค์์ดํ๋ฅผ ์ข
๋ฃํ๋ฉด diff
์ํ๋ ์ด๊ธฐํ๋๊ณ ๋ณ๊ฒฝ๋ currentIndex
์ํ์ ๋ฐ๋ผ ์ด์ ํน์ ๋ค์ ์์ดํ
์ผ๋ก ์ด๋ํ๋ค.
import {
DetailedHTMLProps,
HTMLAttributes,
MouseEvent,
TouchEvent,
} from 'react';
export type CarouselItemAttributes<T = HTMLDivElement> = DetailedHTMLProps<
HTMLAttributes<T>,
T
>;
type TouchEventKeys =
| 'onTouchStart'
| 'onTouchMove'
| 'onTouchEnd'
| 'onTouchCancel';
type MouseEventKeys =
| 'onMouseDown'
| 'onMouseMove'
| 'onMouseUp'
| 'onMouseLeave'
| 'onMouseEnter'
| 'onMouseOut'
| 'onMouseOver';
type TrackEvents = TouchEventKeys & MouseEventKeys;
type SwipeEvent<T = HTMLDivElement> = TouchEvent<T> | MouseEvent<T>;
export type TrackEventAttributes = {
[key in TrackEvents]: (event: SwipeEvent) => void;
};
// useCarousel.tsx
const swipeSizeCalcExpression = `-${currentIndex * 100}% - ${diff}px`;
// ์บ๋ก์
์์ดํ
Parent์ ์ ๋ฌํ Props
const carouselItemProps: CarouselItemAttributes = {
ref: trackRef, // ์ ํ ์์ ์ด๋ฒคํธ ํธ๋ค๋ฌ ๋ฑ๋ก. ์ด๋ฒคํธ ๋ฐ์์ isOnTransitionEvent ์ํ true๋ก ๋ณ๊ฒฝ
style: {
transform: `translateX(calc(${swipeSizeCalcExpression}))`,
transition: carouselModel.isActiveTransition // ์ ํ ํจ๊ณผ On/Off ์ฌ๋ถ
? `transform ${options.speed}ms ${options.transitionType}` // currentIndex๊ฐ ๋ณ๊ฒฝ๋ผ์ ์์ดํ
์ ์ด๋ํ ๋
: undefined, // ๋๋๊ทธ ์ค์ผ ๋
},
onTransitionEnd: () => onTransitionEndAfterDelay(), // isOnTransitionEvent ์ํ false๋ก ๋ณ๊ฒฝ
};
// ์บ๋ก์
์์ดํ
Wrapper(์บ๋ก์
ํธ๋)์ ์ ๋ฌํ Props
const trackEventHandlers: TrackEventAttributes = {
onTouchStart: onSwipeStart,
onMouseDown: onSwipeStart,
// ...
};
์ด๋ฒคํธ ํธ๋ค๋ฌ๋ ์บ๋ก์
์์ดํ
Wrapper(์บ๋ก์
ํธ๋) ์์์ ์ ๋ฌํ๊ณ , ์ ํ ํจ๊ณผ ๊ด๋ จ Props๋ ์บ๋ก์
์์ดํ
Parent ์์์ ์ ๋ฌํ๋ค. Carousel
์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ ๊ณณ์์ ์บ๋ก์
์์ดํ
์ width
๋ฅผ ์ ์ฐํ๊ฒ ์ปจํธ๋กคํ ์ ์๋๋ก React.cloneElement
๋ฅผ ์ด์ฉํด ์บ๋ก์
์์ดํ
์ width
ํ๋กญ์ ์ถ๊ฐํ๋ค.
๐ก ์บ๋ก์
์์ดํ
Parent ๋ฐ๋ก ์๋์(1depth) ์๋ ๋ชจ๋ ์์ ์ปดํฌ๋ํธ๋ width
ํ๋กญ์ ๋ฐ๊ฒ ๋๋ค
// Carousel.tsx
{ /* ์บ๋ก์
์์ดํ
Wrapper (์บ๋ก์
ํธ๋) */ }
<div className="overflow-hidden w-full h-full" {...trackEventHandlers}>
{/* ์บ๋ก์
์์ดํ
Parent */}
<div className="flex no-scrollbar" {...carouselItemProps}>
{Children.map(carouselChildren, (child, i) => {
if (isValidElement(child)) {
return cloneElement(child, {
...child.props,
key: i,
width: `${carouselOptions.itemWidthRatio}%`,
});
}
})}
</div>
</div>;
- React.Children.map(children, callback) : ๊ฐ
children
์ ์ฝ๋ฐฑ์ ํธ์ถํ๊ณ ์ ๋ฐฐ์ด ๋ฐํ.children
์ดnull
,undefined
์ด๋ฉด ๋ฐฐ์ด์ด ์๋null
,undefined
๊ทธ๋๋ก ๋ฐํ. - React.isValidElement(object) :
object
๊ฐ React ์๋ฆฌ๋จผํธ์ธ์ง ์ฌ๋ถ๋ฅผ ๋ํ๋ด๋ Boolean ๋ฐํ. - React.cloneElement(element, [config], […children]) :
element
๋ฅผ ๋ณต์ ํ์ฌ ์๋ก์ด React ์๋ฆฌ๋จผํธ ๋ฐํ. ์ฃผ๋ก prop์ ์ถ๊ฐ/์ญ์ ํ๊ฑฐ๋, ์์ ์ปดํฌ๋ํธ ๊ธฐ๋ฅ ํ์ฅ์ ์ํด ์ฌ์ฉ.element
: ๋ณต์ ํ ์๋ฆฌ๋จผํธconfig
: ๋ณต์ ํ ์๋ฆฌ๋จผํธ์ ๋๊ธธprops
,key
,ref
. ๋ฐ๋กkey
,ref
๋ช ์ ์ํ๋ฉด ์๋ณธ ์ ์งchildren
: ๋ณต์ ํ ์๋ฆฌ๋จผํธ์ ์์ ์์. ์ถ๊ฐ/๋์ฒด ๊ฐ๋ฅ. ๋ฐ๋ก ๋ช ์ ์ํ๋ฉด ์๋ณธ ์ ์ง
๋ฌดํ ์ฌ๋ผ์ด๋
๐ก ํ๋ฉด์ 1๊ฐ ์์ดํ ํ์ ๊ธฐ์ค์ผ๋ก ์์ฑ
๊ตฌํ ๋ฐฉ๋ฒ ์ค๋ช โก๏ธ
- ์ฒซ๋ฒ์งธ / ๋ง์ง๋ง ์์ดํ
์ ๋ณต์ ํด์ ์๋ณธ ์ฒซ๋ฒ์งธ ์์ดํ
์ ์ ๋ง์ง๋ง ์์ดํ
์, ์๋ณธ ๋ง์ง๋ง ์์ดํ
๋ค์ ์ฒซ๋ฒ์งธ ์์ดํ
์ ์ด์ด๋ถ์ธ๋ค.
- ์๋ณธ ์์ดํ
:
A B C D
- ์ด์ด ๋ถ์ธ ํ ์์ดํ
:
cD (A B C D) cA
cD
์ธ๋ฑ์ค : ๋ง์ง๋ง ์์ดํ (D
)cA
์ธ๋ฑ์ค : ์ฒซ๋ฒ์งธ ์์ดํ (A
)
- ์๋ณธ ์์ดํ
:
- ํ์ฌ ์ธ๋ฑ์ค์์(
D
) ๋ค์ ๋ฒํผ์ ๋๋ฌ์ ๋ณ๊ฒฝํ ์ธ๋ฑ์ค๊ฐ ๋ง์ง๋ง(cA
)์ด๋ผ๋ฉด…D
(๋ง์ง๋ง ์์ดํ ) →cA
(์ฒซ๋ฒ์งธ ์์ดํ ) ์ธ๋ฑ์ค๋ก ์ ํํจ๊ณผ๋ฅผ ์ ์งํ๋ฉด์ ์ด๋ํ๋ค- ์ด๋ ํ๋ฉด์์ผ๋ก ์ฒซ๋ฒ์งธ ์์ดํ
(
A
)์ผ๋ก ์ด๋ํ๋ ๊ฒ์ฒ๋ผ ๋ณด์ธ๋ค - ์ด๋์ ์๋ฃํ๋ฉด(์ ํ Delay ์ดํ) ์ ํ ํจ๊ณผ๋ฅผ ์ ์ Offํ๊ณ
currentIndex
๋ฅผA
๋ก ๋ณ๊ฒฝํ๋ค - ์ ํ ํจ๊ณผ๋ฅผ Off ํ์ผ๋ฏ๋ก ํ๋ฉด์์ ์๋ฌด๋ฐ ๋ณํ๊ฐ ์๋ ๊ฒ์ฒ๋ผ ๋ณด์ธ๋ค
์ธ๋ฑ์ค ๋ณ๊ฒฝ ํจ์ ์์
์๋์ฒ๋ผ ์ธ๋ฑ์ค ๊ฐ์ด๋ ๋งต์ ์์๋ก ๋ง๋ค์ด ๋๊ณ ์ฌ์ฉํ๋ฉด ๊ตฌํ์ ์ค์๋ฅผ ์ค์ผ ์ ์๋ค.
export const IDX_OFFSET = 1;
export const IDX_GUIDE_MAP = (childrenCount: number) => ({
FIRST_ITEM: IDX_OFFSET, // ์๋ณธ ๋ฐฐ์ด์์ ์ฒซ๋ฒ์งธ ์์ดํ
์ธ๋ฑ์ค
LAST_ITEM: childrenCount - IDX_OFFSET, // ์๋ณธ ๋ฐฐ์ด์์ ๋ง์ง๋ง ์์ดํ
์ธ๋ฑ์ค
TAIL: childrenCount + IDX_OFFSET, // ๋ณต์ ํ ๋ฐฐ์ด์์ ์ฒซ๋ฒ์งธ ์์ดํ
(๋ง์ง๋ง ์ธ๋ฑ์ค)
FRONT: 0, // ๋ณต์ ํ ๋ฐฐ์ด์์ ๋ง์ง๋ง ์์ดํ
(์ฒซ๋ฒ์งธ ์ธ๋ฑ์ค)
});
์ธ๋ฑ์ค๋ฅผ ๋ณ๊ฒฝํ๋ moveToNextIndex
ํจ์์ ๋ค์ ์ธ๋ฑ์ค๊ฐ ์ฒซ๋ฒ์งธ(FRONT) ํน์ ๋ง์ง๋ง(TAIL)์ธ์ง ํ์
ํ๊ณ Transition Delay(โถ์ธ๋ฑ์ค ๋ณ๊ฒฝ ํ ํธ๋ ์ด๋ ์๋ฃ) ์ดํ โท๊ต์ ํ ์ธ๋ฑ์ค๋ก ๊ต์ฒดํ๋ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ค.
const moveToNextIndex = (type: CarouselArrow, waitTransition = true) => {
// carouselModel.isIdle : ์ ํ ์ด๋ฒคํธ ์งํ์ค ์ฌ๋ถ ์ํ isOnTransitionEvent ์กฐํ
if (waitTransition && !carouselModel.isIdle) return;
const isPrev = type === 'prev';
const nextIndex = carouselIndex + (isPrev ? -1 : 1); // ์ด๋ํด์ผํ ์ธ๋ฑ์ค
setCarouselIndexes(nextIndex); // โด currentIndex ์ํ ๋ณ๊ฒฝ
carouselModel.onMoveToIdxAction(); // ์ ํ ํจ๊ณผ ON (isActiveTransition ์ํ ๋ณ๊ฒฝ)
const isFront = nextIndex === IDX_GUIDE.FRONT;
const isTale = nextIndex === IDX_GUIDE.TAIL;
if (isFront || isTale) {
// nextIndex๊ฐ 0์ด๋ผ๋ฉด ์๋ณธ ๋ฐฐ์ด์ ๋ง์ง๋ง ์ธ๋ฑ์ค๋ก ์ด๋ํด์ผ ํ๋ค.
// ๋ณต์ ํ ๋ฐฐ์ด์ ๊ฐ์ฅ ์์ ์์๊ฐ 1๊ฐ ๋ ์ถ๊ฐ๋์ผ๋ฏ๋ก + 1์ ํด์ค๋ค.
const toIndex = isPrev ? IDX_GUIDE.LAST_ITEM + 1 : 1;
moveToIdxPromptly(toIndex); // โต ์ธ๋ฑ์ค ๊ต์ ํจ์ ์คํ
}
};
์ธ๋ฑ์ค๋ฅผ ๊ต์ ํด์ฃผ๋ moveToIdxPromptly
ํจ์๋ Transition Delay ์ดํ(์บ๋ก์
ํธ๋ ์ด๋ ์๋ฃ), ์ ํ ํจ๊ณผ๋ฅผ ๋๊ณ ์คํํ๋ฏ๋ก ํ๋ฉด ์์ผ๋ก ์๋ฌด๋ฐ ์ผ๋ ์ผ์ด๋์ง ์์ ๊ฒ์ฒ๋ผ ๋ณด์ธ๋ค.
const moveToIdxPromptly = (index: number) => {
setTimeout(() => {
carouselModel.onMoveToIdxPromptlyAction(); // ์ ํ ํจ๊ณผ OFF (isActiveTransition ์ํ ๋ณ๊ฒฝ)
setCarouselIndexes(index); // currentIndex๋ฅผ ๊ต์ ํ ์ธ๋ฑ์ค๋ก ๋ณ๊ฒฝ
onTransitionEndAfterDelay(); // isOnTransitionEvent(์ ํ ์ด๋ฒคํธ ์งํ์ค ์ฌ๋ถ) ์ํ false๋ก ๋ณ๊ฒฝ
}, options.speed); // Transition Delay ms
};
๋ ํผ๋ฐ์ค
- How to Make a Simple React Carousel
- How to create the responsive and swipeable Carousel - Slider component in React
- [React] ๋ฌดํ ์ฌ๋ผ์ด๋ ๋ง๋ค๊ธฐ (infinite carousel)
๊ธ ์์ ์ฌํญ์ ๋ ธ์ ํ์ด์ง์ ๊ฐ์ฅ ๋น ๋ฅด๊ฒ ๋ฐ์๋ฉ๋๋ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์
'๐ช Programming' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Next.js] Next/Image์ sizes ์์ฑ ํบ์๋ณด๊ธฐ (0) | 2024.05.13 |
---|---|
[JS] ์ธ๋ผ์ธ ์คํ์ผ ์ ์ - cssText (0) | 2024.05.13 |
[React] ๋ ธ๋์ ํธ๋ฆฌ ์์น๋ฅผ ๋ํ๋ด๋ useId ํ (0) | 2024.05.13 |
[TS] ์๋ฆฌ๋จผํธ์ ๊ธฐ๋ณธ ์ดํธ๋ฆฌ๋ทฐํธ ์ธํฐํ์ด์ค/ํ์ ์ฌ์ฉํ๊ธฐ (0) | 2024.05.12 |
[JS] ์ JSX ์์์ if ๋ฌธ์ ์ฌ์ฉํ ์ ์์๊น? ํํ์๊ณผ ๋ฌธ ์ฐจ์ด์ (0) | 2024.05.12 |
๋๊ธ
์ด ๊ธ ๊ณต์ ํ๊ธฐ
-
๊ตฌ๋
ํ๊ธฐ
๊ตฌ๋ ํ๊ธฐ
-
์นด์นด์คํก
์นด์นด์คํก
-
๋ผ์ธ
๋ผ์ธ
-
ํธ์ํฐ
ํธ์ํฐ
-
Facebook
Facebook
-
์นด์นด์ค์คํ ๋ฆฌ
์นด์นด์ค์คํ ๋ฆฌ
-
๋ฐด๋
๋ฐด๋
-
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
-
Pocket
Pocket
-
Evernote
Evernote
๋ค๋ฅธ ๊ธ
-
[Next.js] Next/Image์ sizes ์์ฑ ํบ์๋ณด๊ธฐ
[Next.js] Next/Image์ sizes ์์ฑ ํบ์๋ณด๊ธฐ
2024.05.13 -
[JS] ์ธ๋ผ์ธ ์คํ์ผ ์ ์ - cssText
[JS] ์ธ๋ผ์ธ ์คํ์ผ ์ ์ - cssText
2024.05.13 -
[React] ๋ ธ๋์ ํธ๋ฆฌ ์์น๋ฅผ ๋ํ๋ด๋ useId ํ
[React] ๋ ธ๋์ ํธ๋ฆฌ ์์น๋ฅผ ๋ํ๋ด๋ useId ํ
2024.05.13 -
[TS] ์๋ฆฌ๋จผํธ์ ๊ธฐ๋ณธ ์ดํธ๋ฆฌ๋ทฐํธ ์ธํฐํ์ด์ค/ํ์ ์ฌ์ฉํ๊ธฐ
[TS] ์๋ฆฌ๋จผํธ์ ๊ธฐ๋ณธ ์ดํธ๋ฆฌ๋ทฐํธ ์ธํฐํ์ด์ค/ํ์ ์ฌ์ฉํ๊ธฐ
2024.05.12