123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535 |
- <template>
- <view
- class="tn-c-swiper-class tn-c-swiper"
- >
- <!-- 轮播item容器-->
- <view class="tn-swiper__container" :style="[swiperContainerStyle]" :animation="containerAnimation">
- <slot></slot>
- </view>
-
- <!-- 轮播指示器-->
- <view v-if="indicator" class="tn-swiper__indicator" :class="[`tn-swiper__indicator--${vertical ? 'vertical' : 'horizontal'}`]" :style="[indicatorStyle]">
- <!-- 方形 -->
- <block v-if="indicatorType === 'rect'">
- <view
- v-for="(item, index) in children.length"
- :key="index"
- class="tn-swiper__indicator__rect"
- :class="[
- `tn-swiper__indicator__rect--${vertical ? 'vertical' : 'horizontal'}`,
- currentIndex === index ? `tn-swiper__indicator__rect--active tn-swiper__indicator__rect--active--${vertical ? 'vertical' : 'horizontal'}` : ''
- ]"
- :style="[indicatorPointStyle(index)]"
- ></view>
- </block>
- <!-- 点 -->
- <block v-if="indicatorType === 'dot'">
- <view
- v-for="(item, index) in children.length"
- :key="index"
- class="tn-swiper__indicator__dot"
- :class="[
- `tn-swiper__indicator__dot--${vertical ? 'vertical' : 'horizontal'}`,
- currentIndex === index ? `tn-swiper__indicator__dot--active tn-swiper__indicator__dot--active--${vertical ? 'vertical' : 'horizontal'}` : ''
- ]"
- :style="[indicatorPointStyle(index)]"
- ></view>
- </block>
- <!-- 圆角方形 -->
- <block v-if="indicatorType === 'round'">
- <view
- v-for="(item, index) in children.length"
- :key="index"
- class="tn-swiper__indicator__round"
- :class="[
- `tn-swiper__indicator__round--${vertical ? 'vertical' : 'horizontal'}`,
- currentIndex === index ? `tn-swiper__indicator__round--active tn-swiper__indicator__round--active--${vertical ? 'vertical' : 'horizontal'}` : ''
- ]"
- :style="[indicatorPointStyle(index)]"
- ></view>
- </block>
- <!-- 序号 -->
- <block v-if="indicatorType === 'number' && !vertical">
- <view class="tn-swiper__indicator__number">{{ currentIndex + 1 }}/{{ children.length }}</view>
- </block>
- </view>
- </view>
- </template>
- <script>
- export default {
- name: 'tn-custom-swiper',
- props: {
- // 当前所在的轮播位置
- current: {
- type: Number,
- default: 0
- },
- // 自动切换
- autoplay: {
- type: Boolean,
- default: false
- },
- // 自动切换时间间隔
- interval: {
- type: Number,
- default: 5000
- },
- // 滑动动画时长
- duration: {
- type: Number,
- default: 500
- },
- // 是否采用衔接滑动
- circular: {
- type: Boolean,
- default: false
- },
- // 滑动方向为纵向
- vertical: {
- type: Boolean,
- default: false
- },
- // 显示指示点
- indicator: {
- type: Boolean,
- default: false
- },
- // 指示点类型
- // rect -> 方形 round -> 圆角方形 dot -> 点 number -> 轮播图下标
- indicatorType: {
- type: String,
- default: 'dot'
- },
- // 指示点的位置
- // topLeft \ topCenter \ topRight \ bottomLeft \ bottomCenter \ bottomRight
- indicatorPosition: {
- type: String,
- default: 'bottomCenter'
- },
- // 指示点激活时颜色
- indicatorActiveColor: {
- type: String,
- default: ''
- },
- // 指示点未激活时颜色
- indicatorInactiveColor: {
- type: String,
- default: ''
- },
- // 前一个轮播的自定义样式
- prevSwiperStyle: {
- type: Object,
- default() {
- return {}
- }
- },
- // 当前轮播的自定义样式
- customSwiperStyle: {
- type: Object,
- default() {
- return {}
- }
- },
- // 后一个轮播的自定义样式
- nextSwiperStyle: {
- type: Object,
- default() {
- return {}
- }
- }
- },
- computed: {
- parentData() {
- return [
- this.duration,
- this.currentIndex,
- this.swiperContainerAnimationFinish,
- this.circular,
- this.vertical,
- this.prevSwiperStyle,
- this.customSwiperStyle,
- this.nextSwiperStyle
- ]
- },
- indicatorStyle() {
- let style = {}
- if (this.vertical) {
- 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) {
- if (this.vertical) {
- style.right = '12rpx'
- style.left = 'auto'
- } else {
- style.top = '12rpx'
- style.bottom = 'auto'
- }
- } else {
- if (this.vertical) {
- style.right = 'auto'
- style.left = '12rpx'
- } else {
- style.top = 'auto'
- style.bottom = '12rpx'
- }
- }
- } else {
- 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'
- }
- }
- return style
- },
- indicatorPointStyle() {
- return (index) => {
- let style = {}
- if (index === this.currentIndex && this.indicatorActiveColor !== '') {
- style.backgroundColor = this.indicatorActiveColor
- } else if (this.indicatorInactiveColor !== '') {
- style.backgroundColor = this.indicatorInactiveColor
- }
- return style
- }
- }
- },
- watch: {
- parentData() {
- if (this.children.length) {
- this.children.forEach((item) => {
- // 判断子组件如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
- typeof(item.updateParentData) === 'function' && item.updateParentData()
- })
- }
- },
- current(nVal, oVal) {
- if (this.currentIndex === nVal) return
- this.currentIndex = nVal > this.children.length ? this.children.length - 1 : nVal
- this.swiperContainerAnimationFinish = false
- // 设置动画过渡时间
- this.swiperContainerStyle.transitionDuration = `${this.duration + 90}ms`
- this.updateSwiperContainerItem(oVal)
- }
- },
- data() {
- return {
- // 清除动画定时器
- clearAnimationTimer: null,
- // 前后衔接执行定时器
- convergeTimer: null,
- // 自动轮播Timer
- autoPlayTimer: null,
- // 当前选中的轮播
- currentIndex: this.current,
- // swiperContainer样式
- swiperContainerStyle: {
- transform: 'translate3d(0px, 0px, 0px)',
- transitionDuration: '0ms'
- },
- // swiperContainer动画
- containerAnimation: {},
- // 滑动动画结束标记
- swiperContainerAnimationFinish: false
- }
- },
- created() {
- this.children = []
- },
- mounted() {
- this.$nextTick(() => {
- const index = this.currentIndex > this.children.length ? this.children.length - 1 : this.currentIndex
- this.updateSwiperContainerStyle(index)
- this.startAutoPlay()
- })
- },
- methods: {
- // 更新全部swiperItem的样式
- updateAllSwiperItemStyle() {
- this.children.forEach((item, index) => {
- typeof(item.updateSwiperItemStyle) === 'function' && item.updateSwiperItemStyle(this.children.length)
- })
-
- },
- // 根据swiperIndex更新swiperItemContainer的样式
- updateSwiperContainerStyle(index) {
- if (this.vertical) {
- this.swiperContainerStyle.transform = `translate3d(0px, ${-index * 100}%, 0px)`
- } else {
- this.swiperContainerStyle.transform = `translate3d(${-index * 100}%, 0px, 0px)`
- }
- },
- // 根据传递的值更新swiperItemContainer的位置
- updateSwiperContainerStyleWithValue(value) {
- if (this.vertical) {
- this.swiperContainerStyle.transform = `translate3d(0px, ${(-this.currentIndex * 100) + value * 100}%, 0px)`
- } else {
- this.swiperContainerStyle.transform = `translate3d(${(-this.currentIndex * 100) + value * 100}%, 0px, 0px)`
- }
- },
- // 根据传递的方向更新swiperItemContainer的位置
- updateSwiperContainerStyleWithDirection(direction) {
- const oldCurrent = this.currentIndex
- const childrenLength = this.children.length
- const lastSwiperItemIndex = childrenLength - 1
- this.swiperContainerAnimationFinish = false
-
-
- // 向后切换一个SwiperItem
- if (direction === 'reset') {
- // 设置动画过渡时间
- this.swiperContainerStyle.transitionDuration = `${this.duration}ms`
- this.updateSwiperContainerStyle(this.currentIndex)
- this.clearAnimationTimer = setTimeout(() => {
- this.clearSwiperContainerAnimation()
- }, this.duration)
- } else if (direction === 'reload') {
- this.clearConvergeSwiperItemTimer()
- this.clearSwiperContainerAnimation()
- this.updateSwiperItemStyle(0)
- this.updateSwiperItemStyle(lastSwiperItemIndex)
- } else {
- if (direction === 'left' || direction === 'up') {
- if (oldCurrent === childrenLength - 1 && !this.circular) {
- this.clearSwiperContainerAnimation()
- this.clearConvergeSwiperItemTimer()
- return
- }
- this.currentIndex = oldCurrent + 1 >= childrenLength ? 0 : oldCurrent + 1
- } else if (direction === 'right' || direction === 'down') {
- if (oldCurrent === 0 && !this.circular) {
- this.clearSwiperContainerAnimation()
- this.clearConvergeSwiperItemTimer()
- return
- }
- this.currentIndex = oldCurrent - 1 < 0 ? childrenLength - 1 : oldCurrent - 1
- }
- // 设置动画过渡时间
- this.swiperContainerStyle.transitionDuration = `${this.duration + 90}ms`
- // this.updateSwiperItemContainerRect(this.currentIndex)
- }
-
- // console.log(direction, oldCurrent, this.currentIndex);
- this.updateSwiperContainerItem(oldCurrent)
-
- // 切换轮播时触发事件
- this.$emit('change', {
- current: this.currentIndex
- })
- },
- // 设置自动轮播
- startAutoPlay() {
- if (this.autoplay && !this.autoPlayTimer && this.circular) {
- this.autoPlayTimer = setInterval(() => {
- this.updateSwiperContainerStyleWithDirection('left')
- }, this.interval)
- }
- },
- // 停止自动轮播
- stopAutoPlay() {
- if (this.autoPlayTimer) {
- clearInterval(this.autoPlayTimer)
- this.autoPlayTimer = null
- }
- },
- // 更新swiperContainer和swiperItem相关联信息
- updateSwiperContainerItem(oldCurrent) {
- const childrenLength = this.children.length
- const lastSwiperItemIndex = childrenLength - 1
- // 判断当前是否为头尾,如果是更新对应的头尾SwiperItem样式
- // 更新swiperItemContainer的样式
- if (oldCurrent === 0 && this.currentIndex === lastSwiperItemIndex) {
- // 先移动到最左边然后再去除动画偏移到正常的位置
- // this.swiperContainerStyle.transform = `translate3d(100%, 0px, 0px)`
- this.updateSwiperContainerStyle(-1)
- this.clearSwiperContainerAnimationTimer()
- this.clearAnimationTimer = setTimeout(() => {
- this.convergeSwiperItem()
- }, this.duration)
- } else if (oldCurrent === lastSwiperItemIndex && this.currentIndex === 0) {
- // 先移动到最右边然后再去除动画偏移到正常的位置
- // this.swiperContainerStyle.transform = `translate3d(${-childrenLength * 100}%, 0px, 0px)`
- this.updateSwiperContainerStyle(childrenLength)
- this.clearSwiperContainerAnimationTimer()
- this.clearAnimationTimer = setTimeout(() => {
- this.convergeSwiperItem()
- }, this.duration)
- } else {
- this.updateSwiperContainerStyle(this.currentIndex)
- this.updateSwiperItemStyle(0)
- this.updateSwiperItemStyle(lastSwiperItemIndex)
- this.clearAnimationTimer = setTimeout(() => {
- this.clearSwiperContainerAnimation()
- }, this.duration)
- }
- },
- // 更新对应swiperItem的信息
- updateSwiperItemStyle(index) {
- const childrenLength = this.children.length
- if (index < 0) index = 0
- if (index > childrenLength - 1) index = childrenLength - 1
-
- typeof(this.children[index].updateSwiperItemStyle) === 'function' && this.children[index].updateSwiperItemStyle(childrenLength, this.currentIndex)
- },
- // 更新对应swiperItem的容器信息
- updateSwiperItemContainerRect(index) {
- const childrenLength = this.children.length
- if (index < 0) index = 0
- if (index > childrenLength - 1) index = childrenLength - 1
-
- typeof(this.children[index].getSwiperItemRect) === 'function' && this.children[index].getSwiperItemRect()
- },
- // 执行前后衔接
- convergeSwiperItem() {
- const lastSwiperItemIndex = this.children.length - 1
- this.clearSwiperContainerAnimation()
- this.clearConvergeSwiperItemTimer()
- this.convergeTimer = setTimeout(() => {
- this.updateSwiperItemStyle(0)
- this.updateSwiperItemStyle(lastSwiperItemIndex)
- this.updateSwiperContainerStyle(this.currentIndex)
- this.clearConvergeSwiperItemTimer()
- }, 30)
- },
- // 停止/清除切换动画
- clearSwiperContainerAnimation() {
- this.swiperContainerStyle.transitionDuration = `0ms`
- this.swiperContainerAnimationFinish = true
- this.clearSwiperContainerAnimationTimer()
- },
- // 停止/清除执行前后衔接定时器
- clearConvergeSwiperItemTimer() {
- if (this.convergeTimer) {
- clearTimeout(this.convergeTimer)
- this.convergeTimer = null
- }
- },
- // 停止/清除切换动画定时器
- clearSwiperContainerAnimationTimer() {
- if (this.clearAnimationTimer) {
- clearTimeout(this.clearAnimationTimer)
- this.clearAnimationTimer = null
- }
- }
- }
- }
- </script>
- <style lang="scss" scoped>
- .tn-c-swiper {
- position: relative;
- overflow: hidden;
- width: 100%;
- height: 100%;
-
- .tn-swiper {
- &__container {
- width: 100%;
- height: 100%;
- position: absolute;
- top: 0;
- left: 0;
-
- will-change: transform;
- transition-property: all;
- transition-timing-function: ease-out;
- }
-
- &__indicator {
- position: absolute;
- display: flex;
- z-index: 1;
-
- &--horizontal {
- padding: 0 24rpx;
- flex-direction: row;
- width: 100%;
- }
- &--vertical {
- padding: 24rpx 0;
- flex-direction: column;
- height: 100%;
- }
-
- &__rect {
- background-color: rgba(0, 0, 0, 0.3);
- transition: all 0.5s;
-
- &--horizontal {
- width: 26rpx;
- height: 8rpx;
- }
- &--vertical {
- width: 8rpx;
- height: 26rpx;
- }
-
- &--active {
- background-color: rgba(255, 255, 255, 0.8);
- }
- }
-
- &__dot {
- width: 14rpx;
- height: 14rpx;
- border-radius: 20rpx;
- background-color: rgba(0, 0, 0, 0.3);
- transition: all 0.5s;
-
- &--horizontal {
- margin: 0 6rpx;
- }
- &--vertical {
- margin: 6rpx 0;
- }
-
- &--active {
- background-color: rgba(255, 255, 255, 0.8);
- }
- }
-
- &__round {
- width: 14rpx;
- height: 14rpx;
- border-radius: 20rpx;
- background-color: rgba(0, 0, 0, 0.3);
- transition: all 0.5s;
-
- &--horizontal {
- margin: 0 6rpx;
- }
- &--vertical {
- margin: 6rpx 0;
- }
-
- &--active {
- background-color: rgba(255, 255, 255, 0.8);
-
- &--horizontal {
- width: 34rpx;
- }
- &--vertical {
- height: 34rpx;
- }
- }
- }
-
- &__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>
|