/** * part of the code from vant https://github.com/youzan/vant/ * @Author momoko */ <template> <div ref="swipeContent" :class="[c(),cusClass]"> <div :class="c('track')" :style="trackStyle" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd" @touchcancel="touchEnd" @transitionend="transitionend" > <slot /> </div> <div v-if="showIndicators && count > 1" :class="[c('indicators'), { [c('indicators--vertical')]: vertical }]" > <slot name="indicators"> <i v-for="item in count" :key="item" :class="[c('indicators-item'), {[c('indicators-item--active')]: item - 1 === activeIndex }]" /> </slot> </div> </div> </template> <script> import SwipeItem from './SwipeItem' import { base, touch } from '../../common/mixins' // 上抛的事件集合 const emit = { start: 'start', move: 'move', change: 'change', } export default { SwipeItem, name: 'Swipe', mixins: [base, touch], props: { index: { // .sync 当前的位置索引,从0开始 type: Number, default: 0, }, auto: { // 是否自动播放 type: Boolean, default: true, }, loop: { // 是否循环播放 type: Boolean, default: true, }, interval: { // 自动轮播间隔时间 type: Number, default: 3000, }, threshold: { // 滑动超过这个距离(px)时才切换 type: Number, default: 100, }, duration: { // 动画时长 type: Number, default: 500, }, touchable: { // 是否可以手势滑动 type: Boolean, default: true, }, vertical: { // 是否纵向滚动 type: Boolean, default: false, }, showIndicators: { // 是否显示提示点 type: Boolean, default: true, }, defaultWidth: { type: Number, default: 0, }, defaultHeight: { type: Number, default: 0, }, cusClass: { type: String, default: '', }, }, data () { return { width: 0, // swipe 盒子宽 height: 0, // swipe 盒子高 offset: 0, // track 偏移量 deltaX: 0, deltaY: 0, active: 0, // 活动的 index swipes: [], // swipe-items swiping: true, // 关闭 swipe 动画 } }, computed: { activeIndex () { return (this.active + this.count) % this.count }, count () { return this.swipes.length }, delta () { // 差异量 return this.vertical ? this.deltaY : this.deltaX }, size () { // swipe-item 长度 return this[this.vertical ? 'height' : 'width'] }, trackSize () { return this.count * this.size }, trackStyle () { return { [this.vertical ? 'height' : 'width']: `${this.trackSize}px`, transitionDuration: `${this.swiping ? 0 : this.duration}ms`, transform: `translate${this.vertical ? 'Y' : 'X'}(${this.offset}px)`, } }, }, watch: { swipes () { this.init() }, index (value) { value === this.activeIndex || this.init() }, auto (auto) { if (!auto) { this.clear() } }, }, created () { this.timer = null // 定时器 }, mounted () { this.init() }, destroyed () { this.clear() }, methods: { init () { let vm = this vm.clear() if (vm.defaultWidth) { vm.width = vm.defaultWidth } else if (vm.defaultHeight) { vm.hight = vm.defaultHeight } else { if (vm.$el) { vm.width = vm.$el.offsetWidth || document.documentElement.offsetWidth vm.height = vm.$el.offsetHeight || document.documentElement.offsetHeight } else { // 获取窗口宽度 if (window.innerWidth) { vm.width = window.innerWidth } else if ((document.body) && (document.body.clientWidth)) { vm.width = document.body.clientWidth } if (window.innerHeight) { vm.height = window.innerHeight } else if ((document.body) && (document.body.clientHeight)) { vm.height = document.body.clientHeight } // 通过深入Document内部对body进行检测,获取窗口大小 if (document.documentElement && document.documentElement.clientHeight && document.documentElement.clientWidth) { vm.height = document.documentElement.clientHeight vm.width = document.documentElement.clientWidth } } if (!vm.width) { vm.width = 375 vm.height = 667 } } this.active = this.index this.offset = this.count > 1 ? -this.size * this.active : 0 this.swipes.forEach(swipe => { swipe.offset = 0 }) this.autoPlay() }, touchStart (e) { if (!this.$props.touchable) return this.$emit(emit.start, this.activeIndex) this.clear() this.swiping = true this.onTouchStart(e) this.correctPosition() }, touchMove (e) { if (!this.touchable) return this.onTouchMove(e) if ((this.vertical && this.direction === 'vertical') || this.direction === 'horizontal') { event.preventDefault() event.stopPropagation() } this.$emit(emit.move, this.delta) this.move(0, Math.min(Math.max(this.delta, -this.size), this.size)) }, touchEnd (e) { if (!this.touchable) return if (this.delta) { const offset = this.vertical ? this.offsetY : this.offsetX this.move(offset > this.threshold ? (this.delta > 0 ? -1 : 1) : 0) this.swiping = false } this.autoPlay() }, transitionend () { this.$emit(emit.change, this.activeIndex) this.$emit('update:index', this.activeIndex) }, scopeToTop () { this.$refs.swipeContent.scrollTop = 0 }, move (move = 0, offset = 0) { const { delta, active, count, swipes, trackSize } = this const atFirst = active === 0 const atLast = active === count - 1 const outOfBounds = !this.loop && ((atFirst && (offset > 0 || move < 0)) || (atLast && (offset < 0 || move > 0))) if (outOfBounds || count <= 1) { return } if (move) { if (active === -1) { swipes[count - 1].offset = 0 } swipes[0].offset = atLast && move > 0 ? trackSize : 0 this.active += move this.scopeToTop() } else { if (atFirst) { swipes[count - 1].offset = delta > 0 ? -trackSize : 0 } else if (atLast) { swipes[0].offset = delta < 0 ? trackSize : 0 } } this.offset = offset - this.active * this.size }, // 超出边界时移动到正确的位置 correctPosition () { if (this.active <= -1) { this.move(this.count) } if (this.active >= this.count) { this.move(-this.count) } }, clear () { clearTimeout(this.timer) }, autoPlay () { if (this.auto && this.count > 1) { this.clear() this.timer = setTimeout(() => { this.swiping = true this.correctPosition() setTimeout(() => { this.swiping = false this.move(1) this.autoPlay() }, 30) }, this.interval) } }, }, } </script> <style lang="stylus"> .hls-swipe { overflow: auto; position: relative; user-select: none; overflow-x: hidden; -webkit-overflow-scrolling: touch; overflow-scrolling: touch; width: 100%; &-item { float: left; height: 100%; img { width: 100%; max-width: 100%; } } &-track { // height: 100%; } &-indicators { display: flex; position: absolute; left: 50%; bottom: 10px; transform: translateX(-50%); &--vertical { left: 10px; top: 50%; bottom: auto; flex-direction: column; transform: translateY(-50%); .hips-swipe-indicators-item:not(:last-child) { margin-bottom: 12px; } } &-item { border-radius: 100%; background-color: rgba(0, 0, 0, 0.2); width: 6px; height: 6px; margin-top: 6px; &:not(:last-child) { margin-right: 6px; //margin-top: 12px; } /*&:not(:first-child){ margin-top: 12px; }*/ &--active { background-color: rgba(0, 0, 0, 0.5); } } } } </style>