UNPKG

@minix-iot/ui

Version:

基于Vue的移动端轻量级UI组件库

360 lines (344 loc) 10.9 kB
import { Namespace } from "../../utils/namespace"; import { mixParent } from "../../mixins/relation/parent"; import { Touch } from "../../utils/touch"; import { Direction } from "../../utils/direction"; import { MathExtension } from "../../utils/extensions/math"; import { DOMExtension } from "../../utils/extensions/dom"; import { doubleRaf } from "../../utils/raf"; export const namespace = Namespace.Create('swiper'); export default { name: namespace.name, mixins:[ mixParent(namespace.name) ], props:{ autoplay:{ type: String | Number }, duration:{ type: String | Number, default:500 }, defaultActive:{ type: String | Number, default:0 }, loop:{ type: Boolean, default: true }, showIndicator:{ type: Boolean, default: true }, indicatorColor:{ type: String }, vertical:{ type: Boolean, default: false }, swipeable:{ type:Boolean, default: true } }, data(){ return { rect: null, width: 0, height: 0, offset: 0, active: 0, swiping: false, touch: Touch.Create(), touchStartTime:-1, autoplayTimer:-1 } }, computed:{ cls(){ return namespace.derive() }, trackCls(){ return namespace.derive('track',{ vertical: this.vertical }) }, trackStyle(){ const style = { transitionDuration: `${this.swiping ? 0: this.duration}ms`, transform:`translate${this.vertical?'Y':'X'}(${this.offset}px)` } if(this.size){ style[this.vertical ? 'height':'width'] = this.trackSize } return style }, indicatorsCls(){ return namespace.derive('indicators',{ vertical:this.vertical }) }, activeIndicator(){ return (this.active + this.count) % this.count }, count(){ return this.children.length }, size(){ return this[this.vertical ? 'height' : 'width'] }, delta(){ return this.vertical ? this.touch.deltaY : this.touch.deltaX }, minOffset(){ if(this.rect){ return (this.vertical ? this.rect.height : this.rect.width) - this.size* this.count } return 0 }, maxCount(){ return Math.ceil(Math.abs(this.minOffset) / this.size) }, trackSize(){ return this.size * this.count }, isCorrectDirection(){ return this.touch.direction === (this.vertical ? Direction.VER : Direction.HOR) } }, watch:{ defaultActive(val){ this.init(val) }, count(){ this.init(this.active) }, autoPlay(){ this.startAutoplay() } }, onMounted(){ this.init() }, onActivated(){ this.init(this.active) }, onDeactivated(){ this.stopAutoplay() }, onBeforeUnmount(){ this.stopAutoplay() }, methods:{ onTouchStart(e){ if(!this.swipeable){ return } this.touch.start(e) this.touchStartTime = Date.now() this.stopAutoplay() this.correctPosition() }, onTouchMove(e){ if(this.swipeable && this.swiping){ this.touch.move(e) if(this.isCorrectDirection){ DOMExtension.PreventDefault(e,true) this.move(0, this.delta) } } }, onTouchEnd(e){ if(!this.swipeable || !this.swiping){ return } const duration = Date.now() - this.touchStartTime const speed = this.delta / duration const shouldSwipe = Math.abs(speed) > 0.25 || Math.abs(this.delta) > (this.size / 2) if(shouldSwipe && this.isCorrectDirection){ const offset = this.vertical ? this.touch.offsetY : this.touch.offsetX let pace = 0 if(this.loop){ pace = offset > 0 ? (this.delta > 0 ? -1: 1) : 0 }else { pace = -Math[this.delta > 0 ?'ceil':'floor'](this.delta / this.size) } this.move(pace,0, true) }else if(this.delta){ this.move(0) } this.swiping = false this.startAutoplay() }, stopAutoplay(){ clearTimeout(this.autoplayTimer) }, startAutoplay(){ this.stopAutoplay() if(this.autoplay > 0 && this.count > 1){ this.autoplayTimer = setTimeout(()=>{ this.next() this.startAutoplay() }, +this.autoplay) } }, prev(){ this.correctPosition() this.touch.reset() doubleRaf(()=>{ this.swiping = false this.move(-1,0,true) }) }, next(){ this.correctPosition() this.touch.reset() doubleRaf(()=>{ this.swiping = false this.move(1,0,true) }) }, swipeTo(index, options){ this.correctPosition() this.touch.reset() doubleRaf(()=>{ let targetIndex; if(this.loop && index === this.count){ targetIndex = this.active === 0 ? 0 : index }else { targetIndex = index % this.count } if(options.immediate){ doubleRaf(()=>{ this.swiping =false }) }else{ this.swiping = false } this.move(targetIndex - this.active,0, true) }) }, move(pace=0,offset=0,emitChange=false){ if(this.count <= 1){ return } const {active} = this.$data const targetActive = this.getTargetActive(pace) const targetOffset = this.getTargetOffset(targetActive, offset) if(this.loop){ if(this.children[0] && targetOffset !== this.minOffset){ this.children[0].setOffset(targetOffset < this.minOffset ? this.trackSize : 0) } if(this.children[this.count - 1] && targetOffset !== 0){ this.children[this.count - 1].setOffset(targetOffset > 0 ? -this.trackSize : 0 ) } } this.active = targetActive this.offset = targetOffset if(emitChange && active !== targetActive ){ this.$emit('change',this.activeIndicator) } }, correctPosition(){ this.swiping = true if(this.active <= -1){ this.move(this.count) }else if(this.active >= this.count){ this.move(-this.count) } }, getTargetActive(pace){ const {active} = this.$data if(pace){ if(this.loop){ return MathExtension.Clamp(active + pace, -1, this.count) } return MathExtension.Clamp(active + pace, 0, this.maxCount) } return active }, getTargetOffset(targetActive,offset=0){ let currentPosition = targetActive * this.size if(!this.loop){ currentPosition = Math.min(currentPosition, this.minOffset) } let targetOffset = offset - currentPosition if(!this.loop){ targetOffset = MathExtension.Clamp(targetOffset, this.minOffset, 0) } return targetOffset }, isHide(el){ if(!el){ return false } const style = window.getComputedStyle(el); const hidden = style.display === 'none'; const parentHidden = el.offsetParent === null && style.position !== 'fixed'; return hidden || parentHidden; }, init(active = this.defaultActive){ if(!this.$el){ return } if(!this.isHide(this.$el)){ const rect = { width: this.$el.offsetWidth, height: this.$el.offsetHeight } this.rect = rect this.width = rect.width this.height = rect.height } if(this.count){ active = Math.min(this.count-1, active) } this.active = active this.swiping = true this.offset = this.getTargetOffset(active) this.children.forEach(child=>child.setOffset(0)) this.startAutoplay() }, resize(){ this.init(this.active) } }, render(){ const renderDot = (_,index)=>{ const active = index === this.activeIndicator const style = {} if(active){ style.backgroundColor = this.indicatorColor } return <i style ={style} class={namespace.derive('indicator',{active})} /> } const renderIndicator = ()=>{ if(this.$scopedSlots.indicator){ return this.$scopedSlots.indicator({active: this.activeIndicator}) } if(this.showIndicator && this.count > 1){ return ( <div class={this.indicatorsCls}> {Array(this.count).fill(null).map(renderDot)} </div> ) } } return ( <div class={this.cls}> <div class={this.trackCls} style={this.trackStyle} onTouchstart={this.onTouchStart} onTouchmove={this.onTouchMove} onTouchend={this.onTouchEnd} onTouchcancel={this.onTouchEnd} > {this.$slots.default} </div> {renderIndicator()} </div> ) } }