drip-ui
Version:
Lightweight Mobile UI Components built on Vue
299 lines (295 loc) • 9.28 kB
JavaScript
import objectAssign from 'object-assign'
const arrayFrom = (nodeList) => Array.prototype.slice.call(nodeList)
class Swiper {
constructor(options) {
this._default = {
container: '.drip-swiper',
item: '.drip-swiper-item',
direction: 'vertical',
activeClass: 'active',
threshold: 50,
duration: 300,
auto: false,
loop: false,
interval: 3000,
height: 'auto',
minMovingDistance: 0
}
this._options = objectAssign(this._default, options)
this._options.height = this._options.height.replace('px', '')
this._start = {}
this._move = {}
this._end = {}
this._eventHandlers = {}
this._prev = this._current = this._goto = 0
this._width = this._height = this._distance = 0
this._offset = []
this.$box = this._options.container
this.$container = this._options.container.querySelector('.drip-swiper')
this.$items = this.$container.querySelectorAll(this._options.item)
this.count = this.$items.length
this.realCount = this.$items.length // real items length
this._position = [] // used by go event
this._firstItemIndex = 0
if (!this.count) {
return
}
this._init()
this._auto()
this._bind()
this._onResize()
return this
}
_auto() {
const me = this
me.stop()
if (me._options.auto) {
me.timer = setTimeout(() => {
me.next()
}, me._options.interval)
}
}
updateItemWidth() {
this._width = this.$box.offsetWidth || document.documentElement.offsetWidth
this._distance = this._options.direction === 'horizontal' ? this._width : this._height
}
stop() {
this.timer && clearTimeout(this.timer)
}
_loop() {
return this._options.loop && this.realCount >= 3
}
_onResize() {
const me = this
this.resizeHandler = () => {
setTimeout(() => {
me.updateItemWidth()
me._setOffset()
me._setTransform()
}, 100)
}
window.addEventListener('orientationchange', this.resizeHandler, false)
}
_init() {
this._height = this._options.height === 'auto' ? 'auto' : this._options.height - 0
this.updateItemWidth()
this._initPosition()
this._activate(this._current)
this._setOffset()
this._setTransform()
if (this._loop()) {
this._loopRender()
}
}
_initPosition() {
for (let i = 0; i < this.realCount; i++) {
this._position.push(i)
}
}
_movePosition(position) {
const me = this
if (position > 0) {
let firstIndex = me._position.splice(0, 1)
me._position.push(firstIndex[0])
} else if (position < 0) {
let lastIndex = me._position.pop()
me._position.unshift(lastIndex)
}
}
_setOffset() {
let me = this
let index = me._position.indexOf(me._current)
me._offset = []
arrayFrom(me.$items).forEach(function ($item, key) {
me._offset.push((key - index) * me._distance)
})
}
_setTransition(duration) {
duration = duration || (this._options.duration || 'none')
let transition = duration === 'none' ? 'none' : duration + 'ms'
arrayFrom(this.$items).forEach(function ($item, key) {
$item.style.webkitTransition = transition
$item.style.transition = transition
})
}
_setTransform(offset) {
const me = this
offset = offset || 0
arrayFrom(me.$items).forEach(function ($item, key) {
let distance = me._offset[key] + offset
let transform = `translate3d(${distance}px, 0, 0)`
if (me._options.direction === 'vertical') {
transform = `translate3d(0, ${distance}px, 0)`
}
$item.style.webkitTransform = transform
$item.style.transform = transform
})
}
_bind() {
const me = this
me.touchstartHandler = (e) => {
me.stop()
me._start.x = e.changedTouches[0].pageX
me._start.y = e.changedTouches[0].pageY
me._setTransition('none')
}
me.touchmoveHandler = (e) => {
me._move.x = e.changedTouches[0].pageX
me._move.y = e.changedTouches[0].pageY
let distanceX = me._move.x - me._start.x
let distanceY = me._move.y - me._start.y
let distance = distanceY
let noScrollerY = Math.abs(distanceX) > Math.abs(distanceY)
if (me._options.direction === 'horizontal' && noScrollerY) {
distance = distanceX
}
/* set shorter distance for first and last item for better experience */
if (!this._options.loop && (this._current === this.count - 1 || this._current === 0)) {
distance = distance / 3
}
if (((me._options.minMovingDistance && Math.abs(distance) >= me._options.minMovingDistance) || !me._options.minMovingDistance) && noScrollerY) {
me._setTransform(distance)
}
noScrollerY && e.preventDefault()
}
me.touchendHandler = (e) => {
me._end.x = e.changedTouches[0].pageX
me._end.y = e.changedTouches[0].pageY
let distance = me._end.y - me._start.y
if (me._options.direction === 'horizontal') {
distance = me._end.x - me._start.x
}
distance = me.getDistance(distance)
if (distance !== 0 && me._options.minMovingDistance && Math.abs(distance) < me._options.minMovingDistance) {
return
}
if (distance > me._options.threshold) {
me.move(-1)
} else if (distance < -me._options.threshold) {
me.move(1)
} else {
me.move(0)
}
me._loopRender()
}
me.transitionEndHandler = (e) => {
me._activate(me._current)
let cb = me._eventHandlers.swiped
cb && cb.apply(me, [me._prev % me.count, me._current % me.count])
me._auto()
me._loopRender()
e.preventDefault()
}
me.$container.addEventListener('touchstart', me.touchstartHandler, false)
me.$container.addEventListener('touchmove', me.touchmoveHandler, false)
me.$container.addEventListener('touchend', me.touchendHandler, false)
me.$items[1] && me.$items[1].addEventListener('webkitTransitionEnd', me.transitionEndHandler, false)
}
_loopRender() {
const me = this
if (me._loop()) {
// issue #507 (delete cloneNode)
if (me._offset[me._offset.length - 1] === 0) {
me.$container.appendChild(me.$items[0])
me._loopEvent(1)
} else if (me._offset[0] === 0) {
me.$container.insertBefore(me.$items[me.$items.length - 1], me.$container.firstChild)
me._loopEvent(-1)
}
}
}
_loopEvent(num) {
const me = this
me._itemDestoy()
me.$items = me.$container.querySelectorAll(me._options.item)
me.$items[1] && me.$items[1].addEventListener('webkitTransitionEnd', me.transitionEndHandler, false)
me._movePosition(num)
me._setOffset()
me._setTransform()
}
getDistance(distance) {
if (this._loop()) {
return distance
} else {
if (distance > 0 && this._current === 0) {
return 0
} else if (distance < 0 && this._current === this.realCount - 1) {
return 0
} else {
return distance
}
}
}
_moveIndex(num) {
if (num !== 0) {
this._prev = this._current
this._current += this.realCount
this._current += num
this._current %= this.realCount
}
}
_activate(index) {
let clazz = this._options.activeClass
Array.prototype.forEach.call(this.$items, ($item, key) => {
$item.classList.remove(clazz)
if (index === Number($item.dataset.index)) {
$item.classList.add(clazz)
}
})
}
go(index) {
const me = this
me.stop()
index = index || 0
index += this.realCount
index = index % this.realCount
index = this._position.indexOf(index) - this._position.indexOf(this._current)
me._moveIndex(index)
me._setOffset()
me._setTransition()
me._setTransform()
me._auto()
return this
}
next() {
this.move(1)
return this
}
move(num) {
this.go(this._current + num)
return this
}
on(event, callback) {
if (this._eventHandlers[event]) {
console.error(`[swiper] event ${event} is already register`)
}
if (typeof callback !== 'function') {
console.error('[swiper] parameter callback must be a function')
}
this._eventHandlers[event] = callback
return this
}
_itemDestoy() {
this.$items.length && arrayFrom(this.$items).forEach(item => {
item.removeEventListener('webkitTransitionEnd', this.transitionEndHandler, false)
})
}
destroy() {
this.stop()
this._current = 0
this._setTransform(0)
window.removeEventListener('orientationchange', this.resizeHandler, false)
this.$container.removeEventListener('touchstart', this.touchstartHandler, false)
this.$container.removeEventListener('touchmove', this.touchmoveHandler, false)
this.$container.removeEventListener('touchend', this.touchendHandler, false)
this._itemDestoy()
// remove clone item (used by loop only 2)
if (this._options.loop && this.count === 2) {
let $item = this.$container.querySelector(`${this._options.item}-clone`)
$item && this.$container.removeChild($item)
$item = this.$container.querySelector(`${this._options.item}-clone`)
$item && this.$container.removeChild($item)
}
}
}
export default Swiper