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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/7952.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

3-ELK+Kafka+Filebeat 海量级日志收集 TB PB级别

ELKKafkaFilebeat 终极版 4、Kafka&#xff1a; 数据缓冲队列(消息队列)。同时提高了可扩展性。具有峰值处理能力&#xff0c;使用消息队列能够使关键组件顶住突发的访问压力&#xff0c;而不会因为突发的超负荷的请求而完全崩溃。是一个分布式、支持分区的&#xff08;partit…

模板匹配及应用

模板匹配及应用 1)模板匹配 模板匹配是一项在一幅图像中寻找与另一幅模板图像最匹配(相似)部分的技术。模板匹配不是基于直方图的, 而是通过在 输入图像上滑动图像块(模板)同时比对相似度, 来对模板和输入图像进行匹配的一种方法。 应用: ①目标查找定位 ②运动物体跟踪 ③…

SpringMvc中拦截器

文章目录 1.拦截器概述 2.拦截器类中的方法 1.先写一个前端页面 2.写后台代码 3.编写success.jsp页面 4.编写拦截器类&#xff0c;实现HandlerInterceptor接口 5.编写error.jsp页面 6.配置拦截器类 3.配置多个拦截器 3.1再写一个拦截器类 3.2 配置拦截器类 1.拦截器概述 Spring…

中国版ChatGPT即将来袭-国内版ChatGPT入口

必应chatGPT入口 目前并不存在“必应ChatGPT”这个概念。必应&#xff08;Bing&#xff09;是Microsoft公司推出的一款搜索引擎&#xff0c;而ChatGPT是OpenAI开发的自然语言处理技术&#xff0c;它们是两个不同的产品品牌。 不过&#xff0c;Microsoft也在自然语言处理领域里…

Leetcode字符串的排列

其实可以看成使用其中一个字符加上其他字符的连接&#xff0c;最后用set去重 class Solution:lru_cache(None)def permutation(self, s: str) -> List[str]:if not s: return []res set()for i in range(len(s)):for j in self.permutation(s[:i]s[i1:]):res.add(s[i]j)re…

Unity Animation -- 改进动画效果

使用曲线&#xff08;Curves&#xff09;改善动画 在上一篇笔记中&#xff08;Unity Animation -- Overview_亦枫Leonlew的博客-CSDN博客&#xff09;&#xff0c;我们制作了简单的小球弹跳的动画&#xff0c;但这个动画看起来很不自然&#xff0c;小球的弹跳看起来就像是不受…

Leetcode.559 N 叉树的最大深度

题目链接 Leetcode.559 N 叉树的最大深度 easy 题目描述 给定一个 N 叉树&#xff0c;找到其最大深度。 最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。 N 叉树输入按层序遍历序列化表示&#xff0c;每组子节点由空值分隔&#xff08;请参见示例&#xff09;。…

Vector - CAPL - CRC算法介绍(续)

不常用CRC算法 目录 Crc_CalculateCRC8H2F 代码示例 Crc_CalculateCRC32P4 代码示例 Crc_CalculateCRC64 代码示例 Crc_CalculateCRC8H2F 功能&#xff1a;根据数据计算CRC8H2F的相应校验和。 data&#xff1a;待计算CRC8H2F校验和的数据 dataSize&#xff1a;待计算CRC…

Ansys Zemax | 如何使用 Zernike 凹陷表面对全反射系统进行建模

本文介绍如何使用Zernike标准下垂表面对全反射系统进行建模。全反射系统是一种特殊情况&#xff0c;其中Zernike凹陷表面可用于模拟给定场点的所有波长下的性能。使用Zernike凹陷表面代替Zernike相位&#xff0c;因为衍射功率与波长变化时的反射功率不同。一个相位波是任何波长…

linux 共享内存 shmget

专栏内容&#xff1a;linux下并发编程个人主页&#xff1a;我的主页座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物&#xff0e;目录 前言 概述 原理机制 系统命令 接口说明 代码演示 结尾 前言 本专栏主要分享linu…

Day924.自动化测试 -系统重构实战

自动化测试 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于自动化测试的内容。 自动化测试是一个很容易产生“争议”的话题&#xff0c;也经常会有一些很有意思的问题。 自动化测试不是应该由测试同学来编写吗&#xff0c;开发是不是没有必要学吧&#xff1f;之前…

【Linux】进程理解与学习-程序替换

环境&#xff1a;centos7.6&#xff0c;腾讯云服务器Linux文章都放在了专栏&#xff1a;【Linux】欢迎支持订阅 相关文章推荐&#xff1a; 【Linux】冯.诺依曼体系结构与操作系统 【Linux】进程理解与学习Ⅰ-进程概念 【Linux】进程理解与学习Ⅱ-进程状态 【Linux】进程理解与学…

小白的git入门教程(二)

泥闷嚎 今天接着来学习小白入门git的基本过程 今天要学习的是git里面的常见操作 状态查看 git status 所谓的状态查看就是你可以查看到工作区和暂存区的状态&#xff0c;在这里你可以看到你的工作文件的状态&#xff0c;比如是否已经提交等等 首先我们创建一个文本文件&…

FreeRTOS学习(一)

裸机与RTOS对比 裸机&#xff1a;又称为前后台系统&#xff0c;前台系统指的是中断服务函数&#xff0c;后台系统指的大循环&#xff0c;即应用程序。 实时性差&#xff1a;&#xff08;应用程序轮流执行&#xff09;delay&#xff1a;空等待&#xff0c;CPU不执行其它代码结…

【分享】太阳能电池性能测试指标,太阳能电池IV测试软件系统

在现代社会&#xff0c;随着能源需求的不断增加&#xff0c;太阳能电池的应用越来越广泛。太阳能电池是一种利用太阳光能量将化学能转换为电能的半导体材料&#xff0c;它可以将太阳光中的光能直接转换成电能&#xff0c;因此具有广泛的应用前景。本篇文章纳米软件小编为大家分…

JAVAWeb01-BS架构简述、HTML

1. B/S 软件开发架构简述 1.1 Java Web 技术体系图 1.2 B/S 软件开发架构简述 B/S架构 B/S框架&#xff0c;意思是前端(Browser 浏览器)和服务器端(Server)组成的系统的框架结构。B/S架构也可理解为web架构&#xff0c;包含前端、后端、数据库三大组成部分。示意图 &#xf…

学校的地下网站(学校的地下网站1080P高清)

这个问题本身就提得有问题&#xff0c;为什么这么说&#xff0c;这是因为YouTube本身就不是一个视频网站或者说YouTube不是一个传统的视频网站&#xff01;&#xff01;&#xff01; YouTube能够一家独大&#xff0c;可不仅仅是因为有了Google这个亲爹&#xff0c;还有一点&…

ROS实践12 自定义源文件并调用

文章目录运行环境&#xff1a;思路&#xff1a;原理&#xff1a;1.1 头文件编写1.2 编写源文件1.3 编写可执行文件1.4 &#x1f3ef;配置文件&#x1f3ef;1.5 编译运行运行环境&#xff1a; ubuntu20.04 noetic 宏基暗影骑士笔记本 思路&#xff1a; 上一期&#xff1a;类和…

Serverless MQTT 服务即将正式上线、新增 2 个平台安装包

3 月&#xff0c;EMQX 开源版发布了 v5.0.19、v5.0.20 以及 v5.0.21 三个版本&#xff0c;提供 Rocky Linux 9 以及 MacOS 12 Intel 平台安装包。企业版发布了 v4.4.15 以及 v4.4.16 版本&#xff0c;提供了 Apache IoTDB 支持、HStreamDB 最新版本的适配、MongoDB 6.0 支持等多…

Python SMTP发送邮件和线程

文章目录一、Python SMTP发送邮件二、Python3 多线程总结一、Python SMTP发送邮件 SMTP&#xff08;Simple Mail Transfer Protocol&#xff09;即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则&#xff0c;由它来控制信件的中转方式。 python的smtplib提供…
最新文章