ABOUT ME

-

오늘
-
어제
-
-
  • 완전 초 단순하게 캐러셀(Carousel) 구현해보기
    Front-end/Javascript 2020. 10. 27. 01:45

    캐러셀(Carousel)

    캐러셀이라는 용어가 조금 생소하실 수도 있는데요

    캐러셀은 여러개의 이미지 혹은 영상을 슬라이더 형태로 만들어 표현해주는 것을 말합니다. 바로 위 움짤처럼 말이죠!

     

    정말 간단하게 캐러셀을 구현해보면서 어떤식으로 만들어지는지 살펴보도록 하겠습니다.

     

    ❗️ 제가 구현하는 방법은 학습하며 얻은 정보와 지식을 바탕으로 구현한 자료입니다. 혹시 잘못된 정보가 있거나 코드가 있다면 말씀 부탁드리겠습니다. 🙏

     

    전체 코드 보기

    프로젝트 구조

    엄청 간단하죠? 이제 차근차근 만들어보겠습니다.

    index.html

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <link rel="stylesheet" href="style.css" />
        <title>Carousel</title>
      </head>
      <body>
        <div class="carousel-wrapper">
          <div class="carousel">
            <img
              src="https://images.unsplash.com/photo-1602808180309-2e0c62986635?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60"
              alt=""
            />
            <img
              src="https://images.unsplash.com/photo-1583434987437-1b9dcbe44c9e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60"
              alt=""
            />
            <img
              src="https://images.unsplash.com/photo-1603052227529-e8ed43c7af99?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=500&q=60"
              alt=""
            />
          </div>
        </div>
        <button class="prev" type="button">prev</button>
        <button class="next" type="button">next</button>
    
        <script src="./index.js"></script>
      </body>
    </html>
    

    먼저 캐러셀을 넣을 컨테이너와 리소스(예제에서는 이미지) 이전, 다음 버튼을 만들어줍니다.

    위와 같이 만들어 주면 아래와 같은 화면이 나오게 됩니다.

     

    자 이제 저 위의 사진 3개를 캐러셀로 만들어주어야겠죠?

    본격적으로 스타일링을 해보겠습니다.

    style.css

    자 우리는 넓이 500px, 높이 300px인 캐러셀을 만들 것입니다.

    그러면 캐러셀을 감싸는 컨테이너는 위 값을 가지고 있어야겠죠?

    * {
      box-sizing: border-box;
    }
    
    .carousel-wrapper {
      width: 500px;
      height: 300px;
    }
    

    우린 넓이와 높이를 선언해주었습니다.

    그러면 결과는 어떻게 될까요?

     

    상위 컨테이너가 width를 가지고 있기 때문에 하위 캐러셀은 당연히 상위 width를 따라가서 넓이가 맞추어지게 됩니다.

    그런데 우리가 원하는건 이게 아니라 사진을 순서대로 하나씩 슬라이딩 시키며 보고싶습니다.

     

    그러면 위 사진을 일단 감추어야겠죠?

     

    * {
      box-sizing: border-box;
    }
    
    .carousel-wrapper {
      width: 500px;
      height: 300px;
      overflow: hidden;
    }
    

    그래서 사진들을 감싸고 있는 컨테이너를 안보이게 처리해줍니다.

     

    그러면 이런식으로 첫 사진을 제외한 나머지는 숨어버리게 되죠

     

    자 이제 우리는 숨기는 것 까지 완료했습니다. 다음은 무얼 해야할까요?

    .

    .

    .

     

    바로 가로 정렬입니다.

    캐러셀은 좌, 우로 슬라이딩 하며 리소스를 보여주기 때문에 이 안보이는 리소스들을 가로로 정렬시켜주어야 합니다. 

     

    * {
      box-sizing: border-box;
    }
    
    .carousel-wrapper {
      width: 500px;
      height: 300px;
      overflow: hidden;
    }
    
    .carousel-wrapper > .carousel {
      display: flex;
    }
    

    flex를 활용하여 간단하게 가로로 정렬을 시켜주었습니다.

    이렇게 되면 우측으로 나란히 정렬되겠죠? 확인해보겠습니다.

     

    정렬이 잘 되었네요!

     

    자 이제 높이까지 맞추어줍시다.

    * {
      box-sizing: border-box;
    }
    
    .carousel-wrapper {
      width: 500px;
      height: 300px;
      overflow: hidden;
    }
    
    .carousel-wrapper > .carousel {
      display: flex;
    }
    
    .carousel-wrapper > .carousel > img {
      width: 500px;
      height: 300px;
    }

    각각의 이미지들도 캐러셀 컨터이너에 맞도록 넓이와 높이를 지정해주었습니다.

     

    이제 이전, 다음 버튼을 구현해주면 되겠군요!

     

    index.js

    먼저 이전, 다음 버튼과 이미지들을 가지고 있는 캐러셀을 가져옵니다.

    const prevButton = document.querySelector('.prev');
    const nextButton = document.querySelector('.next');
    const carousel = document.querySelector('.carousel');

    자 이제는 직접 이벤트를 선언하여 클릭 했을 때 반응하여 이미지가 넘어가도록 할 예정입니다.

    먼저 코드를 구현하기 전에 잘 생각해보게 되면...

     

    클릭 했을 때 리소스가 몇개가 있는지 알고 처음과 마지막을 판단하지? 라는 생각이 드실겁니다.

    그래서 우리는 index라는 값을 통해 이미지 숫자를 입력하여 이전과 이후에 리소스가 있는지 없는지 판단할 것입니다.

     

    또 생각 해야할게 있습니다. 우리는 좌, 우측으로 이동해야하는데 클릭을 하면 어떤 스타일을 통해 제어해야하지?

    그래서 우리는 transformtranslate3d를 활용하여 제어 해보겠습니다.

     

    먼저 숫자값인 index와 이전, 이후에 클릭 이벤트를 등록합니다. (첫 리소스의 값은 0)

    const prevButton = document.querySelector('.prev');
    const nextButton = document.querySelector('.next');
    const carousel = document.querySelector('.carousel');
    
    let index = 0;
    
    prevButton.addEventListener('click', () => {
    	
    });
    
    nextButton.addEventListener('click', () => {
    
    });

     

    이전 버튼을 클릭할 때부터 생각해보면, 우리는 첫 리소스에 접근해 있을 때 이전 버튼이 클릭이 되면 안됩니다.

    즉, index가 0일 때 입니다. 

    그래서 이전 버튼 이벤트에는 index가 0이면 return을 하여 이벤트를 무효화 시켜줄 것입니다.

     

    그렇다면 다음 버튼도 마찬가지겠죠? 우리의 리소스는 총 3개이기 때문에 index가 2일 때 return 시켜준다면 이벤트가 무효화 될 것입니다.

     

    const prevButton = document.querySelector('.prev');
    const nextButton = document.querySelector('.next');
    const carousel = document.querySelector('.carousel');
    
    let index = 0;
    
    prevButton.addEventListener('click', () => {
       if (index === 0) return;
    });
    
    nextButton.addEventListener('click', () => {
       if (index === 2) return;
    });

    자 그렇다면 각자 끝에 있는 리소스가 아닐 경우가 있겠죠?

    그 때 버튼을 누른다면 index 값이 바뀌어 추가로 이벤트를 실행해야할지 index로 판단을 할 것입니다.

     

    그래서 이전 버튼에는 한 번 클릭할 때마다 index가 1씩 감소, 이후 버튼에는 1씩 증가하게 된다면 끝 리소스에서는 이벤트가 실행되지 않을 것 입니다.

    const prevButton = document.querySelector('.prev');
    const nextButton = document.querySelector('.next');
    const carousel = document.querySelector('.carousel');
    
    let index = 0;
    
    prevButton.addEventListener('click', () => {
       if (index === 0) return;
       index -= 1;
    });
    
    nextButton.addEventListener('click', () => {
       if (index === 2) return;
       index += 1;
    });

     

    지금까지 우리는 리소스의 유무에 따른 이벤트 조건을 만들었습니다.

    그러면 이 조건이 부합할 때 좌, 우로 움직이는 스타일을 추가해야겠죠?

     

    여기서 사용되는 것이 바로 translate3d입니다.

    브라우저는 DOM TreeCSSOM을 가지고 화면에 렌더할 Render Tree를 만들게 되는데요, 이 Render Tree를 가지고 화면에 배치가 될 레이아웃(Layout)을 계산하게(Flow) 됩니다. 그리고 최종적으로 계산된 결과를 가지고 그려지게(Paint) 되죠

     

    만약에 내가 다시 화면을 그려야할 상황이 오게된다면 레이아웃을 계산하는게 더 빠를까요? 아니면 화면을 다시 그릴 부분만 캐치해서 다시 그리는게 빠를까요?

    당연히 후자입니다. 전자는 ReFlow, 후자는 RePaint라고 부르는데요

     

    ReFlow의 경우에는 말 그대로 레이아웃을 다시 계산해서 위치를 다시 잡는 연산을 하기 때문에 그만큼 소비가 커서 사용성이 떨어지게 됩니다. 반면에, RePaint의 경우에는 해당 위치에서 다시 그려주기 때문에 레이아웃에 대한 연산이 들어가지 않아 상대적으로 사용성이 좋아지게 됩니다.

    즉, 이 RePaint의 역할을 translate3d를 통해 할 수 있는 것이죠!

     

    3가지 인자를 통해 구현할 수 있고 x, y, z축을 선언하여 해당 속성을 다시 그려주게 됩니다.

    현재 예제에서는 x축에 해당되니 x축에 대해서만 신경쓰면 될 것입니다!

     

    다시 예제로 돌아와서..

    리소스를 다시 생각해보면 총 3개의 리소스가 있고 현재 0부터 시작하게 됩니다. 즉 우리는 이 index를 활용하여 x축을 이동시키면 된다는 것을 눈치챘습니다!!

    추가로 우리는 현재 캐러셀의 넓이가 500px인 것을 감안하여 index가 증가할 수록 width가 좌로 밀려나게 되면(감소) 오른쪽에 있는 다른 리소스를 볼 수 있다는 것을 유추할 수 있네요!

     

    빨리 구현해보겠습니다.

    const prevButton = document.querySelector('.prev');
    const nextButton = document.querySelector('.next');
    const carousel = document.querySelector('.carousel');
    
    let index = 0;
    
    prevButton.addEventListener('click', () => {
       if (index === 0) return;
       index -= 1;
       
       carousel.style.transform = `translate3d(-${500 * index}px, 0, 0)`;
    });
    
    nextButton.addEventListener('click', () => {
       if (index === 2) return;
       index += 1;
       
       carousel.style.transform = `translate3d(-${500 * index}px, 0, 0)`;
    });

     

    여기서 (-)를 하는 부분이 조금 헷갈릴 수도 있는데요

    쉽게 생각하면 밀어내기 라고 이해하시면 됩니다. 우리가 최초에 볼 리소스는 넓이가 500px 일겁니다.

    하지만 가로로 정렬된 오른쪽 리소스를 보기 위해서는 원래 있었던 리소스는 좌로 밀려나야 할테고, 오른쪽에 있던 리소스는 캐러셀 컨테이너에 위치해야겠죠

    예제에서 캐러셀의 넓이는 500px이고 첫 리소스는 0부터 시작하여 500px까지를 차지하고 있으니, 다음 리소스는 500px 이후에 있을 것입니다.

    즉, 현재의 넓이를 왼쪽에 동일한 넓이만큼 밀어낸다면? 다음 리소스가 기존에 있는 리소스에 자리를 차지하겠죠?

     

    한 번 잘 동작하는지 테스트 해보겠습니다.

    잘 동작 하네요!!

     

    하지만 뭔가 딱딱하고 조금 부자연스러움이 있죠?

    그래서 우린 transiton을 활용해서 조금 부드럽게 진행되도록 해보겠습니다.

    style.css

    * {
      box-sizing: border-box;
    }
    
    .carousel-wrapper {
      width: 500px;
      height: 300px;
      overflow: hidden;
    }
    
    .carousel-wrapper > .carousel {
      display: flex;
      transform: translate3d(0, 0, 0);
      transition: transform 0.2s;
    }
    
    .carousel-wrapper > .carousel > img {
      width: 500px;
      height: 300px;
    }

    지금 사용하고 있는 transform을 0.2s에 걸쳐서 부드럽게 슬라이딩 하도록 선언했습니다.

    잘 동작하는지 다시 확인해볼까요?

     

    끝내줍니다.

     

     

     

    이렇게 정말 초 간단하게 캐러셀을 구현해보았습니다. 정말 필요한 스타일링만 하다보니 조금 완성본이 조잡하긴 합니다만...

    캐러셀이 어떻게 동작하는지와 예시 코드들을 통해 눈에 익히고 발전시키면서 차차 손에 익혀간다면 나중에는 더 완성도 있고 멋진 캐러셀을 구현할 수 있을 것 같습니다. :) 👍

    댓글