Vue.jsでスライドショーを自作

Vue.jsの勉強中でスライドショーを自作したので自分用のメモ
カスタムイベントtransitionendを定義して終了時に再度setIntervalを実行。

```html
<div id="top_slide">
    <div class="sliderpro_wrap">
      <div class="sliderpro_main" ref="getParentWidth">
        <ul :style="{width:containerW * itemsCount + 'px',transform: `translateX(${sliderTransform}px)`}"
          v-transitionend="slideInterval" @touchstart="touchstart" @touchmove="touchMove" @touchend="touchEnd">
          <li v-for="item in items" :key="item.id" :style="{width:containerW + 'px'}"><img :src="item.src"></li>
        </ul>

        <div class="sliderpro_arrows">
          <div class="sliderpro_arrow sliderpro_arrow_prev" v-on:click="onClickPrev"></div>
          <div class="sliderpro_arrow sliderpro_arrow_next" v-on:click="onClickNext"></div>
        </div>
      </div>

      <div class="sliderpro_thumbnails">
        <ul>
          <li v-for="(item,index) in items" :key="item.id" v-on:click="onClickThumbnal(index)"
            :class="{ active: item.activeClass}"><img :src="item.src"></li>
        </ul>
      </div>
    </div>
  </div>
```
```javascript
const top_slide = Vue.createApp({
    data: function () {
      return {
        // カルーセル画像の配列
        items: [
          {
            id: 1,
            src: "img/slide/img_01.jpg"
          },
          {
            id: 2,
            src: "img/slide/img_02.jpg"
          },
          {
            id: 3,
            src: "img/slide/img_03.jpg"
          }
        ],
        sliderTransform:0,
        selectedIndex: 0,
        containerW:0,
        timer:false,
        startX:0,
        moveX:0,
      }
    },
    mounted () {
      this.initFunc();
      // 要素の幅を取得するメソッド
      this.getTargetWidth();
      // ユーザーがウィンドウサイズを変更したら実行されるようにする
      window.addEventListener('resize', this.getTargetWidth)
    },
    beforeDestroy(){
      //コンポーネントが破棄されたとき
      this.slideStopInterval();
    },
    computed: {
      itemsCount:function(){
        return this.items.length
      }
    },
    methods:{
      initFunc:function(){
        this.onClickThumbnal(this.selectedIndex);
        this.slideInterval();
      },
      getTargetWidth(){
        this.containerW = this.$refs.getParentWidth.clientWidth;
        this.sliderTransform = -(this.containerW * this.selectedIndex);
      },
      onClickPrev:function(){
        this.selectedIndex < 1 ? this.selectedIndex = this.itemsCount -1:  this.selectedIndex -= 1;
        this.onClickThumbnal(this.selectedIndex);
      },
      onClickNext:function(){
        this.selectedIndex < this.itemsCount - 1 ? this.selectedIndex += 1 :  this.selectedIndex = 0;
        this.onClickThumbnal(this.selectedIndex);
      },
      onClickThumbnal:function(index){
      this.slideStopInterval(); //スライド一旦停止
      this.selectedIndex = index;
       this.sliderTransform = -(this.containerW * index);
       this.items.filter(function(value,f_index,array){
        //activeClass切り替え
        return f_index === index ? value.activeClass = true: value.activeClass = false
       });
      },
      slideInterval:function(){
        this.timer = setInterval(function(){
          this.onClickNext();
        }.bind(this),4000);
      },
      slideStopInterval(){
        if(this.timer){
          clearInterval(this.timer);
          this.timer = 0;
        }
      },
      touchstart(e){
        this.slideStopInterval();
        this.startX = e.touches[0].pageX;
      },
      touchMove(e){
        this.moveX = e.touches[0].pageX - this.startX
      },
      touchEnd(){
        if(this.moveX > 10){
         this.onClickPrev();
          //右スワイプ
        }else if(this.moveX < -10){
          //左スワイプ
          this.onClickNext();
        }
        this.slideStopInterval();
      }
    }
  });
  top_slide.directive('transitionend', {
    mounted(el,binding) {
      el.addEventListener('transitionend', () => {
        binding.value();
      });
  }
});

top_slide.mount("#top_slide");
```

```css
.sliderpro_wrap{
    position: relative;
    max-width: 1400px;
    margin: 0 auto;
}
.sliderpro_wrap .sliderpro_main{
    overflow: hidden;
    position: relative;
    margin-bottom: 10px;
}
.sliderpro_wrap .sliderpro_main ul{
    transition: transform 1.1s ease-in-out;
}
.sliderpro_wrap .sliderpro_main ul > li{
    float: left;
}
.sliderpro_wrap img{
    width: 100%;
}

.sliderpro_wrap .sliderpro_thumbnails ul{
   letter-spacing: -0.5em;
   text-align: center;
}
.sliderpro_wrap .sliderpro_thumbnails ul li{
    cursor: pointer;
    display: inline-block;
    letter-spacing: normal;
    vertical-align: top;
    width: 9%;
    margin-right: 1.111%;
}
.sliderpro_wrap .sliderpro_thumbnails ul li:nth-child(10){
    margin-right: 0;
}
.sliderpro_wrap .sliderpro_thumbnails ul li:not(:nth-child(-n + 10)){
    margin-top: 1.1111%;
}
.sliderpro_wrap .sliderpro_thumbnails ul img{
    -webkit-filter: grayscale(100%);
    filter: grayscale(100%);
    transition:filter 0.5s ease-in-out;
}
.sliderpro_wrap .sliderpro_thumbnails ul li.active img,
.sliderpro_wrap .sliderpro_thumbnails ul li:hover img{
    -webkit-filter: grayscale(0%);
    filter: grayscale(0%);
}

/* 矢印ここから */
.sliderpro_wrap .sliderpro_arrows .sliderpro_arrow{
    position: absolute;
    left: 20px;
    top: 50%;
    transform: translateY(-50%) rotate(45deg);
    transform-origin: center center;
    width: 25px;
    height: 25px;
    border-left: 6px solid #fff;
    border-bottom: 6px solid #fff;
    transition: opacity 0.6s ease-in-out;
    cursor: pointer;
}
.sliderpro_wrap .sliderpro_arrows .sliderpro_arrow.sliderpro_arrow_next{
    transform: translateY(-50%) rotate(-135deg);
    left: auto;
    right: 20px;
}
.sliderpro_wrap .sliderpro_arrows .sliderpro_arrow:hover{
    opacity: 0.7;
}


@media screen and (max-width:767px) {
    .sliderpro_wrap .sliderpro_arrows .sliderpro_arrow{
        width: 15px;
        height: 15px;
        border-width: 3px;
    }
}
```