365 lines
9.6 KiB
Vue
365 lines
9.6 KiB
Vue
|
<template>
|
|||
|
<view class="tn-swiper__wrap-class tn-swiper__wrap" :style="{borderRadius: `${radius}rpx`}">
|
|||
|
<!-- 轮播图 -->
|
|||
|
<swiper
|
|||
|
class="tn-swiper"
|
|||
|
:class="[backgroundColorClass]"
|
|||
|
:style="[swiperStyle]"
|
|||
|
:current="current"
|
|||
|
:interval="interval"
|
|||
|
:circular="circular"
|
|||
|
:autoplay="autoplay"
|
|||
|
:duration="duration"
|
|||
|
:previous-margin="effect3d ? effect3dPreviousSpacing + 'rpx' : '0'"
|
|||
|
:next-margin="effect3d ? effect3dPreviousSpacing + 'rpx' : '0'"
|
|||
|
@change="change"
|
|||
|
>
|
|||
|
<swiper-item
|
|||
|
v-for="(item, index) in list"
|
|||
|
:key="index"
|
|||
|
class="tn-swiper__item"
|
|||
|
>
|
|||
|
<view
|
|||
|
class="tn-swiper__item__image__wrap"
|
|||
|
:class="[swiperIndex !== index ? 'tn-swiper__item__image--scale' : '']"
|
|||
|
:style="{
|
|||
|
borderRadius: `${radius}rpx`,
|
|||
|
transform: effect3d && swiperIndex !== index ? 'scaleY(0.9)' : 'scaleY(1)',
|
|||
|
margin: effect3d && swiperIndex !== index ? '0 20rpx' : 0
|
|||
|
}"
|
|||
|
@click="click(index)"
|
|||
|
>
|
|||
|
<image class="tn-swiper__item__image" :src="item[name] || item" :mode="imageMode"></image>
|
|||
|
<view
|
|||
|
v-if="title && item[titleName]"
|
|||
|
class="tn-swiper__item__title tn-text-ellipsis"
|
|||
|
:style="[titleStyle]">
|
|||
|
{{ item[titleName] }}
|
|||
|
</view>
|
|||
|
</view>
|
|||
|
</swiper-item>
|
|||
|
</swiper>
|
|||
|
|
|||
|
<!-- 指示点 -->
|
|||
|
<view class="tn-swiper__indicator" :style="[indicatorStyle]">
|
|||
|
<block v-if="mode === 'rect'">
|
|||
|
<view
|
|||
|
v-for="(item, index) in list"
|
|||
|
:key="index"
|
|||
|
class="tn-swiper__indicator__rect"
|
|||
|
:class="{'tn-swiper__indicator__rect--active': swiperIndex === index}"
|
|||
|
></view>
|
|||
|
</block>
|
|||
|
<block v-if="mode === 'dot'">
|
|||
|
<view
|
|||
|
v-for="(item, index) in list"
|
|||
|
:key="index"
|
|||
|
class="tn-swiper__indicator__dot"
|
|||
|
:class="{'tn-swiper__indicator__dot--active': swiperIndex === index}"
|
|||
|
></view>
|
|||
|
</block>
|
|||
|
<block v-if="mode === 'round'">
|
|||
|
<view
|
|||
|
v-for="(item, index) in list"
|
|||
|
:key="index"
|
|||
|
class="tn-swiper__indicator__round"
|
|||
|
:class="{'tn-swiper__indicator__round--active': swiperIndex === index}"
|
|||
|
></view>
|
|||
|
</block>
|
|||
|
<block v-if="mode === 'number'">
|
|||
|
<view class="tn-swiper__indicator__number">{{ swiperIndex + 1 }}/{{ list.length }}</view>
|
|||
|
</block>
|
|||
|
</view>
|
|||
|
</view>
|
|||
|
</template>
|
|||
|
|
|||
|
<script>
|
|||
|
export default {
|
|||
|
name: 'tn-swiper',
|
|||
|
props: {
|
|||
|
// 轮播图列表数据
|
|||
|
// [{image: xxx.jpg, title: 'xxxx'}]
|
|||
|
list: {
|
|||
|
type: Array,
|
|||
|
default() {
|
|||
|
return []
|
|||
|
}
|
|||
|
},
|
|||
|
// 初始化时,默认显示第几项
|
|||
|
current: {
|
|||
|
type: Number,
|
|||
|
default: 0
|
|||
|
},
|
|||
|
// 高度
|
|||
|
height: {
|
|||
|
type: Number,
|
|||
|
default: 250
|
|||
|
},
|
|||
|
// 背景颜色
|
|||
|
backgroundColor: {
|
|||
|
type: String,
|
|||
|
default: 'transparent'
|
|||
|
},
|
|||
|
// 图片的属性名
|
|||
|
name: {
|
|||
|
type: String,
|
|||
|
default: 'image'
|
|||
|
},
|
|||
|
// 是否显示标题
|
|||
|
title: {
|
|||
|
type: Boolean,
|
|||
|
default: false
|
|||
|
},
|
|||
|
// 标题的属性名
|
|||
|
titleName: {
|
|||
|
type: String,
|
|||
|
default: 'title'
|
|||
|
},
|
|||
|
// 用户自定义标题样式
|
|||
|
titleStyle: {
|
|||
|
type: Object,
|
|||
|
default() {
|
|||
|
return {}
|
|||
|
}
|
|||
|
},
|
|||
|
// 圆角的值
|
|||
|
radius: {
|
|||
|
type: Number,
|
|||
|
default: 8
|
|||
|
},
|
|||
|
// 指示器模式
|
|||
|
// rect -> 方形 round -> 圆角方形 dot -> 点 number -> 轮播图下标
|
|||
|
mode: {
|
|||
|
type: String,
|
|||
|
default: 'round'
|
|||
|
},
|
|||
|
// 指示器位置
|
|||
|
// topLeft \ topCenter \ topRight \ bottomLeft \ bottomCenter \ bottomRight
|
|||
|
indicatorPosition: {
|
|||
|
type: String,
|
|||
|
default: 'bottomCenter'
|
|||
|
},
|
|||
|
// 开启3D缩放效果
|
|||
|
effect3d: {
|
|||
|
type: Boolean,
|
|||
|
default: false
|
|||
|
},
|
|||
|
// 在3D缩放模式下,item之间的间隔
|
|||
|
effect3dPreviousSpacing: {
|
|||
|
type: Number,
|
|||
|
default: 50
|
|||
|
},
|
|||
|
// 自定播放
|
|||
|
autoplay: {
|
|||
|
type: Boolean,
|
|||
|
default: true
|
|||
|
},
|
|||
|
// 图片之间播放间隔多久
|
|||
|
interval: {
|
|||
|
type: Number,
|
|||
|
default: 3000
|
|||
|
},
|
|||
|
// 轮播间隔时间
|
|||
|
duration: {
|
|||
|
type: Number,
|
|||
|
default: 500
|
|||
|
},
|
|||
|
// 是否衔接滑动
|
|||
|
circular: {
|
|||
|
type: Boolean,
|
|||
|
default: true
|
|||
|
},
|
|||
|
// 图片裁剪模式
|
|||
|
imageMode: {
|
|||
|
type: String,
|
|||
|
default: 'aspectFill'
|
|||
|
}
|
|||
|
},
|
|||
|
computed: {
|
|||
|
backgroundColorStyle() {
|
|||
|
return this.$tn.color.getBackgroundColorStyle(this.backgroundColor)
|
|||
|
},
|
|||
|
backgroundColorClass() {
|
|||
|
return this.$tn.color.getBackgroundColorInternalClass(this.backgroundColor)
|
|||
|
},
|
|||
|
swiperStyle() {
|
|||
|
let style = {}
|
|||
|
if (this.backgroundColorStyle) {
|
|||
|
style.backgroundColor = this.backgroundColorStyle
|
|||
|
}
|
|||
|
if (this.height) {
|
|||
|
style.height = this.height + 'rpx'
|
|||
|
}
|
|||
|
return style
|
|||
|
},
|
|||
|
indicatorStyle() {
|
|||
|
let style = {}
|
|||
|
if (this.indicatorPosition === 'topLeft' || this.indicatorPosition === 'bottomLeft') style.justifyContent = 'flex-start'
|
|||
|
if (this.indicatorPosition === 'topCenter' || this.indicatorPosition === 'bottomCenter') style.justifyContent = 'center'
|
|||
|
if (this.indicatorPosition === 'topRight' || this.indicatorPosition === 'bottomRight') style.justifyContent = 'flex-end'
|
|||
|
if (['topLeft','topCenter','topRight'].indexOf(this.indicatorPosition) >= 0) {
|
|||
|
style.top = '12rpx'
|
|||
|
style.bottom = 'auto'
|
|||
|
} else {
|
|||
|
style.top = 'auto'
|
|||
|
style.bottom = '12rpx'
|
|||
|
}
|
|||
|
style.padding = `0 ${this.effect3d ? '74rpx' : '24rpx'}`
|
|||
|
|
|||
|
return style
|
|||
|
},
|
|||
|
swiperTitleStyle() {
|
|||
|
let style = {}
|
|||
|
if (this.mode === 'none' || this.mode === '') style.paddingBottom = '12rpx'
|
|||
|
if (['bottomLeft','bottomCenter','bottomRight'].indexOf(this.indicatorPosition) >= 0 && this.mode === 'number') {
|
|||
|
style.paddingBottom = '60rpx'
|
|||
|
} else if (['bottomLeft','bottomCenter','bottomRight'].indexOf(this.indicatorPosition) >= 0 && this.mode !== 'number') {
|
|||
|
style.paddingBottom = '40rpx'
|
|||
|
} else {
|
|||
|
style.paddingBottom = '12rpx'
|
|||
|
}
|
|||
|
|
|||
|
style = Object.assign(style, this.titleStyle)
|
|||
|
return style
|
|||
|
}
|
|||
|
},
|
|||
|
data() {
|
|||
|
return {
|
|||
|
// 当前显示的item的index
|
|||
|
swiperIndex: this.current
|
|||
|
}
|
|||
|
},
|
|||
|
watch: {
|
|||
|
list(newVal, oldVal) {
|
|||
|
// 如果修改了list的数据,重置current的值
|
|||
|
if (newVal.length !== oldVal.length) this.swiperIndex = 0
|
|||
|
},
|
|||
|
current(value) {
|
|||
|
// 监听外部current的变化,实时修改内部依赖于此测swiperIndex值,如果更新了current,而不是更新swiperIndex,就会错乱,因为指示器是依赖于swiperIndex的
|
|||
|
this.swiperIndex = value
|
|||
|
}
|
|||
|
},
|
|||
|
methods: {
|
|||
|
click(index) {
|
|||
|
this.$emit('click', index)
|
|||
|
},
|
|||
|
// 图片自动切换时触发
|
|||
|
change(event) {
|
|||
|
const current = event.detail.current
|
|||
|
this.swiperIndex = current
|
|||
|
this.$emit('change', current)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
</script>
|
|||
|
|
|||
|
<style lang="scss" scoped>
|
|||
|
|
|||
|
.tn-swiper {
|
|||
|
|
|||
|
&__wrap {
|
|||
|
position: relative;
|
|||
|
overflow: hidden;
|
|||
|
transform: translateY(0);
|
|||
|
}
|
|||
|
|
|||
|
&__item {
|
|||
|
display: flex;
|
|||
|
flex-direction: row;
|
|||
|
align-items: center;
|
|||
|
overflow: hidden;
|
|||
|
|
|||
|
&__image {
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
will-change: transform;
|
|||
|
display: block;
|
|||
|
/* #ifdef H5 */
|
|||
|
pointer-events: none;
|
|||
|
/* #endif */
|
|||
|
|
|||
|
&__wrap {
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
flex: 1;
|
|||
|
transition: all 0.5s;
|
|||
|
overflow: hidden;
|
|||
|
box-sizing: content-box;
|
|||
|
position: relative;
|
|||
|
}
|
|||
|
|
|||
|
&--scale {
|
|||
|
transform-origin: center center;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
&__title {
|
|||
|
width: 100%;
|
|||
|
position: absolute;
|
|||
|
background-color: rgba(0, 0, 0, 0.3);
|
|||
|
bottom: 0;
|
|||
|
left: 0;
|
|||
|
font-size: 28rpx;
|
|||
|
padding: 12rpx 24rpx;
|
|||
|
color: rgba(255, 255, 255, 0.8);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
&__indicator {
|
|||
|
padding: 0 24rpx;
|
|||
|
position: absolute;
|
|||
|
display: flex;
|
|||
|
flex-direction: row;
|
|||
|
width: 100%;
|
|||
|
z-index: 1;
|
|||
|
|
|||
|
&__rect {
|
|||
|
width: 26rpx;
|
|||
|
height: 8rpx;
|
|||
|
background-color: rgba(0, 0, 0, 0.3);
|
|||
|
transition: all 0.5s;
|
|||
|
|
|||
|
&--active {
|
|||
|
background-color: rgba(255, 255, 255, 0.8);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
&__dot {
|
|||
|
width: 14rpx;
|
|||
|
height: 14rpx;
|
|||
|
margin: 0 6rpx;
|
|||
|
border-radius: 20rpx;
|
|||
|
background-color: rgba(0, 0, 0, 0.3);
|
|||
|
transition: all 0.5s;
|
|||
|
|
|||
|
&--active {
|
|||
|
background-color: rgba(255, 255, 255, 0.8);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
&__round {
|
|||
|
width: 14rpx;
|
|||
|
height: 14rpx;
|
|||
|
margin: 0 6rpx;
|
|||
|
border-radius: 20rpx;
|
|||
|
background-color: rgba(0, 0, 0, 0.3);
|
|||
|
transition: all 0.5s;
|
|||
|
|
|||
|
&--active {
|
|||
|
width: 34rpx;
|
|||
|
background-color: rgba(255, 255, 255, 0.8);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
&__number {
|
|||
|
padding: 6rpx 16rpx;
|
|||
|
line-height: 1;
|
|||
|
background-color: rgba(0, 0, 0, 0.3);
|
|||
|
color: rgba(255, 255, 255, 0.8);
|
|||
|
border-radius: 100rpx;
|
|||
|
font-size: 26rpx;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
</style>
|