본문 바로가기

Javascript/응용프로젝트

Auto play, Indicator 슬라이더 구현하기

강의를 보며 혼자 정리하는 느낌이라.. 조금 조잡하고 설명이 정확하지 않을 수 있습니다
HTML,CSS는 강의자료로 받았고, 기능만 구현했습니다.

완성본


기능 설명

  1. Previous , Next 버튼 슬라이드
  2. Indicator를 사용해, 현재 슬라이드가 몇 번째인지 알려주고, Indicator 를 클릭 시, 클릭한 인덱스의 슬라이드로 갈 수 있게 함.
  3. setInterval 함수와 clearInterval 함수를 활용해, 자동으로 슬라이드가 넘어가고 오토 슬라이드를 정지할 수 있는 기능

HTML 과 CSS

강의 자료이기때문에, 참고용으로 보시라고 캡쳐본으로 올립니다

index.html
style.css


javascript 초기 작업

스크립트는 ES6 클래스 문법을 사용했습니다. ( 저도 아직 ES6 문법에 익숙치 않아서 기능 구현에 중점을 맞춰서 설명합니다 )

 

슬라이더 기능에서 사용 할 변수를 지정해줍니다.

  #currentPosition = 0; // 현재 슬라이드 순서

  #sliderNumber = 0; // 총 슬라이드의 length

  #slideWidth = 0; // 슬라이드 한개의 넓이

  #intervalId; // setInterval 중지를 위해서 함수를 넣어놓을 공간

  #autoPlay = true; // setInterval이 실행중인지 알기 위한 변수

 

사용 할 Element 들 찾아주기

  sliderWrapEl; // 슬라이더의 전체 요소를 묶고 있는 아이

  sliderListEl; // 슬라이더의 컨텐츠들을 (즉 슬라이드 이미지들 )을 묶고 있는 아이

  nextBtnEl; // 다음 버튼

  previousBtnEl; // 이전 버튼

  indicatorWrapEl; // 인디케이터를 묶고 있는 부모 요소

  controlWrapEl; // Autoplay를 조절할 수 있게하는 버튼을 묶어놓은 부모 요소
  
  assignElement() {
    this.sliderWrapEl = document.getElementById('slider-wrap');
    this.sliderListEl = this.sliderWrapEl.querySelector('#slider');
    this.nextBtnEl = this.sliderWrapEl.querySelector('#next');
    this.previousBtnEl = this.sliderWrapEl.querySelector('#previous');
    this.indicatorWrapEl = this.sliderWrapEl.querySelector('#indicator-wrap');
    this.controlWrapEl = this.sliderWrapEl.querySelector('#control-wrap');
  }
sliderWrapEl 을 통해서 querySelector 로 Element 를 찾아주는 이유
- Document 부터 요소들을 찾게하지 않고, 이 요소들을 전체적으로 묶어주는 아이부터 찾도록 해서 비용 절감을 할 수 있도록 하기 위해서 (라고합니다)

 

초기화 작업을 해줍니다. 

this에 바인딩되어 있는 인스턴스에 프로퍼티나 메서드를 추가하고 생성자 함수가 인수로 전달받은 초기값을 인스턴스 프로퍼티에 할당하여 초기화하거나 고정값을 할당하기위해 작업한 것 같아요
  • 슬라이드의 총 갯수를 알아내는 함수
  initSliderNumber() {
    this.#sliderNumber = this.sliderListEl.querySelectorAll('li').length;
  } // 슬라이드의 개수를 알기 위한 초기함수
  • 슬라이드 한 개의 넓이를 알아내는 함수
  initSlideWidth() {
    this.#slideWidth = this.sliderWrapEl.clientWidth;
  } // 슬라이드 컨텐츠의 한개 넓이를 알기 위한 함수
  • 슬라이드의 전체 넓이를 동적으로 할당해주는 함수
  initSliderListWidth() {
    this.sliderListEl.style.width = `${
      this.#sliderNumber * this.#slideWidth
    }px`;
  } // 슬라이드 컨텐츠의 개수와 슬라이드 한 개의 넓이를 곱해서, 전체 넓이를 동적으로 할당해주기 위한 함수
  • addEventListener 함수를 사용하는 코드 묶어놓기
addEvent() {추후에 들어갈 addEventListener 함수}
  • constructor 에 클래스 필드를 선언하고 초기화 해주기
  constructor() {
    this.assignElement();
    this.initSliderNumber();
    this.initSlideWidth();
    this.initSliderListWidth();
    this.addEvent();
  }
constructor 에 짧게.. 알아봤습니다

- 인스턴스를 생성하고 클래스 필드를 초기화하기 위한 특수한 메서드
- constructor 는 클래스 안에 한 개만 존재 가능
- 클래스 필드의 선언과 초기화는 반드시 constructor 내부에서 실시
- constructor 내부에서 선언한 클래스 필드는 클래스가 생성 할 인스턴스에 바인딩 된다

 


기본 슬라이드 구현하기

 

next 버튼과, previous 버튼에게 이벤트 리스너를 준다

  addEvent() {
    this.nextBtnEl.addEventListener('click', this.moveToRight.bind(this));
    this.previousBtnEl.addEventListener('click', this.moveToLeft.bind(this));
  }
콜백 함수를 사용했기 때문에, bind 메소드를 활용해, this 를 바인딩 해준다.

 

moveToRight ()

  moveToRight() {
    this.#currentPosition += 1;
    if (this.#currentPosition === this.#sliderNumber) {
      this.#currentPosition = 0;
    }
    this.sliderListEl.style.left = `-${
      this.#slideWidth * this.#currentPosition
    }px`;
  }
1. 버튼을 클릭 시, currentPosition 의 값이 +1 될 수 있도록 했음 (오른쪽으로 가려면, left 값이 늘어나야하기 때문에)
2. 현재 슬라이드 순서가 슬라이더의 마지막 순서의 인덱스와 같을 시, 첫 번째 슬라이드로 이동 될 수 있게 if 문 사용
3. 버튼을 누를 때마다 옆으로 이동되는 모습을 구현하기 위해서 style.left를 사용
4. left 이동하는 위치는, 현재 슬라이드 순서값과, 슬라이드 한개의 넓이를 곱해서 구현

 

moveToLeft ()

  moveToLeft() {
    this.#currentPosition -= 1;
    if (this.#currentPosition === -1) {
      this.#currentPosition = this.#sliderNumber - 1;
    }
    this.sliderListEl.style.left = `-${
      this.#slideWidth * this.#currentPosition
    }px`;
  }
1. 버튼을 클릭 시, currentPosition 의 값이 -1 될 수 있도록 했음 (왼쪽으로 가려면, left 값이 줄어들어야하기 때문에)
2. 현재 슬라이드 순서가 슬라이더의 첫번째 순서(index = 0)보다 적을 경우 , 마지막 순서 슬라이드로 이동 될 수 있게 if 문 사용
3. 버튼을 누를 때마다 옆으로 이동되는 모습을 구현하기 위해서 style.left를 사용
4. left 이동하는 위치는, 현재 슬라이드 순서값과, 슬라이드 한개의 넓이를 곱해서 구현

인디케이터(Indicator) 를 활용한 슬라이더 구현하기

 

인디케이터를 동적으로 생성할 수 있게 한다 ( 슬라이드 개수에 맞춰서 생성 될 수 있게 )

  createIndicator() {
    const docFragment = document.createDocumentFragment();
    for (let i = 0; i < this.#sliderNumber; i += 1) {
      const li = document.createElement('li');
      li.dataset.index = i;
      docFragment.appendChild(li);
    }
    this.indicatorWrapEl.querySelector('ul').appendChild(docFragment);
  }
  constructor() {
    this.assignElement();
    this.initSliderNumber();
    this.initSlideWidth();
    this.initSliderListWidth();
    this.addEvent();
    this.createIndicator(); // 추가해주기
  }
1. 슬라이드 개수만큼 인디케이터를 생성해주기 위해서 for 문을 사용해준다.
2. 인디케이터는 ul 안에 li 로 생성해준다.
3. li 안에 data-index 를 만들어주어, index 를 알 수 있도록 한다.
4. indicatortWrap (div) 안에 있는 ul 안에 li 를 생성해주기 위해서 appendChild 를 사용해주도록 한다.
5. constructor 안에 함수를 추가해준다.

* createDocumentFragment()

다른 노드를 담는 임시 컨테이너 역할을 하는 특수노드이다. 하지만 Element처럼, appendChild()와 insertBefore() 등으로 조작할 수 있는 자손 객체를 가질 수 있다.

 

인디케이터에서 현재 슬라이드를 알게 해주는 함수

  setIndicator() {
    this.indicatorWrapEl.querySelector('li.active')?.classList.remove('active');
    this.indicatorWrapEl
      .querySelector(`ul li:nth-child(${this.#currentPosition + 1})`)
      .classList.add('active');
  }
  constructor() {
    this.assignElement();
    this.initSliderNumber();
    this.initSlideWidth();
    this.initSliderListWidth();
    this.addEvent();
    this.createIndicator();
    this.setIndicator();
  }
1. 만들어놓은 li (인디케이터 요소) 중 "active" 라는 클래스를 갖고 있는 요소에게 클래스를 지워주는 작업을 해준다.
2. li 태그에서 현재 슬라이드 순서에 맞게 "active" 클래스를 추가해준다
- +1 를 해준 이유는 nth-child 는 0부터 시작하지 않고 1부터 시작하기 때문에, +1 를 추가해주어야함.
3. constructor 에 함수를 추가해준다.
  moveToRight() {
    this.#currentPosition += 1;
    if (this.#currentPosition === this.#sliderNumber) {
      this.#currentPosition = 0;
    }
    this.sliderListEl.style.left = `-${
      this.#slideWidth * this.#currentPosition
    }px`;
    this.setIndicator(); // 추가
  }

  moveToLeft() {
    this.#currentPosition -= 1;
    if (this.#currentPosition === -1) {
      this.#currentPosition = this.#sliderNumber - 1;
    }
    this.sliderListEl.style.left = `-${
      this.#slideWidth * this.#currentPosition
    }px`;

    this.setIndicator(); // 추가
  }
1. moveToRight 와 moveToLeft 함수가 실행 될 때도, 인디케이터가 실행될 수 있도록 setIndicator() 함수를 호출한다.

 

인디케이터를 클릭하면 슬라이드가 이동하는 함수

  addEvent() {
    this.nextBtnEl.addEventListener('click', this.moveToRight.bind(this));
    this.previousBtnEl.addEventListener('click', this.moveToLeft.bind(this));
    this.indicatorWrapEl.addEventListener(
      'click',
      this.onClickIndicator.bind(this),
    ); // 추가 된 함수
  }
  onClickIndicator(event) {
    const indexPosition = parseInt(event.target.dataset.index, 10);
    if (Number.isInteger(indexPosition)) {
      this.#currentPosition = indexPosition;
      this.sliderListEl.style.left = `-${
        this.#slideWidth * this.#currentPosition
      }px`;
      this.setIndicator();
    }
  }
1. 인디케이터를 감싸고 있는 부모 요소에게 addEventListener 함수를 준다 (이벤트 버블링때문에)
2. indexPosition 이라는 변수에 만들어놓은 li 속성인 data-index를 숫자 타입으로 바꿔준다.
- 10을 넣은 이유는 10진법으로 만들어달라고 하는 것이라고 합니다.
3. indexPosition 이 정수라면, moveTo 함수와 같은 이벤트를 준다.
4. 바뀐 currentPosition 값을 인디케이터에도 적용해줘야하기 때문에 , setIndicator() 함수를 호출한다.

AutoPlay 슬라이더 구현하기

 

AutoPlay 기본 기능 구현하기

  initAutoPlay() {
    this.#intervalId = setInterval(this.moveToRight.bind(this), 3000);
  }
  constructor() {
    this.assignElement();
    this.initSliderNumber();
    this.initSlideWidth();
    this.initSliderListWidth();
    this.addEvent();
    this.createIndicator();
    this.setIndicator();
    this.initAutoPlay();
  }
1. claerInterval() 를 사용하기 위해서 IntervalId 에 setInterval 함수를 구현해준다.
2. moveToRight 의 기능이 3초 간격으로 실행될 수 있도록 해준다.
- 이 함수 또한 콜백 함수이기 때문에 this 를 바인딩 해준다 (bind 메소드)
3. constructor 에 initAutoPlay() 를 추가해준다.

 

AutoPlay 실행, 정지 기능 구현하기 ( togglePlay )

  addEvent() {
    this.nextBtnEl.addEventListener('click', this.moveToRight.bind(this));
    this.previousBtnEl.addEventListener('click', this.moveToLeft.bind(this));
    this.indicatorWrapEl.addEventListener(
      'click',
      this.onClickIndicator.bind(this),
    );
    this.controlWrapEl.addEventListener('click', this.togglePlay.bind(this)); // 추가
  }

  togglePlay(event) {
    if (event.target.dataset.status === 'play') {
      this.#autoPlay = true;
      this.controlWrapEl.classList.add('play');
      this.controlWrapEl.classList.remove('pause');
      this.initAutoPlay();
    } else if (event.target.dataset.status === 'pause') {
      this.#autoPlay = false;
      this.controlWrapEl.classList.remove('play');
      this.controlWrapEl.classList.add('pause');
      clearInterval(this.#intervalId);
    }
  }
1. autoplay 의 버튼을 감싸고 있는 부모요소에게 addEventListener 함수를 적용
2. 버튼의 속성 중 data-status 를 활용해준다.
- play 일 경우는 만들어놓은 autoplay 변수를 true 로 만들어준다.
- 부모요소에게 "play" 라는 클래스를 추가해주고 "pause" 라는 클래스를 제거해준다. (현재 실행 상태를 알려주기 위해서)
- initAutoPlay () 함수를 호출 해, autoplay 가 실행될 수 있도록 한다.

- pause 일 경우는 만들어놓은 autoplay 변수를 false 로 만들어준다.
- 부모요소에게 "play" 라는 클래스를 제거하고, "pause" 라는 클래스를 추가해준다. (현재 실행 상태를 알려주기 위해서)
- clearInterval() 함수에 실행되고 있던 autoPlay setInterval 함수를 넣어, 실행을 멈추게 해준다.

 

AutoPlay 가 실행되는 중에 버튼 클릭 이벤트를 사용할 경우

  moveToRight() {
    this.#currentPosition += 1;
    if (this.#currentPosition === this.#sliderNumber) {
      this.#currentPosition = 0;
    }
    this.sliderListEl.style.left = `-${
      this.#slideWidth * this.#currentPosition
    }px`;
    if (this.#autoPlay) {
      clearInterval(this.#intervalId);
      this.#intervalId = setInterval(this.moveToRight.bind(this), 3000);
    } // 추가
    this.setIndicator();
  }

  moveToLeft() {
    this.#currentPosition -= 1;
    if (this.#currentPosition === -1) {
      this.#currentPosition = this.#sliderNumber - 1;
    }
    this.sliderListEl.style.left = `-${
      this.#slideWidth * this.#currentPosition
    }px`;
    if (this.#autoPlay) {
      clearInterval(this.#intervalId);
      this.#intervalId = setInterval(this.moveToRight.bind(this), 3000);
    } // 추가
    this.setIndicator();
  }
1. autoPlay 라는 변수가 true 라면 (즉 play 중이라면), 버튼을 클릭 시 autoplay 를 중단하고 버튼 클릭 이벤트를 수행하게 함.
2. 버튼 클릭 이벤트가 종료된다면 다시 autoplay를 이어나가도록 함. (3초 뒤에 다시 이벤트 실행)

전체 기능 구현 코드