본문 바로가기

front_end

react & sass single child carousel 싱글 차일드 케러셀

 

https://github.com/joepasss/single-carousel

 

GitHub - joepasss/single-carousel: single child carousel in react

single child carousel in react. Contribute to joepasss/single-carousel development by creating an account on GitHub.

github.com

 

작업내용 정리

1. 캐러셀 내부에 들어갈 자식 요소들 정렬하기

2. 캐러셀 자식 또는 부모 컨테이너 이동하기

 

시작 전 세팅

- 리엑트 세팅

import './singleCarousel.scss';

export const SingleCarousel = () => {
  const [current, setCurrent] = useState<number>(0);

  const buttons = [];
  for (let i = 0; i < 5; i++) {
    buttons.push(
      <button
        className='nav-btn'
        key={i}
        onClick={() => setCurrent(i)}
        id={current === i ? 'current--img' : ''}
      />
    );
  }

  return (
    <section id='carousel'>
      {current !== 0 && (
        <button className='arrow-left' onClick={() => setCurrent(current - 1)}>
          &lt;
        </button>
      )}
      {current !== 4 && (
        <button className='arrow-right' onClick={() => setCurrent(current + 1)}>
          &gt;
        </button>
      )}

      <div className='image-container'>
        <ul className='items'>
          <li>1</li>
          <li>2</li>
          <li>3</li>
          <li>4</li>
          <li>5</li>
        </ul>
      </div>

      <div className='carousel-nav'>{buttons}</div>
    </section>
  );
};

 

- sass (css) 세팅

@import '../../styles/globalVar.scss';

section#carousel {
  position: relative;
  padding: 1rem 2rem;
  height: 60vh;
  width: 50rem;

  button.arrow-left,
  button.arrow-right {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    font-size: 3rem;
    font-weight: 500;

    border: none;
    background-color: transparent;
    cursor: pointer;
    z-index: 2;
  }

  button.arrow-left {
    left: 0;
  }

  button.arrow-right {
    right: 0;
  }

  div.image-container {
    height: 100%;
    width: 100%;
    position: relative;
    overflow: hidden;

    ul {
      width: 100%;
      height: 100%;
      position: relative;
      transition: transform 0.35s ease-in-out;

      li {
        position: absolute;
        width: 100%;
        height: 100%;
        background-color: white;
        font-size: 4rem;

        display: flex;
        align-items: center;
        justify-content: center;
        border-radius: 1rem;
      }
    }
  }

  div.carousel-nav {
    position: absolute;
    bottom: -2rem;
    height: 5%;
    width: 100%;
    left: 50%;
    transform: translateX(-50%);

    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1rem;

    button.nav-btn {
      width: 1.5rem;
      height: 1.5rem;
      background-color: transparent;
      border: 4px solid black;
      border-radius: 50%;
      cursor: pointer;
    }

    button#current--img {
      background-color: black;
    }
  }
}

 

li (캐러셀 자식요소) 정렬하기

1. 부모의 width, height와 동일한 크기로 적용되어 있으므로 부모 또는 자식의 width를 받아와 준다

2. 받아온 width값을 이용해서 다시 정렬해 준다 (width 가 1000이라 가정하면 첫 번째 자식의 left: 0, 두 번쨰: 1000, 세 번째: 2000 ...)

 

컨테이너 (부모, ul)의 width 가져오기

1. useRef를 이용, list라는 이름의 변수를 만들어준 뒤 ul에 ref로 적용시켜준다

import { useRef, RefObject } from 'react'

export const SingleCarousel = () => {
  const list = useRef() as RefObject<HTMLUListElement>;
  
  // ... 생략 ...
  
  return (
    <section id='carousel'>
      
      // ... 생략 ...
      
      <div classname='image-container>
        <ul className='items' ref={list}>
        
        // ... 생략 ...
        
        </ ul>
      </div>
      
    </section>
   );
 };

 

2. list.current 에 들어있는 children중 하나의 width를 가져오기 위해 useEffect 를 사용, useEffect 내에서 const 를 지정한 뒤 저장해 놓는다

useEffect(() => {
  const imgWidth = list.current!.children[0].getBoundingClientRect().width + 20;
 });

이미지 사이에 간격을 주기 위해 20을 더했다

 

3. 이미지의 left값을 결정해준다 ul의 각 자식 (list.current!.children[]) 모두에 left값을 지정해 준다 이 때 left값은 이전에 구했던 imgWidth * index + 'px' 이 된다 (첫 번째 자식 0, 두 번째 자식 1000, 세 번쨰 자식 2000 ....)

    - useEffect 내부에서 함수를 만든 다음 이미지 array를 만든 다음 foreach로 순회하며 적용시킨다

  useEffect(() => {
    const imgWidth =
      list.current!.children[0].getBoundingClientRect().width + 20;

    const setImgPosition = (img: HTMLElement, index: number) => {
      img.style.left = imgWidth * index + 'px';
    };

    const imgs = Array.from(list.current!.children) as HTMLElement[];

    imgs.forEach(setImgPosition);
  });

이미지 정렬된 모습

 

4. 스크롤 하듯이 이동을 하기 위해 ul(container) 에 transform 옵션을 준다

  useEffect(() => {
    const imgWidth =
      list.current!.children[0].getBoundingClientRect().width + 20;

    const setImgPosition = (img: HTMLElement, index: number) => {
      img.style.left = imgWidth * index + 'px';
    };

    const imgs = Array.from(list.current!.children) as HTMLElement[];

    imgs.forEach(setImgPosition);

    list.current!.style.transform =
      'translateX(-' + imgs[current].style.left + ')';
  });

적용 된 모습