vue3.0 setup风格实现轮播图

3,023 阅读2分钟

轮播图是一个老生常谈的问题啊~本文提供一个功能齐全,但是样式简陋的轮播图。

简陋的轮播图

主要功能有:

  1. 点击radio跳转
  2. 上一页/下一页 按钮点击
  3. 左滑动和右滑动(F12移动模式可以试)
  4. 自动播放
  5. 无限滚动 虽然样子简陋了点,但是功能还可。

思路

先说一下思路吧,假设有4个图要轮播1->2->3->4。那么安排dom(本文使用div)的时候,要这么安排:4->1->2->3->4->1。

为什么这么搞? 因为要实现无限滚动呐!比如,当前位置是图1,那么左移到图1前的图4,再使用点技巧定位到真·图4,即3后面的图4,这样看起来就像是无限滚动了。说实话,不要无限滚动代码会好写很多。

本文使用的技巧是使用transitioned监听器。(transition是一个css3属性,提供动画变化)。 本文全程使用css3变量控制动画!让JS最少的干涉css

代码

template:

<template>
  <div id="lab-swipe" ref="labRef">
    <!-- imgs -->
    <div id="imgs" @touchstart="touchStart($event)" @touchmove="touchMove($event)" @touchend="touched($event)">
      <div>4</div>
      <div>1</div>
      <div>2</div>
      <div>3</div>
      <div>4</div>
      <div>1</div>
    </div>
    <!-- 选择控制区 -->
    <div id="radios">
      <!-- name="swipe" -->
      <input type="radio" name="radio"
        v-for="i in 4" @click="c(i)" :key="i" :checked="i == checked"/>
    </div>

    <div id="buttons">
      <button @click="before">上一页</button>
      <button @click="after">下一页</button>
    </div>
  </div>
</template>

ts:script lang="ts"

import { onMounted, ref, watch, watchEffect } from "vue";
export default {
  setup() {
    const labRef = ref<any>();
    let sum = 4;
    const transition = (pos: number) => {
      labRef.value;
      let width = getComputedStyle(labRef.value as Element).getPropertyValue("--width");
      let width_num = parseInt(width.split("px")[0]);
      labRef.value.style.setProperty(`--pos`, -1 * pos * width_num + "px");
    };

    const c = (i: number) => {
      let basewidth = 200;
      console.log(i);
      // 注入灵魂
      now.value = i;
      transition(i);
    };

    const now = ref(1);
    // 为了在动画完成后radio再变化!
    const checked=ref(1);
    let beforeReset=false;
    let afterReset =false;
    onMounted(() => {
      // 初始化位置
      transition(1);
      // 完成动画后.....完全是为了复位,真的令人头大...
      (labRef.value as Element).addEventListener("transitionend", () => {
          checked.value = now.value
          if (!afterReset&&!beforeReset)return
          labRef.value.style.setProperty(`--time`, "0s");
          if (afterReset){
            transition(1)
            now.value = 1;  
          }else{
            transition(sum)
            now.value=sum
          }
          setTimeout(() => {
            labRef.value.style.setProperty(`--time`, "1s");
            afterReset= false;
            beforeReset=false;
            checked.value = now.value      
          }, 0);
        });
        // 设置自动播放呢~
        autoDisplay()
    });
    let timer=-1;
    const autoDisplay=()=>{
      if (timer!=-1){
        clearInterval(timer)
      }
      timer = setInterval(()=>{
        after()
      },4000)
    }
    // 之前
    const after = () => {      
      if (now.value == sum) {
          // 让侦听器做一些操作
          afterReset=true;
          transition(sum+1)
      } else {
          now.value++;
          transition(now.value)
      }
    };
    // 之后
    const before = () => {
      if (now.value == 1) {
        // 让侦听器做一些操作
        beforeReset=true;
        transition(0)
      } else {
        now.value--;
        transition(now.value)
      }
    };
    // 应对触摸事件
    let startTime;
    let lastPos:number;
    let startX:number;
    let startY:number;

    const touchStart= (e:any)=>{
      //取消自动播放
      clearInterval(timer);
      timer=-1
      startTime = new Date().getTime();
      if (e.touches.length=== 1){
        startX = e.touches[0].clientX // 记录开始位置
        startY = e.touches[0].clientY // 记录开始位置
      }
      let pos = getComputedStyle(labRef.value as Element).getPropertyValue("--pos");
      let pos_num = parseInt(pos.split("px")[0]);
      lastPos = pos_num;
    }
    // 根据距离移动位置
    const move=(dis:number)=>{
      labRef.value.style.setProperty(`--pos`,lastPos+dis*-1+"px");
    }
    let moveX:number;
    const  touchMove=(e:any)=>{
      //记录移动位置
      moveX = e.touches[0].clientX
      let disX = startX - moveX
      // 冻结动画效果
      labRef.value.style.setProperty(`--time`,"0s");
      // 得到pos值
      move(disX)
    }
    // 最后判定位置
    const touched=(e:any)=>{
      // 释放动画效果
      labRef.value.style.setProperty(`--time`,"1s");
      let width = getComputedStyle(labRef.value as Element).getPropertyValue("--width");
      let width_num = parseInt(width.split("px")[0]);
      let dis = moveX - startX;
      if (dis>width_num/3){
         before()
      }
      if (dis<-width_num/3){
        after()
      }
      //开始动画
      autoDisplay()
    }
    return {
      c,
      labRef,
      after,
      before,
      now,
      checked,
      touchMove,
      touchStart,
      touched
    };
  },
};

scss:style lang="scss"

#lab-swipe {
  position: relative;
  display: flex;
  flex-flow: nowrap row;
  overflow: hidden;
  --width: 200px;
  --pos: -200px;
  width: var(--width);
  //初始--pos:反正会初始化的~不是0,在onMounted里
  --pos: 0;
  --time: 1s;
  height: 150px;
  #imgs {
    display: flex;
    flex-wrap: nowrap row;
    transform: translateX(var(--pos));
    transition: transform var(--time) ease 0s;
    div {
      //position: relative;
      width: 200px;
      height: 150px;
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 2rem;
      flex-shrink: 0;
    }
    div:nth-of-type(2n) {
      background-color: rgb(238, 180, 238);
      //position: -;
    }
    div:nth-of-type(2n + 1) {
      background-color: rgb(209, 209, 110);
    }
  }

  #radios {
    width: 100px;
    height: 20px;
    position: absolute;
    margin-left: auto;
    margin-right: auto;
    text-align: center;
    z-index: 10;
    //background-color: brown;
    bottom: 1rem;
    left: 0;
    right: 0;
  }
  #buttons {
    text-align: center;
    position: absolute;
    z-index: 10;
    left: 0;
    right: 0;
    top: 1rem;
  }
}

css是真的没有做任何优化。。。主要重点是功能!