Vue3走马灯(Carousel)

Vue2走马灯(Carousel)

可自定义设置以下属性: 

  • 走马灯图片数组(imageData),类型:Array<{title: string, link?: string, imgUrl: string}>,默认 []

  • 自动滑动轮播间隔(interval),类型:number,默认 3000ms

  • 走马灯宽度(width),类型:number|string,默认 '100%'

  • 走马灯高度(height),类型:number|string,默认 '100vh'

  • 是否显示导航(navigation),默认 true

  • 是否显示分页(pagination),默认 true

  • 用户操作导航或分页之后,是否禁止自动切换(disableOnInteraction),默认 true

  • 鼠标悬浮时暂停自动切换,鼠标离开时恢复自动切换(pauseOnMouseEnter),默认true

效果如下图:

 ①创建走马灯组件Carousel.vue:

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { rafTimeout, cancelRaf } from '../index'
interface Image {
  title: string, // 图片名称
  link?: string, // 图片跳转链接
  imgUrl: string // 图片地址
}
const props = defineProps({
    imageData: { // 走马灯图片数组
      type: Array<Image>,
      default: () => []
    },
    interval: { // 自动滑动轮播间隔
      type: Number,
      default: 3000
    },
    width: { // 走马灯宽度
      type: [Number, String],
      default: '100%'
    },
    height: { // 走马灯高度
      type: [Number, String],
      default: '100vh'
    },
    navigation: { // 是否显示导航
      type: Boolean,
      default: true
    },
    pagination: { // 是否显示分页
      type: Boolean,
      default: true
    },
    disableOnInteraction: { // 用户操作导航或分页之后,是否禁止自动切换。默认为true:停止。
      type: Boolean,
      default: true
    },
    pauseOnMouseEnter: { // 鼠标悬浮时暂停自动切换,鼠标离开时恢复自动切换,默认true
      type: Boolean,
      default: true
    }
  })
const toLeft = ref(true) // 左滑标志,默认左滑
const left = ref(0) // 滑动偏移值
const transition = ref(false) // 暂停时为完成滑动的过渡标志
const slideTimer = ref() // 轮播切换定时器
const moveRaf = ref() // 滑动效果回调标识
const targetMove = ref() // 目标移动位置
const switched = ref(false) // 是否在进行跳转切换,用于区别箭头或自动切换(false)和跳转切换(true)
const carousel = ref() // DOM引用
const activeSwitcher = ref(1) // 当前展示图片标识

const carouselWidth = computed(() => { // 走马灯区域宽度
  if (typeof props.width === 'number') {
    return props.width + 'px'
  } else {
    return props.width
  }
})
const carouselHeight = computed(() => { // 走马灯区域高度
  if (typeof props.height === 'number') {
    return props.height + 'px'
  } else {
    return props.height
  }
})
const totalWidth = computed(() => { // 容器宽度:(图片数组长度+1) * 图片宽度
  return (props.imageData.length + 1) * imageWidth.value
})
const len = computed(() => { // 图片数量
  return props.imageData.length
})

onMounted(() => {
  getFPS() // 获取浏览器的刷新率
  getImageSize() // 获取每张图片大小
})

const fpsRaf = ref() // fps回调标识
const fps = ref(60)
const step = computed(() => { // 移动参数(120fps: 24, 60fps: 12)
  if (fps.value === 60) {
    return 12
  } else {
    return 12 * (fps.value / 60)
  }
})
function getFPS () { // 获取屏幕刷新率
  // @ts-ignore
  const requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame
  var start: any = null
  function timeElapse (timestamp: number) {
    /*
      timestamp参数:与performance.now()的返回值相同,它表示requestAnimationFrame() 开始去执行回调函数的时刻
    */
    // console.log('timestamp:', timestamp)
    if (!start) {
      if (fpsRaf.value > 10) {
        start = timestamp
      }
      fpsRaf.value = requestAnimationFrame(timeElapse)
    } else {
      fps.value = Math.floor(1000 / (timestamp - start))
      console.log('fps', fps.value)
      onStart()
    }
  }
  fpsRaf.value = requestAnimationFrame(timeElapse)
}
const imageWidth = ref() // 图片宽度
const imageHeight = ref() // 图片高度
function getImageSize () {
  imageWidth.value = carousel.value.offsetWidth
  imageHeight.value = carousel.value.offsetHeight
}
function onStart () {
  if (len.value > 1) { // 超过一条时滑动
    toLeft.value = true // 重置左滑标志
    transition.value = false
    onAutoSlide() // 自动滑动轮播
    console.log('imageSlider start')
  }
}
function onStop () {
  cancelRaf(slideTimer.value)
  if (toLeft.value) { // 左滑箭头移出时
    onStopLeft()
  } else {
    onStopRight()
  }
  console.log('imageSlider stop')
}
function onStopLeft () { // 停止往左滑动
  cancelRaf(slideTimer.value)
  cancelAnimationFrame(moveRaf.value)
  transition.value = true
  left.value = Math.ceil(left.value / imageWidth.value) * imageWidth.value // ceil:向上取整,floor:向下取整
}
function onStopRight () { // 停止往右滑动
  cancelAnimationFrame(moveRaf.value)
  transition.value = true
  left.value = Math.floor(left.value / imageWidth.value) * imageWidth.value // ceil:向上取整,floor:向下取整
}
function onAutoSlide () {
  slideTimer.value = rafTimeout(() => {
    const target = left.value % (len.value * imageWidth.value) + imageWidth.value
    activeSwitcher.value = activeSwitcher.value % len.value + 1
    autoMoveLeft(target)
  }, props.interval)
}
function goLeft (target: number) { // 点击右箭头,往左滑动
  if (toLeft.value) {
    onStopLeft()
  } else {
    onStopRight()
    toLeft.value = true // 向左滑动
  }
  transition.value = false
  moveLeft(target)
}
function goRight (target: number) { // 点击左箭头,往右滑动
  if (toLeft.value) {
    onStopLeft()
    toLeft.value = false // 非向左滑动
  } else {
    onStopRight()
  }
  transition.value = false
  moveRight(target)
}
function onLeftArrow (target: number) {
  activeSwitcher.value = (activeSwitcher.value - 1 > 0) ? activeSwitcher.value - 1 : len.value
  goRight(target)
}
function onRightArrow (target: number) {
  activeSwitcher.value = activeSwitcher.value % len.value + 1
  goLeft(target)
}
function autoMoveLeftEffect () {
  if (left.value >= targetMove.value) {
    onAutoSlide() // 自动间隔切换下一张
  } else {
    var move = Math.ceil((targetMove.value - left.value) / step.value) // 越来越慢的滑动过程
    left.value += move
    moveRaf.value = requestAnimationFrame(autoMoveLeftEffect)
  }
}
function autoMoveLeft (target: number) { // 自动切换,向左滑动效果
  if (left.value === len.value * imageWidth.value) { // 最后一张时,重置left
    left.value = 0
  }
  targetMove.value = target
  moveRaf.value = requestAnimationFrame(autoMoveLeftEffect)
}
function moveLeftEffect () {
  if (left.value >= targetMove.value) {
    if (switched.value) { // 跳转切换,完成后自动滑动
      switched.value = false
      if (!props.disableOnInteraction && !props.pauseOnMouseEnter) {
        onStart()
      }
    }
  } else {
    var move = Math.ceil((targetMove.value - left.value) / step.value) // 越来越慢的滑动过程
    left.value += move
    moveRaf.value = requestAnimationFrame(moveLeftEffect)
  }
}
function moveLeft (target: number) { // 箭头切换或跳转切换,向左滑动效果
  if (left.value === len.value * imageWidth.value) { // 最后一张时,重置left
    left.value = 0
  }
  targetMove.value = target
  moveRaf.value = requestAnimationFrame(moveLeftEffect)
}
function moveRightEffect () {
  if (left.value <= targetMove.value) {
    if (switched.value) { // 跳转切换,完成后自动滑动
      switched.value = false
      if (!props.disableOnInteraction && !props.pauseOnMouseEnter) {
        onStart()
      }
    }
  } else {
    var move = Math.floor((targetMove.value - left.value) / step.value) // 越来越慢的滑动过程
    left.value += move
    moveRaf.value = requestAnimationFrame(moveRightEffect)
  }
}
function moveRight (target: number) { // 箭头切换或跳转切换,向右滑动效果
  if (left.value === 0) { // 第一张时,重置left
    left.value = len.value * imageWidth.value
  }
  targetMove.value = target
  moveRaf.value = requestAnimationFrame(moveRightEffect)
}
function onSwitch (n: number) { // 分页切换图片
  if (activeSwitcher.value !== n) {
    switched.value = true // 跳转切换标志
    const target = (n - 1) * imageWidth.value
    if (n < activeSwitcher.value) { // 往右滑动
      activeSwitcher.value = n
      goRight(target)
    }
    if (n > activeSwitcher.value) { // 往左滑动
      activeSwitcher.value = n
      goLeft(target)
    }
  }
}
</script>
<template>
  <div
    class="m-slider"
    ref="carousel"
    :style="`width: ${carouselWidth}; height: ${carouselHeight};`"
    @mouseenter="pauseOnMouseEnter ? onStop() : (e: Event) => e.preventDefault()"
    @mouseleave="pauseOnMouseEnter ? onStart() : (e: Event) => e.preventDefault()">
    <div :class="{'transition': transition}" :style="`width: ${totalWidth}px; height: 100%; will-change: transform; transform: translateX(${-left}px);`">
      <div
        v-for="(image, index) in imageData"
        :key="index"
        class="m-image">
        <a :href="image.link ? image.link:'javascript:;'" :target="image.link ? '_blank':'_self'" class="m-link">
          <img :src="image.imgUrl" :key="image.imgUrl" :alt="image.title" class="u-img" :style="`width: ${imageWidth}px; height: ${imageHeight}px;`"/>
        </a>
      </div>
      <div class="m-image" v-if="len">
        <a :href="imageData[0].link ? imageData[0].link:'javascript:;'" :target="imageData[0].link ? '_blank':'_self'" class="m-link">
          <img :src="imageData[0].imgUrl" :key="imageData[0].imgUrl" :alt="imageData[0].title" class="u-img"  :style="`width: ${imageWidth}px; height: ${imageHeight}px;`"/>
        </a>
      </div>
    </div>
    <template v-if="navigation">
      <svg class="arrow-left" @click="onLeftArrow((activeSwitcher + len - 2)%len*imageWidth)" viewBox="64 64 896 896" data-icon="left-circle" aria-hidden="true" focusable="false"><path d="M603.3 327.5l-246 178a7.95 7.95 0 0 0 0 12.9l246 178c5.3 3.8 12.7 0 12.7-6.5V643c0-10.2-4.9-19.9-13.2-25.9L457.4 512l145.4-105.2c8.3-6 13.2-15.6 13.2-25.9V334c0-6.5-7.4-10.3-12.7-6.5z"></path><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"></path></svg>
      <svg class="arrow-right" @click="onRightArrow(activeSwitcher*imageWidth)" viewBox="64 64 896 896" data-icon="right-circle" aria-hidden="true" focusable="false"><path d="M666.7 505.5l-246-178A8 8 0 0 0 408 334v46.9c0 10.2 4.9 19.9 13.2 25.9L566.6 512 421.2 617.2c-8.3 6-13.2 15.6-13.2 25.9V690c0 6.5 7.4 10.3 12.7 6.5l246-178c4.4-3.2 4.4-9.8 0-13z"></path><path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"></path></svg>
    </template>
    <div class="m-switch" v-if="pagination">
      <div
        @click="onSwitch(n)"
        :class="['u-rect', {'active': activeSwitcher === n }]"
        v-for="n in len"
        :key="n">
      </div>
    </div>
  </div>
</template>
<style lang="less" scoped>
.m-slider {
  display: inline-block;
  margin: 0 auto;
  position: relative;
  overflow: hidden;
  .transition {
    transition: transform 0.3s ease-out;
  }
  .m-image {
    display: inline-block;
    .m-link {
      display: block;
      height: 100%;
      .u-img {
        object-fit: cover;
        vertical-align: bottom; // 消除img标签底部的5px
        cursor: pointer;
      }
    }
  }
  &:hover {
    .arrow-left {
      opacity: 1;
      pointer-events: auto;
    }
    .arrow-right {
      opacity: 1;
      pointer-events: auto;
    }
  }
  .arrow-left {
    width: 28px;
    height: 28px;
    position: absolute;
    left: 16px;
    top: 50%;
    transform: translateY(-50%);
    fill: rgba(255, 255, 255, .6);
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: all .3s;
    &:hover {
      fill: rgba(255, 255, 255);
    }
  }
  .arrow-right {
    width: 28px;
    height: 28px;
    position: absolute;
    right: 16px;
    top: 50%;
    transform: translateY(-50%);
    fill: rgba(255, 255, 255, .6);
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: all .3s;
    &:hover {
      fill: rgba(255, 255, 255);
    }
  }
  .m-switch {
    position: absolute;
    width: 100%;
    text-align: center;
    bottom: 8px;
    .u-rect {
      display: inline-block;
      vertical-align: middle;
      width: 36px;
      height: 4px;
      background: #E3E3E3;
      border-radius: 1px;
      margin: 0 4px;
      cursor: pointer;
      transition: background-color 0.3s;
    }
    .active {
      background-color: @themeColor;
    }
  }
}
</style>

②在要使用的页面引入:

<script setup lang="ts">
import { Carousel } from './Carousel.vue'
import { ref, onMounted } from 'vue'
import { getImageUrl } from '@/utils/util'

const imageData = ref<any[]>([])
function loadImages () {
  for (let i = 1; i <= 8; i++) {
    imageData.value.push({
      title: `image-${i}`,
      link: '',
      imgUrl: getImageUrl(i)
    })
  }
  console.log(imageData.value)
}
onMounted(() => {
  loadImages()
})
</script>
<template>
  <div>
    <h2 class="mb10">Carousel 走马灯基本使用</h2>
    <Carousel
      :imageData="imageData"
      :width="800"
      :height="450"
      :interval="1500"
      :pauseOnMouseEnter="true"
      :disableOnInteraction="false" />
  </div>
</template>
<style lang="less" scoped>
</style>

猜你喜欢

转载自blog.csdn.net/Dandrose/article/details/129947958
今日推荐