본문 바로가기
Vuejs_JS_CSS(3)

자바스크립트 관련 - 일반 슬라이드, requestAnimationFrame

by 꿀이다 2022. 9. 26.
반응형

자바스크립트를 사용한 슬라이드, requestAnimationFrame 사용, setTimeout / setInterval 사용하지 않음

좌우 버튼을 클릭해서 슬라이드 형태로 내용이 바뀌는 스크립트

 

html
<div class="imgwrap">
  <div class="img_line">
    <div class="img_area" :style="'transform:translateX('+ info.lx +'px)'">
      <div class="imgbox">
        <a href="#imglink" class="img_link" style="background-image:url(/img1.png)"></a>
        <a href="#userlink" class="user_link">
          <div class="img" style="background-image:url(/photo1.png)"></div>
          <div class="userinfo">
            <p class="title">제목1</p>
            <p class="user">사용자1</p>
          </div>
        </a>
      </div>
      <div class="imgbox">
        <a href="#imglink" class="img_link" style="background-image:url(/img2.png)"></a>
        <a href="#userlink" class="user_link">
          <div class="img" style="background-image:url(/photo2.png)"></div>
          <div class="userinfo">
            <p class="title">제목2</p>
            <p class="user">사용자2</p>
          </div>
        </a>
      </div>
      <div class="imgbox">
        <a href="#imglink" class="img_link" style="background-image:url(/img3.png)"></a>
        <a href="#userlink" class="user_link">
          <div class="img" style="background-image:url(/photo3.png)"></div>
          <div class="userinfo">
            <p class="title">제목3</p>
            <p class="user">사용자3</p>
          </div>
        </a>
      </div>
      <div class="imgbox">
        <a href="#imglink" class="img_link" style="background-image:url(/img4.png)"></a>
        <a href="#userlink" class="user_link">
          <div class="img" style="background-image:url(/photo4.png)"></div>
          <div class="userinfo">
            <p class="title">제목4</p>
            <p class="user">사용자4</p>
          </div>
        </a>
      </div>
    </div>
  </div>
  <div class="btn_line">
    <button class="arrowbtn left" :class="{disabled: info.imgNo === 0}" @click="showImg(-1)"><img src="/backward_img.svg" alt=""></button>
    <button class="arrowbtn right" :class="{disabled: info.imgNo === info.imgLen-1}" @click="showImg(1)"><img src="/forward_img.svg" alt=""></button>
  </div>
  <div class="progress_line">
    <div class="progress_bg">
      <div class="progress_bar" :style="'width:'+ info.pcnt +'%'"></div>
    </div>
    <button class="btn_progress" :class="{on: info.playPause}" @click="bannerPlay"><img src="/play_img.svg" alt=""></button>
    <p class="progress_txt"><span>{{info.imgNo + 1}}</span> / {{info.imgLen}}</p>
  </div>
</div>

 

css
......
.btn_progress::after{content:"";position:absolute;left:0;top:0;display:flex;align-items:center;
                     justify-content:center;width:100%;height:100%;
                     background:url(/pause_img.svg) no-repeat center;opacity:0;
                     transition:opacity 0.2s ease;}
.btn_progress.on img{opacity:0;}
.btn_progress.on::after{opacity:1;}
......

 

js(vue)
const {createApp, ref, computed, onMounted} = Vue;
const visual = {
  setup() {
    // ref() 를 사용하면 js에선 .value로 찾아감. dom에는 .value 없음
    // reactive() 는 항상 .value 필요없음
    const info = ref({
      imgNo: 0,// 이미지 위치
      imgLen: null,// 전체 배너 개수
      playPause: false, // 자동 슬라이드 여부

      // 이미지 슬라이드 x위치
      lx: computed(() => {
        return -(info.value.imgNo * 400);// 400 = 이미지 영역 가로 길이
      }),

      // progressbar의 %
      pcnt: computed(() => {
        return Math.ceil((info.value.imgNo+1) / info.value.imgLen * 100);
      }),

      bannerPlay: null, // requestAnimationFrame 에서 timestamp 비교용
      bannerPlayId: null, // requestAnimationFrame id
      bannerTime: 4000, // delay time : 4초
    })

    const showImg = (no) => {
      // 이전으로
      if(no < 0){
        info.value.imgNo < 0 ?  0 : info.value.imgNo -= 1;
      }
      // 다음으로
      else{
        info.value.imgNo > info.value.imgLen -1 ? info.value.imgLen -1 : info.value.imgNo += 1;
      }

      // 이전, 다음 버튼으로 이동 시 멈춤. 멈추면 timestamp 초기화해서 다시 n초를 처음부터
      info.value.playPause = false;
      info.value.bannerPlay = null;
      cancelAnimationFrame(info.value.bannerPlayId);
    }

    const bannerPlay = () => {
      // 자동 실행중이면 멈춤
      if(info.value.playPause) {
        info.value.playPause = false;

        // 멈추면 timestamp 초기화해서 다시 n초를 처음부터
        info.value.bannerPlay = null;
        cancelAnimationFrame(info.value.bannerPlayId);
      }
      // 멈춰진 상태면 자동 실행
      else {
        info.value.playPause = true;
        requestAnimationFrame(bannerMove);
      }
    }

    bannerMove = (timestamp) =>{
      if(!info.value.bannerPlay) info.value.bannerPlay = timestamp;
      let timer = timestamp - info.value.bannerPlay;
      //console.log(" : ", info.value.bannerPlay, " : ", timestamp, " : ", timer);

      // n초마다 실행
      if(timer < info.value.bannerTime){
        info.value.bannerPlayId = requestAnimationFrame(bannerMove);
      }
      // n초 후
      else {
        // imgNo(현재 번호)가 전체 길이(예: 4)보다 작으면
        if (info.value.imgNo < info.value.imgLen-1) {
          // imgNo 증가
          info.value.imgNo += 1;
        }
        // imgNo(현재 번호)가 전체 길이와 같으면
        else if (info.value.imgNo === info.value.imgLen-1) {
          // imgNo 초기화
          info.value.imgNo = 0;
        }

        // 다시 n초를 적용하기 위해 timestamp 초기화
        info.value.bannerPlay = timestamp;

        requestAnimationFrame(bannerMove);

      }
    }

    onMounted(() => {
      // 임의로 tag의 개수를 찾아서 전체 개수를 체크하지만, 데이터를 받을 때 데이터 길이를 바로 넣어도 됨
      // 전체 배너 개수
      info.value.imgLen = document.querySelectorAll(".imgbox").length;
    })

    return {
      info, showImg, bannerPlay,
    }
  }
};
createApp(visual).mount("#visual");
반응형

댓글