可自定义设置以下属性:
走马灯图片数组(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
效果如下图:(Vue2走马灯的扩展版)
①创建走马灯组件Carousel.vue:
<template>
<div
class="m-slider"
ref="carousel"
:style="`width: ${carouselWidth}; height: ${carouselHeight};`"
@mouseenter="pauseOnMouseEnter ? onStop() : e => e.preventDefault()"
@mouseleave="pauseOnMouseEnter ? onStart() : e => 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 v-lazy="getDefault(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 v-lazy="getDefault(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>
<script>
import Vue from 'vue'
import VueLazyLoad from 'vue-lazyload'
Vue.use(VueLazyLoad) // 图片懒加载插件
export default {
name: 'Carousel',
props: {
imageData: { // 走马灯图片数组
type: Array,
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
}
},
data () {
return {
toLeft: true, // 左滑标志,默认左滑
left: 0, // 滑动偏移值
transition: false, // 暂停时为完成滑动的过渡标志
slideTimer: null, // 轮播切换定时器
moveRaf: null, // 滑动效果回调标识
targetMove: null, // 目标移动位置
switched: false, // 是否在进行跳转切换,用于区别箭头或自动切换(false)和跳转切换(true)
activeSwitcher: 1, // 当前展示图片标识
fpsRaf: null, // fps回调标识
fps: 60,
imageWidth: null, // 图片宽度
imageHeight: null // 图片高度
}
},
computed: {
carouselWidth () { // 走马灯区域宽度
if (typeof this.width === 'number') {
return this.width + 'px'
} else {
return this.width
}
},
carouselHeight () { // 走马灯区域高度
if (typeof this.height === 'number') {
return this.height + 'px'
} else {
return this.height
}
},
totalWidth () { // 容器宽度:(图片数组长度+1) * 图片宽度
return (this.imageData.length + 1) * this.imageWidth
},
len () { // 图片数量
return this.imageData.length
},
step () { // 移动参数(120fps: 24, 60fps: 12)
if (this.fps === 60) {
return 12
} else {
return 12 * (this.fps / 60)
}
}
},
mounted () {
this.getFPS() // 获取浏览器的刷新率
this.getImageSize() // 获取每张图片大小
},
methods: {
getDefault (src) { // 获取懒加载默认图
return {
src: src,
error: require('../assets/images/default.png'),
loading: require('../assets/images/default.png')
}
},
getFPS () { // 获取屏幕刷新率
const requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame
var start = null
const that = this
function timeElapse (timestamp) {
/*
timestamp参数:与performance.now()的返回值相同,它表示requestAnimationFrame() 开始去执行回调函数的时刻
*/
// console.log('timestamp:', timestamp)
if (!start) {
if (that.fpsRaf > 10) {
start = timestamp
}
that.fpsRaf = requestAnimationFrame(timeElapse)
} else {
that.fps = Math.floor(1000 / (timestamp - start))
console.log('fps', that.fps)
that.onStart()
}
}
this.fpsRaf = requestAnimationFrame(timeElapse)
},
getImageSize () {
this.imageWidth = this.$refs.carousel.offsetWidth
this.imageHeight = this.$refs.carousel.offsetHeight
},
onStart () {
if (this.len > 1) { // 超过一条时滑动
this.toLeft = true // 重置左滑标志
this.transition = false
this.onAutoSlide() // 自动滑动轮播
console.log('imageSlider start')
}
},
onStop () {
clearTimeout(this.slideTimer)
if (this.toLeft) { // 左滑箭头移出时
this.onStopLeft()
} else {
this.onStopRight()
}
console.log('imageSlider stop')
},
onStopLeft () { // 停止往左滑动
clearTimeout(this.slideTimer)
cancelAnimationFrame(this.moveRaf)
this.transition = true
this.left = Math.ceil(this.left / this.imageWidth) * this.imageWidth // ceil:向上取整,floor:向下取整
},
onStopRight () { // 停止往右滑动
cancelAnimationFrame(this.moveRaf)
this.transition = true
this.left = Math.floor(this.left / this.imageWidth) * this.imageWidth // ceil:向上取整,floor:向下取整
},
onAutoSlide () {
this.slideTimer = setTimeout(() => {
// 或者使用 this.activeSwitcher % (this.len + 1) * this.imageWidth
const target = this.left % (this.len * this.imageWidth) + this.imageWidth
this.activeSwitcher = this.activeSwitcher % this.len + 1
this.autoMoveLeft(target)
}, this.interval)
},
goLeft (target) { // 点击右箭头,往左滑动
if (this.toLeft) {
this.onStopLeft()
} else {
this.onStopRight()
this.toLeft = true // 向左滑动
}
this.transition = false
this.moveLeft(target)
},
goRight (target) { // 点击左箭头,往右滑动
if (this.toLeft) {
this.onStopLeft()
this.toLeft = false // 非向左滑动
} else {
this.onStopRight()
}
this.transition = false
this.moveRight(target)
},
onLeftArrow (target) {
this.activeSwitcher = (this.activeSwitcher - 1 > 0) ? this.activeSwitcher - 1 : this.len
this.goRight(target)
},
onRightArrow (target) {
this.activeSwitcher = this.activeSwitcher % this.len + 1
this.goLeft(target)
},
autoMoveLeftEffect () {
if (this.left >= this.targetMove) {
this.onAutoSlide() // 自动间隔切换下一张
} else {
var move = Math.ceil((this.targetMove - this.left) / this.step) // 越来越慢的滑动过程
this.left += move
this.moveRaf = requestAnimationFrame(this.autoMoveLeftEffect)
}
},
autoMoveLeft (target) { // 自动切换,向左滑动效果
if (this.left === this.len * this.imageWidth) { // 最后一张时,重置left
this.left = 0
}
this.targetMove = target
this.moveRaf = requestAnimationFrame(this.autoMoveLeftEffect)
},
moveLeftEffect () {
if (this.left >= this.targetMove) {
if (this.switched) { // 跳转切换,完成后自动滑动
this.switched = false
if (!this.disableOnInteraction && !this.pauseOnMouseEnter) {
this.onStart()
}
}
} else {
var move = Math.ceil((this.targetMove - this.left) / this.step) // 越来越慢的滑动过程
this.left += move
this.moveRaf = requestAnimationFrame(this.moveLeftEffect)
}
},
moveLeft (target) { // 箭头切换或跳转切换,向左滑动效果
if (this.left === this.len * this.imageWidth) { // 最后一张时,重置left
this.left = 0
}
this.targetMove = target
this.moveRaf = requestAnimationFrame(this.moveLeftEffect)
},
moveRightEffect () {
if (this.left <= this.targetMove) {
if (this.switched) { // 跳转切换,完成后自动滑动
this.switched = false
if (!this.disableOnInteraction && !this.pauseOnMouseEnter) {
this.onStart()
}
}
} else {
var move = Math.floor((this.targetMove - this.left) / this.step) // 越来越慢的滑动过程
this.left += move
this.moveRaf = requestAnimationFrame(this.moveRightEffect)
}
},
moveRight (target) { // 箭头切换或跳转切换,向右滑动效果
if (this.left === 0) { // 第一张时,重置left
this.left = this.len * this.imageWidth
}
this.targetMove = target
this.moveRaf = requestAnimationFrame(this.moveRightEffect)
},
onSwitch (n) { // 分页切换图片
if (this.activeSwitcher !== n) {
this.switched = true // 跳转切换标志
const target = (n - 1) * this.imageWidth
if (n < this.activeSwitcher) { // 往右滑动
this.activeSwitcher = n
this.goRight(target)
}
if (n > this.activeSwitcher) { // 往左滑动
this.activeSwitcher = n
this.goLeft(target)
}
}
},
beforeDestroy () {
clearTimeout(this.slideTimer)
this.slideTimer = null
}
}
}
</script>
<style lang="less" scoped>
@themeColor: #1890FF;
.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>
②在要使用的页面引入:
<Carousel
:imageData="imageData"
:width="800"
:height="450"
:interval="1500"
:pauseOnMouseEnter="true"
:disableOnInteraction="false" />
import Carousel from '@/components/Carousel'
components: {
Carousel
}
data () {
return {
imageData: [
{
title: 'image-1,image-1,image-1,image-1,image-1,image-1,image-1,image-1,image-1',
imgUrl: 'image src...'
},
{
title: 'image-2,image-2,image-2,image-2,image-2,image-2,image-2,image-2,image-2,image-2,image-2,image-2',
imgUrl: 'image src...'
},
{
title: 'image-3,image-3,image-3,image-3,image-3,image-3,image-3,image-3,image-3',
imgUrl: 'image src...'
}
]
}
}