프로필

프로필 사진
Popomon
Frontend Developer
(2020/12 ~)

    카테고리

    포스트

    [많이쓰는 UI] 배너 슬라이더 (순수 자바스크립트)

    2021. 7. 10. 18:28

    꿈가게: To Do List - iOS

    꿈가게: To Do List - Android

    요즘 쇼핑몰/영화/웹서비스... 홈페이지에 들어가 보면 아래와 같이 메인배너에 슬라이더를 넣는 경우를 많이 보실 수 있습니다. (CGV)

    이런 슬라이더를 구현한 오픈소스는 매우 많지만, 메인 배너인 만큼 디자이너의 요구사항에 맞게 화면을 만들어 주어야 하기 때문에, 화면의 스타일을 입맛에 맞게 잘 바꾸어 주어야 합니다.

    스타일을 자유롭게 변경해야하기 때문에, 슬라이더는 각각 사이트마다 별도로 구현하는 경우가 많습니다. 그래서 이번 포스팅에서는 슬라이더의 뼈대를 만드는 방법을 설명하려고 합니다.

    슬라이더의 뼈대를 만드는 방법

    우선 슬라이더는 디자이너가 만든 화면을 넘기면서 보여주는 화면이기 때문에, HTML 코드로 먼저 열심히 퍼블리싱을 한 다음에 적용해야 합니다. 제 옆에는 ... 디자이너분이 없기 때문에 매우 단순한 숫자 카드로 작업을 진행하도록 하겠습니다.

    슬라이더에 넣을 웹 화면 작성

    정말 예쁘지 않은 숫자카드의 CSS를 작성해줍니다.

    <style>
    .slider {
      width: 300px;
      height: 300px;
    }
    .box {
      text-align: center;
      line-height: 300px;
      font-size: 140px;
      color: #fff;
      font-weight: bold;
    }
    </style>

    이어서 이에 맞는 HTML 코드를 작성해 줍니다. 이 때, 겉에 감싸고있는 section 태그와 그 하위에는 보여주고 싶은 메인 배너 div를 넣어주면 됩니다.

    section 태그 - 배너 영역

    div 태그 - 배너 한장

    <section id="slider1" class="slider">
      <div class="box" style="background-color: #000;">1</div>
      <div class="box" style="background-color: #000;">2</div>
      <div class="box" style="background-color: #000;">3</div>
      <div class="box" style="background-color: #000;">4</div>
      <div class="box" style="background-color: #000;">5</div>
    </section>

    슬라이더의 상태관리 코드 추가

    슬라이더에 필요한 상태값은 다음과 같습니다.

    현재 페이지 (index)

    슬라이더 이동이 완료되었는가? (isMoved)

    슬라이더의 이동 속도는 얼마나 빠른가? (speed)

    // 상태
    let index = 1;
    let isMoved = true;
    const speed = 1000; // ms

    슬라이더의 속도와 방향

    슬라이더를 세로 / 가로 중에서 어떤 방향으로 진행할 것인지 정해야 하는데, 저는 입력을 받아서 방향을 결정하는 것으로 작성하였습니다.

    // 속도
    const transform = "transform " + speed / 1000 + "s";
    
    // 방향
    let translate = (i) => "translateX(-" + 100 * i + "%)";
    if (type === "V") {
      translate = (i) => "translateY(-" + 100 * i + "%)";
    }

    슬라이더를 구성하는 요소 만들기

    배너 슬라이더를 구성하는 돔 요소에는 배너 영역(slider) / 컨테이너(container) / 배너 여러장(boxes) 이 있습니다.

    배너 영역 (slider)

    target 은 배너 영역의 클래스(class) 또는 아이디(id) 값이 들어갑니다. 현재 HTML 코드에서는 #slider 가 될 것입니다.

    // 슬라이더
    const slider = document.querySelector(target);
    const sliderRects = slider.getClientRects()[0];
    slider.style["overflow"] = "hidden";

    컨테이너 (container)

    이전에 HTML 코드에서는 배너 영역 안에 바로 배너들을 집어넣어 주었습니다. 하지만 슬라이더를 만들러면 이 배너들을 감싸는 컨테이너를 하나 만들어서 포장해 주어야 합니다. 따라서, 돔을 만드는 함수인 document.createElement 함수를 사용하여 컨테이너를 만들어줍니다.

    // 슬라이더 화면 컨테이너
    const container = document.createElement("div");
    container.style["display"] = "flex";
    container.style["flex-direction"] = type === "V" ? "column" : "row";
    container.style["width"] = sliderRects.width + "px";
    container.style["height"] = sliderRects.height + "px";
    container.style["transform"] = translate(index);

    배너 여러장 (boxes)

    숫자 카드가 1 ~ 5 까지 있다고 하면, 연속되는 슬라이더를 보여주기 위해서, 맨 앞에는 5를 맨 뒤에는 1을 추가하여 다시 배너 목록을 구성해 줍니다.

    // 슬라이더 화면 목록
    let boxes = [].slice.call(slider.children);
    boxes = [].concat(boxes[boxes.length - 1], boxes, boxes[0]);
    
    // 슬라이더 화면 스타일
    const size = boxes.length;
    for (let i = 0; i < size; i++) {
      const box = boxes[i];
      box.style["flex"] = "none";
      box.style["flex-wrap"] = "wrap";
      box.style["height"] = "100%";
      box.style["width"] = "100%";
      container.appendChild(box.cloneNode(true));
    }

    이어서, 눈속임 이벤트를 2가지 적용합니다.

    container.addEventListener("transitionend", function () {
      // 처음으로 순간이동
      if (index === size - 1) {
        index = 1;
        container.style["transition"] = "none";
        container.style["transform"] = translate(index);
      }
      // 끝으로 순간이동
      if (index === 0) {
        index = size - 2;
        container.style["transition"] = "none";
        container.style["transform"] = translate(index);
      }
    });

    화면이 5 에서 1(복사)로 이동한 후, 트랜지션의 애니메이션을 잠시 꺼두고 1(원본) 카드 위치로 순간이동하고,

    // 처음으로 순간이동
    if (index === size - 1) {
      index = 1;
      container.style["transition"] = "none";
      container.style["transform"] = translate(index);
    }

    화면이 1에서 5(복사)로 이동하면 트랜지션의 애니메이션을 잠시 꺼두고 5(원본) 카드 위치로 순간이동 합니다.

    // 끝으로 순간이동
    if (index === 0) {
      index = size - 2;
      container.style["transition"] = "none";
      container.style["transform"] = translate(index);
    }

    그런데, 이 순간이동하는 속도보다 슬라이드를 이동하는 함수가 더 빨리 실행된다면 눈속임을 구현할 수 없습니다. 그래서 애니메이션의 속도에 맞춰서 트랜지션이 시작함과 동시에 애니메이션 시간(speed) 동안은 슬라이드 이동 함수를 실행할 수 없도록 제약을 걸어줍니다.

    // 처음/마지막 화면 눈속임 제약
    container.addEventListener("transitionstart", function () {
      isMoved = false;
      setTimeout(() => {
      	isMoved = true;
      }, speed);
    });

    마지막으로 배너 영역(slider)에 컨테이너(container)를 붙여줍니다.

    // 슬라이더 붙이기
    slider.innerHTML = "";
    slider.appendChild(container);

    슬라이더 이벤트 함수 만들기

    슬라이더에는 기본적으로 다음(next)/이전(prev)/특정배너이동(move) 이 세가지를 제공합니다. 슬라이더에 페이지를 표시하거나, 이동하는 화살표 버튼이 있는 경우도 많이 있는데, 그 부분은 페이지마다 너무 다르기 때문에, 뼈대 만들기에 어울리지 않다고 생각되므로 생략하겠습니다.

    move: function (i) {
      if (isMoved === true) {
        index = i;
        container.style["transition"] = transform;
        container.style["transform"] = translate(index);
      }
    },
    next: function () {
      if (isMoved === true) {
        index = (index + 1) % size;
        container.style["transition"] = transform;
        container.style["transform"] = translate(index);
      }
    },
    prev: function () {
      if (isMoved === true) {
        index = index === 0 ? index + size : index;
        index = (index - 1) % size;
        container.style["transition"] = transform;
        container.style["transform"] = translate(index);
      }
    }

     

    See the Pen Pure javascript slider by 홍지성 (@lnbvocxe) on CodePen.

    'Publishing > 많이쓰는 UI' 카테고리의 다른 글

    [많이쓰는 UI] 댓글  (0) 2021.07.12
    [많이쓰는 UI] 탭 뷰  (0) 2021.07.12
    [많이쓰는 UI] 로딩 화면  (0) 2021.07.12
    [많이쓰는 UI] 모달 팝업  (0) 2021.07.12
    [많이쓰는 UI] 그리드 시스템 (Flexbox Grid)  (0) 2021.07.10