React로 Carousel(캐러셀) 만들기 (3) - translateX를 이용한 구현

가만히있으세요·2022년 6월 19일
1
post-thumbnail

목 차

2. Carousel(캐러셀) 만들기 (2) - margin, width 을 이용한 구현

📍1. 구조

  • view

  • element

  • 계층
    + movie-row
      + movie-row__carousel
        + movie-row__button--left
        + movie-row__button--right
        + movie-row__list
          + MovieItems
          + MovieItems
          + ..

버튼이 carousel 안으로 들어간것을 제외하면 이전과 동일하다.

📍2. 구현

이전 방식이 margin-left에 음수 양수 값을 주어, 내부 요소를 왼쪽, 오른쪽으로 이동시키는 방법을 사용한 것이라면,
이번에 사용한 방법은 translateX 를 이용해 좌우로 이동시키는 방법이다.

✅ translateX
The translateX() CSS function repositions an element horizontally on the 2D plane.

차이점은
이전 margin 방식은 window.innerWidth를 통해 viewable한 width를 구해서 사용했었다.
이번 translateX를 이용한 방식에서는 위 방식 대신에 useRef()getBoundingClientRect()를 사용해 CSS속성값을 가져와 viewable한 width를 구해서 사용해보았다.
관련 포스트 참고.

[React] DOM Element의 css정보를 가져오기 - (getComputedStyle, useRef)

movie-row__list에서는 width를 계산하지 않고, CSS의 max-content를 이용하여 width를 지정하였다.

parameter

title : 영화제목 ("영화제목1")
items : 영화정보가 담긴 배열. ([item1, item2, item3, item4, ..])

📍3. 코드

📌 3 - 1. 설명

useRef()를 이용해 list(movie-rowlist)와 right Button(movie-rowbutton--right) DOM을 선택한다.

list의 렌더링된 직후의 최초 좌측 x위치를 저장(startX)한다.

슬라이드버튼을 누르면
list의 현재 x좌표, right Button의 좌측x좌표, list의 너비를 가져온다.
list의 child인 movieItem의 너비를 가져와 슬라이드할 거리(slideDistance)를 설정한다.(movieItem * 3)

좌우측버튼을 누르면 list의 현재 x좌표에 slideDistance를 더하거나 빼서 list를 좌우측으로 이동시킨다.

list가 시작, 끝 위치를 초과해서 넘어갔을 때의 계산 방법은 이전 marin-left 방식과 같다.
달라진 것은 window.innerwidth로 계산했던 viewable width를 list의 최초 시작x좌표와 right buttond의 좌측x를 통해 보다 더 정밀하게 계산하였고,
list의 width를 max-content 속성으로 하여 이전의 수동너비지정 방식과는 차이가 있다.

코드가 어렵진 않다.

📌 3 - 2. React Code

import React, { useRef, useState, useLayoutEffect } from 'react';
import './MovieRow_translate.scss';
import { FaLessThan, FaGreaterThan } from 'react-icons/fa';
import MovieItem from './MovieItem_translate';

function MovieRow({ title, items }) {
  const listRef = useRef();
  const btnRef = useRef();
  const [startX, setstartX] = useState(0);

  useLayoutEffect(() => {
    const getCoordinate = () => {
      const listLeft = listRef.current.getBoundingClientRect().left;

      setstartX(listLeft);
    };
    getCoordinate();
  }, []);

  const handleClick = (direction) => {
    let currenX = listRef.current.getBoundingClientRect().x;
    let btnLeft = btnRef.current.getBoundingClientRect().left;
    let listWidth = listRef.current.getBoundingClientRect().width;
    let listRef_NodeWidth = items.length > 0 ? listRef.current.childNodes[0].getBoundingClientRect().width : 0;
    const slideDistance = listRef_NodeWidth * 3;

    let calculate_distance = 0;
    if (direction === 'left') {
      calculate_distance = currenX + slideDistance;
      if (startX < calculate_distance) {
        calculate_distance = 0;
      }
    } else if (direction === 'right') {
      calculate_distance = currenX - slideDistance;
      if (btnLeft - startX - listWidth > calculate_distance) {
        calculate_distance = btnLeft - startX - listWidth;
      }
    }
    listRef.current.style.transform = `translateX(${calculate_distance}px)`;
  };

  return (
    <div className="movie-row">
      <h2 className="movie-row__title">{title}</h2>
      <div className="movie-row__carousel">
        <div className="movie-row__button movie-row__button--left" onClick={() => handleClick('left')}>
          <FaLessThan style={{ fontSize: 30 }} />
        </div>
        <div className="movie-row__button movie-row__button--right" onClick={() => handleClick('right')} ref={btnRef}>
          <FaGreaterThan style={{ fontSize: 30 }} />
        </div>
        <div className="movie-row__list" ref={listRef}>
          {items.length > 0 && items.map((item, index) => <MovieItem key={index} item={item} />)}
        </div>
      </div>
    </div>
  );
}

export default MovieRow;

- CSS Code

.movie-row {
  width:100%;
  margin-top: -10px;
  margin-bottom : 20px;
  overflow: hidden;

  .movie-row__title {
    color: white;
    font-weight: bold;
    font-size: 20px;
    margin : 0px 0px 5px 10px;
  }
  
  .movie-row__carousel {
    position: relative;

    .movie-row__list {
      margin-left: 40px;
      display: flex;
      width : max-content;
      transform: translateX();
      transition: all ease 1s;
    }
    
    .movie-row__button {
      width : 40px;
      height : 100%;
      background-color: rgba(0, 0, 0, 0.6);
      color : white;
      position: absolute;
      display: flex;
      justify-content: center;
      align-items: center;
      z-index: 99;
      cursor : pointer;
      overflow: hidden;

      // opacity: 0;
      // transition: all ease 0.6s;

      &.movie-row__button--left {
        left : 0;
      }
      &.movie-row__button--right {
        right : 0;
      }
    }

  }
}

📍4. 결론

margin-left방식과 translate 방식이 유사한데 아무거나 써도 되는 것이 아닐까 생각했지만, translateX는 margin-left와는 달리 디바이스의 GPU에 의해 처리되고, 레이아웃을 재계산하지 않기 때문에 성능 기준에 매우 적합하단다. (넷플릭스의 carousel이 translateX를 사용하여 제작되었다)

✅ margin-left는 레이아웃을 재계산한다.

Changing margin-left alters the geometry of the element. That means that it may affect the position or size of other elements on the page, both of which require the browser to perform layout operations.

✅ transform은 레이아웃을 재계산하지 않는다.

Changing transform does not trigger any geometry changes or painting, which is very good. This means that the operation can likely be carried out by the compositor thread with the help of the GPU.

성능을 원한다면 당연히 translate 방식이 옳은것에 틀림없다.

또한 기능을 추가하고 확장하여
마우스, 터치, 키보드를 이용한 스크롤, 동적인 너비계산방식, 윈도우 크기가 변할 때 재측정, 페이지네이션 등등.. 더 완벽한 carousel을 구현하고 싶다면 trnaslte방식이 더 적절해 보인다.

현재의 코드는 정말 간단한 기초 기능들만 구현했지만 추후 보완하면 더 좋은 결과물들을 만들어 갈 수 있지 않을까 생각한다.


다음 포스트

2. ✅ Carousel(캐러셀) 만들기 (2) - margin, width 을 이용한 구현

0개의 댓글