@minix-iot/ui
Version:
基于Vue的移动端轻量级UI组件库
360 lines (344 loc) • 10.9 kB
JSX
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>
)
}
}