free-mode.js 6.6 KB
import {
	now
} from '../../shared/utils.js';
export default function freeMode({
	swiper,
	extendParams,
	emit,
	once
}) {
	extendParams({
		freeMode: {
			enabled: false,
			momentum: true,
			momentumRatio: 1,
			momentumBounce: true,
			momentumBounceRatio: 1,
			momentumVelocityRatio: 1,
			sticky: false,
			minimumVelocity: 0.02
		}
	});

	function onTouchMove() {
		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
	}) {
		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.momentum) {
			if (data.velocities.length > 1) {
				const lastMoveEvent = data.velocities.pop();
				const velocityEvent = data.velocities.pop();
				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.minimumVelocity) {
					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.momentumVelocityRatio;
			data.velocities.length = 0;
			let momentumDuration = 1000 * params.freeMode.momentumRatio;
			const momentumDistance = swiper.velocity * momentumDuration;
			let newPosition = swiper.translate + momentumDistance;
			if (rtl) newPosition = -newPosition;
			let doBounce = false;
			let afterBouncePosition;
			const bounceAmount = Math.abs(swiper.velocity) * 20 * params.freeMode.momentumBounceRatio;
			let needsLoopFix;

			if (newPosition < swiper.maxTranslate()) {
				if (params.freeMode.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.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.sticky) {
				let nextSlide;

				for (let j = 0; j < snapGrid.length; j += 1) {
					if (snapGrid[j] > -newPosition) {
						nextSlide = j;
						break;
					}
				}

				if (Math.abs(snapGrid[nextSlide] - newPosition) < Math.abs(snapGrid[nextSlide - 1] - newPosition) ||
					swiper.swipeDirection === 'next') {
					newPosition = snapGrid[nextSlide];
				} else {
					newPosition = snapGrid[nextSlide - 1];
				}

				newPosition = -newPosition;
			}

			if (needsLoopFix) {
				once('transitionEnd', () => {
					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.sticky) {
					const moveDistance = Math.abs((rtl ? -newPosition : newPosition) - swiper.translate);
					const currentSlideSize = swiper.slidesSizesGrid[swiper.activeIndex];

					if (moveDistance < currentSlideSize) {
						momentumDuration = params.speed;
					} else if (moveDistance < 2 * currentSlideSize) {
						momentumDuration = params.speed * 1.5;
					} else {
						momentumDuration = params.speed * 2.5;
					}
				}
			} else if (params.freeMode.sticky) {
				swiper.slideToClosest();
				return;
			}

			if (params.freeMode.momentumBounce && doBounce) {
				swiper.updateProgress(afterBouncePosition);
				swiper.setTransition(momentumDuration);
				swiper.setTranslate(newPosition);
				swiper.transitionStart(true, swiper.swipeDirection);
				swiper.animating = true;
				$wrapperEl.transitionEnd(() => {
					if (!swiper || swiper.destroyed || !data.allowMomentumBounce) return;
					emit('momentumBounce');
					swiper.setTransition(params.speed);
					setTimeout(() => {
						swiper.setTranslate(afterBouncePosition);
						$wrapperEl.transitionEnd(() => {
							if (!swiper || swiper.destroyed) return;
							swiper.transitionEnd();
						}, momentumDuration);
					}, 0);
				}, momentumDuration);
			} 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;
					$wrapperEl.transitionEnd(() => {
						if (!swiper || swiper.destroyed) return;
						swiper.transitionEnd();
					}, momentumDuration);
				}
			} else {
				swiper.updateProgress(newPosition);
			}

			swiper.updateActiveIndex();
			swiper.updateSlidesClasses();
		} else if (params.freeMode.sticky) {
			swiper.slideToClosest();
			return;
		} else if (params.freeMode) {
			emit('_freeModeNoMomentumRelease');
		}

		if (!params.freeMode.momentum || timeDiff >= params.longSwipesMs) {
			swiper.updateProgress();
			swiper.updateActiveIndex();
			swiper.updateSlidesClasses();
		}
	}

	Object.assign(swiper, {
		freeMode: {
			onTouchMove,
			onTouchEnd
		}
	});
}