[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] ์ธ๋ผ์ธ ์คํ์ผ ์ ์ - cssText2024.05.13
- 
[React] ๋ ธ๋์ ํธ๋ฆฌ ์์น๋ฅผ ๋ํ๋ด๋ useId ํ[React] ๋ ธ๋์ ํธ๋ฆฌ ์์น๋ฅผ ๋ํ๋ด๋ useId ํ2024.05.13
- 
[TS] ์๋ฆฌ๋จผํธ์ ๊ธฐ๋ณธ ์ดํธ๋ฆฌ๋ทฐํธ ์ธํฐํ์ด์ค/ํ์ ์ฌ์ฉํ๊ธฐ[TS] ์๋ฆฌ๋จผํธ์ ๊ธฐ๋ณธ ์ดํธ๋ฆฌ๋ทฐํธ ์ธํฐํ์ด์ค/ํ์ ์ฌ์ฉํ๊ธฐ2024.05.12