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>
|