@zebra-ui/swiper
Version:
专为多端设计的高性能swiper轮播组件库,支持多种复杂的 3D swiper轮播效果。
282 lines (259 loc) • 9.39 kB
text/typescript
import { elementTransitionEnd, now } from '../../components/shared/utils'
import type { FreeModeOptions } from '../../types/module/free-mode'
import type { SwiperInterface } from '../../types/swiper-class'
export default function freeMode({
swiper,
extendParams,
emit,
once
}: {
swiper: SwiperInterface
extendParams: (params: Record<string, any>) => void
emit: (event: string, ...args: any[]) => void
once: (event: string, handler: () => void) => void
}): void {
extendParams({
freeMode: {
enabled: false,
momentum: true,
momentumRatio: 1,
momentumBounce: true,
momentumBounceRatio: 1,
momentumVelocityRatio: 1,
sticky: false,
minimumVelocity: 0.02
} as FreeModeOptions
})
function onTouchStart() {
if (swiper.params.cssMode) return
const translate = swiper.getTranslate()
swiper.setTranslate(translate)
swiper.setTransition(0)
swiper.touchEventsData.velocities.length = 0
swiper.freeMode.onTouchEnd({
currentPos: swiper.rtl ? swiper.translate : -swiper.translate
})
}
function onTouchMove() {
if (swiper.params.cssMode) return
const { touchEventsData: data, touches } = swiper
// Velocity
if (data.velocities.length === 0) {
data.velocities.push({
position: touches[swiper.isHorizontal() ? 'startX' : 'startY'],
time: data.touchStartTime
})
}
data.velocities.push({
position: touches[swiper.isHorizontal() ? 'currentX' : 'currentY'],
time: now()
})
}
function onTouchEnd({ currentPos }: { currentPos: number }) {
if (swiper.params.cssMode) return
const {
params,
wrapperEl,
rtlTranslate: rtl,
snapGrid,
touchEventsData: data
} = swiper
// Time diff
const touchEndTime = now()
const timeDiff = touchEndTime - data.touchStartTime
if (currentPos < -swiper.minTranslate()) {
swiper.slideTo(swiper.activeIndex)
return
}
if (currentPos > -swiper.maxTranslate()) {
if (swiper.slides.length < snapGrid.length) {
swiper.slideTo(snapGrid.length - 1)
} else {
swiper.slideTo(swiper.slides.length - 1)
}
return
}
if ((params.freeMode as FreeModeOptions)?.momentum) {
if (data.velocities.length > 1) {
const lastMoveEvent = data.velocities.pop()
const velocityEvent = data.velocities.pop()
if (!lastMoveEvent || !velocityEvent) return
const distance = lastMoveEvent.position - velocityEvent.position
const time = lastMoveEvent.time - velocityEvent.time
swiper.velocity = distance / time
swiper.velocity /= 2
if (
Math.abs(swiper.velocity) <
((params.freeMode as FreeModeOptions)?.minimumVelocity as number)
) {
swiper.velocity = 0
}
// this implies that the user stopped moving a finger then released.
// There would be no events with distance zero, so the last event is stale.
if (time > 150 || now() - lastMoveEvent.time > 300) {
swiper.velocity = 0
}
} else {
swiper.velocity = 0
}
swiper.velocity *= (params.freeMode as FreeModeOptions)
?.momentumVelocityRatio as number
data.velocities.length = 0
let momentumDuration =
1000 * ((params.freeMode as FreeModeOptions)?.momentumRatio as number)
const momentumDistance = swiper.velocity * momentumDuration
let newPosition = swiper.translate + momentumDistance
if (rtl) newPosition = -newPosition
let doBounce = false
let afterBouncePosition: number
const bounceAmount =
Math.abs(swiper.velocity) *
20 *
((params.freeMode as FreeModeOptions)?.momentumBounceRatio as number)
let needsLoopFix: boolean
if (newPosition < swiper.maxTranslate()) {
if ((params.freeMode as FreeModeOptions)?.momentumBounce) {
if (newPosition + swiper.maxTranslate() < -bounceAmount) {
newPosition = swiper.maxTranslate() - bounceAmount
}
afterBouncePosition = swiper.maxTranslate()
doBounce = true
data.allowMomentumBounce = true
} else {
newPosition = swiper.maxTranslate()
}
if (params.loop && params.centeredSlides) needsLoopFix = true
} else if (newPosition > swiper.minTranslate()) {
if ((params.freeMode as FreeModeOptions)?.momentumBounce) {
if (newPosition - swiper.minTranslate() > bounceAmount) {
newPosition = swiper.minTranslate() + bounceAmount
}
afterBouncePosition = swiper.minTranslate()
doBounce = true
data.allowMomentumBounce = true
} else {
newPosition = swiper.minTranslate()
}
if (params.loop && params.centeredSlides) needsLoopFix = true
} else if ((params.freeMode as FreeModeOptions)?.sticky) {
let nextSlide
for (let j = 0; j < snapGrid.length; j += 1) {
if (snapGrid[j] > -newPosition) {
nextSlide = j
break
}
}
if (
Math.abs(snapGrid[nextSlide as number] - newPosition) <
Math.abs(snapGrid[(nextSlide as number) - 1] - newPosition) ||
swiper.swipeDirection === 'next'
) {
newPosition = snapGrid[nextSlide as number]
} else {
newPosition = snapGrid[(nextSlide as number) - 1]
}
newPosition = -newPosition
}
// @ts-ignore
if (needsLoopFix) {
once('transitionEnd', () => {
// @ts-ignore
swiper.loopFix()
})
}
// Fix duration
if (swiper.velocity !== 0) {
if (rtl) {
momentumDuration = Math.abs(
(-newPosition - swiper.translate) / swiper.velocity
)
} else {
momentumDuration = Math.abs(
(newPosition - swiper.translate) / swiper.velocity
)
}
if ((params.freeMode as FreeModeOptions)?.sticky) {
// If freeMode.sticky is active and the user ends a swipe with a slow-velocity
// event, then durations can be 20+ seconds to slide one (or zero!) slides.
// It's easy to see this when simulating touch with mouse events. To fix this,
// limit single-slide swipes to the default slide duration. This also has the
// nice side effect of matching slide speed if the user stopped moving before
// lifting finger or mouse vs. moving slowly before lifting the finger/mouse.
// For faster swipes, also apply limits (albeit higher ones).
const moveDistance = Math.abs(
(rtl ? -newPosition : newPosition) - swiper.translate
)
const currentSlideSize = swiper.slidesSizesGrid[swiper.activeIndex]
if (moveDistance < currentSlideSize) {
momentumDuration = params.speed as number
} else if (moveDistance < 2 * currentSlideSize) {
momentumDuration = (params.speed as number) * 1.5
} else {
momentumDuration = (params.speed as number) * 2.5
}
}
} else if ((params.freeMode as FreeModeOptions)?.sticky) {
swiper.slideToClosest()
return
}
if ((params.freeMode as FreeModeOptions)?.momentumBounce && doBounce) {
// @ts-ignore
swiper.updateProgress(afterBouncePosition as number)
swiper.setTransition(momentumDuration)
swiper.setTranslate(newPosition)
swiper.transitionStart(true, swiper.swipeDirection)
swiper.animating = true
elementTransitionEnd(wrapperEl, () => {
if (!swiper || swiper.destroyed || !data.allowMomentumBounce) return
emit('momentumBounce')
swiper.setTransition(params.speed as number)
setTimeout(() => {
swiper.setTranslate(afterBouncePosition as number)
elementTransitionEnd(wrapperEl, () => {
if (!swiper || swiper.destroyed) return
swiper.transitionEnd()
})
}, 0)
})
} else if (swiper.velocity) {
emit('_freeModeNoMomentumRelease')
swiper.updateProgress(newPosition)
swiper.setTransition(momentumDuration)
swiper.setTranslate(newPosition)
swiper.transitionStart(true, swiper.swipeDirection)
if (!swiper.animating) {
swiper.animating = true
elementTransitionEnd(wrapperEl, () => {
if (!swiper || swiper.destroyed) return
swiper.transitionEnd()
})
}
} else {
swiper.updateProgress(newPosition)
}
swiper.updateActiveIndex()
swiper.updateSlidesClasses()
} else if ((params.freeMode as FreeModeOptions)?.sticky) {
swiper.slideToClosest()
return
} else if (params.freeMode) {
emit('_freeModeNoMomentumRelease')
}
if (
!((params.freeMode as FreeModeOptions)?.momentum as boolean) ||
timeDiff >= (params.longSwipesMs as number)
) {
emit('_freeModeStaticRelease')
swiper.updateProgress()
swiper.updateActiveIndex()
swiper.updateSlidesClasses()
}
}
Object.assign(swiper, {
freeMode: {
onTouchStart,
onTouchMove,
onTouchEnd
}
})
}