swiper
Version:
Most modern mobile touch slider and framework with hardware accelerated transitions
1,398 lines (1,376 loc) • 154 kB
JavaScript
import { n as extend, u as now, t as nextTick, f as elementIsChildOf, x as showWarning, e as elementChildren, a as createElement, j as elementParents, q as getTranslate, l as elementStyle, v as setCSSProperty, i as elementOuterSize, g as elementNextAll, k as elementPrevAll, b as elementIndex, d as deleteProps } from './utils.mjs';
let supportCached;
function calcSupport() {
if (typeof window === 'undefined')
return { touch: false };
return {
touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0,
};
}
function getSupport() {
if (!supportCached)
supportCached = calcSupport();
return supportCached;
}
let deviceCached;
function calcDevice({ userAgent } = {}) {
if (typeof window === 'undefined')
return { ios: false, android: false };
const support = getSupport();
const platform = navigator.platform;
const ua = userAgent || navigator.userAgent;
const device = { ios: false, android: false };
const isAndroid = /(Android);?[\s/]+([\d.]+)?/.test(ua);
const isIPhoneOrIPod = /(iPhone\sOS|iOS|iPod)/.test(ua);
const isIPadDirect = /iPad/.test(ua);
// iPad on iPadOS 13+ reports as MacIntel; distinguish from a real Mac by touch capability.
const isIPadMasquerade = platform === 'MacIntel' && support.touch && navigator.maxTouchPoints > 1;
const isIPad = isIPadDirect || isIPadMasquerade;
const isWindows = platform === 'Win32';
if (isAndroid && !isWindows) {
device.os = 'android';
device.android = true;
}
if (isIPad || isIPhoneOrIPod) {
device.os = 'ios';
device.ios = true;
}
return device;
}
function getDevice(overrides = {}) {
if (!deviceCached)
deviceCached = calcDevice(overrides);
return deviceCached;
}
let browserCached;
function calcBrowser() {
if (typeof window === 'undefined') {
return { isSafari: false, isWebView: false, need3dFix: false };
}
const device = getDevice();
const ua = navigator.userAgent;
const uaLower = ua.toLowerCase();
const isSafari = uaLower.includes('safari') && !uaLower.includes('chrome') && !uaLower.includes('android');
const isWebView = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(ua);
// 3D transform glitches still affect iOS WebView and Safari at the baseline (16.4+).
const need3dFix = isSafari || (isWebView && device.ios);
return { isSafari, isWebView, need3dFix };
}
function getBrowser() {
if (!browserCached)
browserCached = calcBrowser();
return browserCached;
}
const processLazyPreloader = (swiper, imageEl) => {
if (!swiper || swiper.destroyed || !swiper.params)
return;
const slideSelector = () => (swiper.isElement ? 'swiper-slide' : `.${swiper.params.slideClass}`);
const slideEl = imageEl.closest(slideSelector());
if (slideEl) {
let lazyEl = slideEl.querySelector(`.${swiper.params.lazyPreloaderClass}`);
if (!lazyEl && swiper.isElement) {
if (slideEl.shadowRoot) {
lazyEl = slideEl.shadowRoot.querySelector(`.${swiper.params.lazyPreloaderClass}`);
}
else {
requestAnimationFrame(() => {
if (slideEl.shadowRoot) {
const innerLazy = slideEl.shadowRoot.querySelector(`.${swiper.params.lazyPreloaderClass}`);
if (innerLazy && !innerLazy.lazyPreloaderManaged)
innerLazy.remove();
}
});
}
}
if (lazyEl && !lazyEl.lazyPreloaderManaged)
lazyEl.remove();
}
};
const unlazy = (swiper, index) => {
if (!swiper.slides[index])
return;
const imageEl = swiper.slides[index].querySelector('[loading="lazy"]');
if (imageEl)
imageEl.removeAttribute('loading');
};
const preload = (swiper) => {
if (!swiper || swiper.destroyed || !swiper.params)
return;
let amount = swiper.params.lazyPreloadPrevNext;
const len = swiper.slides.length;
if (!len || !amount || amount < 0)
return;
amount = Math.min(amount, len);
const slidesPerView = swiper.params.slidesPerView === 'auto'
? swiper.slidesPerViewDynamic()
: Math.ceil(swiper.params.slidesPerView);
const activeIndex = swiper.activeIndex;
if (swiper.params.grid && (swiper.params.grid.rows ?? 1) > 1) {
const activeColumn = activeIndex;
const preloadColumns = [activeColumn - amount];
preloadColumns.push(...Array.from({ length: amount }).map((_, i) => activeColumn + slidesPerView + i));
swiper.slides.forEach((slideEl, i) => {
if (slideEl.column !== undefined && preloadColumns.includes(slideEl.column))
unlazy(swiper, i);
});
return;
}
const slideIndexLastInView = activeIndex + slidesPerView - 1;
if (swiper.params.rewind || swiper.params.loop) {
for (let i = activeIndex - amount; i <= slideIndexLastInView + amount; i += 1) {
const realIndex = ((i % len) + len) % len;
if (realIndex < activeIndex || realIndex > slideIndexLastInView)
unlazy(swiper, realIndex);
}
}
else {
for (let i = Math.max(activeIndex - amount, 0); i <= Math.min(slideIndexLastInView + amount, len - 1); i += 1) {
if (i !== activeIndex && (i > slideIndexLastInView || i < activeIndex)) {
unlazy(swiper, i);
}
}
}
};
function getBreakpoint(breakpoints, base = 'window', containerEl) {
if (!breakpoints || (base === 'container' && !containerEl))
return undefined;
let breakpoint = false;
const currentHeight = base === 'window' ? window.innerHeight : containerEl.clientHeight;
const points = Object.keys(breakpoints).map((point) => {
if (typeof point === 'string' && point.indexOf('@') === 0) {
const minRatio = parseFloat(point.substr(1));
const value = currentHeight * minRatio;
return { value, point };
}
return { value: point, point };
});
points.sort((a, b) => parseInt(String(a.value), 10) - parseInt(String(b.value), 10));
for (let i = 0; i < points.length; i += 1) {
const { point, value } = points[i];
if (base === 'window') {
if (window.matchMedia(`(min-width: ${value}px)`).matches) {
breakpoint = point;
}
}
else if (value <= containerEl.clientWidth) {
breakpoint = point;
}
}
return breakpoint || 'max';
}
const isGridEnabled = (swiper, params) => {
return !!(swiper.grid && params.grid && params.grid.rows > 1);
};
function setBreakpoint() {
const swiper = this;
const { realIndex, initialized, params, el } = swiper;
const breakpoints = params.breakpoints;
if (!breakpoints || (breakpoints && Object.keys(breakpoints).length === 0))
return;
// Get breakpoint for window/container width and update parameters
const breakpointsBase = params.breakpointsBase === 'window' || !params.breakpointsBase
? params.breakpointsBase
: 'container';
const breakpointContainer = ['window', 'container'].includes(params.breakpointsBase) || !params.breakpointsBase
? swiper.el
: document.querySelector(params.breakpointsBase);
const breakpoint = swiper.getBreakpoint(breakpoints, breakpointsBase, breakpointContainer);
if (!breakpoint || swiper.currentBreakpoint === breakpoint)
return;
const breakpointsRecord = breakpoints;
const breakpointOnlyParams = breakpoint in breakpointsRecord ? breakpointsRecord[breakpoint] : undefined;
const breakpointParams = breakpointOnlyParams || swiper.originalParams;
const wasMultiRow = isGridEnabled(swiper, params);
const isMultiRow = isGridEnabled(swiper, breakpointParams);
const wasGrabCursor = swiper.params.grabCursor;
const isGrabCursor = breakpointParams.grabCursor;
const wasEnabled = params.enabled;
if (wasMultiRow && !isMultiRow) {
el.classList.remove(`${params.containerModifierClass}grid`, `${params.containerModifierClass}grid-column`);
swiper.emitContainerClasses();
}
else if (!wasMultiRow && isMultiRow) {
el.classList.add(`${params.containerModifierClass}grid`);
if ((breakpointParams.grid.fill && breakpointParams.grid.fill === 'column') ||
(!breakpointParams.grid.fill && params.grid.fill === 'column')) {
el.classList.add(`${params.containerModifierClass}grid-column`);
}
swiper.emitContainerClasses();
}
if (wasGrabCursor && !isGrabCursor) {
swiper.unsetGrabCursor();
}
else if (!wasGrabCursor && isGrabCursor) {
swiper.setGrabCursor();
}
const moduleOpt = (opts, prop) => opts[prop];
['navigation', 'pagination', 'scrollbar'].forEach((prop) => {
const bpOpts = moduleOpt(breakpointParams, prop);
if (typeof bpOpts === 'undefined')
return;
const paramsOpts = moduleOpt(params, prop);
const wasModuleEnabled = typeof paramsOpts === 'object' && paramsOpts !== null && paramsOpts.enabled;
const isModuleEnabled = typeof bpOpts === 'object' && bpOpts !== null && bpOpts.enabled;
const moduleApi = swiper[prop];
if (wasModuleEnabled && !isModuleEnabled)
moduleApi?.disable?.();
if (!wasModuleEnabled && isModuleEnabled)
moduleApi?.enable?.();
});
const directionChanged = breakpointParams.direction && breakpointParams.direction !== params.direction;
const needsReLoop = params.loop && (breakpointParams.slidesPerView !== params.slidesPerView || directionChanged);
const wasLoop = params.loop;
if (directionChanged && initialized) {
swiper.changeDirection();
}
extend(swiper.params, breakpointParams);
const isEnabled = swiper.params.enabled;
const hasLoop = swiper.params.loop;
Object.assign(swiper, {
allowTouchMove: swiper.params.allowTouchMove,
allowSlideNext: swiper.params.allowSlideNext,
allowSlidePrev: swiper.params.allowSlidePrev,
});
if (wasEnabled && !isEnabled) {
swiper.disable();
}
else if (!wasEnabled && isEnabled) {
swiper.enable();
}
swiper.currentBreakpoint = breakpoint;
swiper.emit('_beforeBreakpoint', breakpointParams);
if (initialized) {
if (needsReLoop) {
swiper.loopDestroy();
swiper.loopCreate(realIndex);
swiper.updateSlides();
}
else if (!wasLoop && hasLoop) {
swiper.loopCreate(realIndex);
swiper.updateSlides();
}
else if (wasLoop && !hasLoop) {
swiper.loopDestroy();
}
}
swiper.emit('breakpoint', breakpointParams);
}
var breakpoints = { setBreakpoint, getBreakpoint };
function checkOverflow() {
const swiper = this;
const { isLocked: wasLocked, params } = swiper;
const { slidesOffsetBefore } = params;
if (slidesOffsetBefore) {
const lastSlideIndex = swiper.slides.length - 1;
const lastSlideRightEdge = swiper.slidesGrid[lastSlideIndex] +
swiper.slidesSizesGrid[lastSlideIndex] +
slidesOffsetBefore * 2;
swiper.isLocked = swiper.size > lastSlideRightEdge;
}
else {
swiper.isLocked = swiper.snapGrid.length === 1;
}
if (params.allowSlideNext === true) {
swiper.allowSlideNext = !swiper.isLocked;
}
if (params.allowSlidePrev === true) {
swiper.allowSlidePrev = !swiper.isLocked;
}
if (wasLocked && wasLocked !== swiper.isLocked) {
swiper.isEnd = false;
}
if (wasLocked !== swiper.isLocked) {
swiper.emit(swiper.isLocked ? 'lock' : 'unlock');
}
}
var checkOverflow$1 = { checkOverflow };
function prepareClasses(entries, prefix) {
const resultClasses = [];
entries.forEach((item) => {
if (typeof item === 'object') {
Object.keys(item).forEach((classNames) => {
if (item[classNames]) {
resultClasses.push(prefix + classNames);
}
});
}
else if (typeof item === 'string') {
resultClasses.push(prefix + item);
}
});
return resultClasses;
}
function addClasses() {
const swiper = this;
const { classNames, params, rtl, el, device } = swiper;
// oxfmt-ignore
const suffixes = prepareClasses([
'initialized',
params.direction,
{ 'free-mode': swiper.params.freeMode && params.freeMode.enabled },
{ 'autoheight': params.autoHeight },
{ 'rtl': rtl },
{ 'grid': params.grid && params.grid.rows > 1 },
{ 'grid-column': params.grid && params.grid.rows > 1 && params.grid.fill === 'column' },
{ 'android': device.android },
{ 'ios': device.ios },
{ 'css-mode': params.cssMode },
{ 'centered': params.cssMode && params.centeredSlides },
{ 'watch-progress': params.watchSlidesProgress },
], params.containerModifierClass);
classNames.push(...suffixes);
el.classList.add(...classNames);
swiper.emitContainerClasses();
}
function removeClasses() {
const swiper = this;
const { el, classNames } = swiper;
if (!el || typeof el === 'string')
return;
el.classList.remove(...classNames);
swiper.emitContainerClasses();
}
var classes = { addClasses, removeClasses };
const defaults = {
init: true,
direction: 'horizontal',
oneWayMovement: false,
swiperElementNodeName: 'SWIPER-CONTAINER',
touchEventsTarget: 'wrapper',
initialSlide: 0,
speed: 300,
cssMode: false,
updateOnWindowResize: true,
resizeObserver: true,
nested: false,
createElements: false,
eventsPrefix: 'swiper',
enabled: true,
focusableElements: 'input, select, option, textarea, button, video, label',
// Overrides
width: null,
height: null,
//
preventInteractionOnTransition: false,
// ssr
userAgent: null,
url: null,
// To support iOS's swipe-to-go-back gesture (when being used in-app).
edgeSwipeDetection: false,
edgeSwipeThreshold: 20,
// Autoheight
autoHeight: false,
// Set wrapper width
setWrapperSize: false,
// Virtual Translate
virtualTranslate: false,
// Effects
effect: 'slide',
// Breakpoints
breakpoints: undefined,
breakpointsBase: 'window',
// Slides grid
spaceBetween: 0,
slidesPerView: 1,
slidesPerGroup: 1,
slidesPerGroupSkip: 0,
slidesPerGroupAuto: false,
centeredSlides: false,
centeredSlidesBounds: false,
slidesOffsetBefore: 0,
slidesOffsetAfter: 0,
normalizeSlideIndex: true,
centerInsufficientSlides: false,
snapToSlideEdge: false,
// Disable swiper and hide navigation when container not overflow
watchOverflow: true,
// Round length
roundLengths: false,
// Touches
touchRatio: 1,
touchAngle: 45,
simulateTouch: true,
shortSwipes: true,
longSwipes: true,
longSwipesRatio: 0.5,
longSwipesMs: 300,
followFinger: true,
allowTouchMove: true,
threshold: 5,
touchMoveStopPropagation: false,
touchStartPreventDefault: true,
touchStartForcePreventDefault: false,
touchReleaseOnEdges: false,
// Unique Navigation Elements
uniqueNavElements: true,
// Resistance
resistance: true,
resistanceRatio: 0.85,
// Progress
watchSlidesProgress: false,
// Cursor
grabCursor: false,
// Clicks
preventClicks: true,
preventClicksPropagation: true,
slideToClickedSlide: false,
// loop
loop: false,
loopAddBlankSlides: true,
loopAdditionalSlides: 0,
loopPreventsSliding: true,
// rewind
rewind: false,
// Swiping/no swiping
allowSlidePrev: true,
allowSlideNext: true,
swipeHandler: null,
noSwiping: true,
noSwipingClass: 'swiper-no-swiping',
noSwipingSelector: null,
// Passive Listeners
passiveListeners: true,
maxBackfaceHiddenSlides: 10,
// NS
containerModifierClass: 'swiper-',
slideClass: 'swiper-slide',
slideBlankClass: 'swiper-slide-blank',
slideActiveClass: 'swiper-slide-active',
slideVisibleClass: 'swiper-slide-visible',
slideFullyVisibleClass: 'swiper-slide-fully-visible',
slideNextClass: 'swiper-slide-next',
slidePrevClass: 'swiper-slide-prev',
wrapperClass: 'swiper-wrapper',
lazyPreloaderClass: 'swiper-lazy-preloader',
lazyPreloadPrevNext: 0,
// Callbacks
runCallbacksOnInit: true,
// Internals
_emitClasses: false,
};
var eventsEmitter = {
on(events, handler, priority) {
const self = this;
if (!self.eventsListeners || self.destroyed)
return self;
if (typeof handler !== 'function')
return self;
const method = priority ? 'unshift' : 'push';
events.split(' ').forEach((event) => {
if (!self.eventsListeners[event])
self.eventsListeners[event] = [];
self.eventsListeners[event][method](handler);
});
return self;
},
once(events, handler, priority) {
const self = this;
if (!self.eventsListeners || self.destroyed)
return self;
if (typeof handler !== 'function')
return self;
const onceHandler = function onceHandlerFn(...args) {
self.off(events, onceHandler);
if (onceHandler.__emitterProxy) {
delete onceHandler.__emitterProxy;
}
handler.apply(self, args);
};
onceHandler.__emitterProxy = handler;
return self.on(events, onceHandler, priority);
},
onAny(handler, priority) {
const self = this;
if (!self.eventsListeners || self.destroyed)
return self;
if (typeof handler !== 'function')
return self;
const method = priority ? 'unshift' : 'push';
if (self.eventsAnyListeners.indexOf(handler) < 0) {
self.eventsAnyListeners[method](handler);
}
return self;
},
offAny(handler) {
const self = this;
if (!self.eventsListeners || self.destroyed)
return self;
if (!self.eventsAnyListeners)
return self;
const index = self.eventsAnyListeners.indexOf(handler);
if (index >= 0) {
self.eventsAnyListeners.splice(index, 1);
}
return self;
},
off(events, handler) {
const self = this;
if (!self.eventsListeners || self.destroyed)
return self;
if (!self.eventsListeners)
return self;
events.split(' ').forEach((event) => {
if (typeof handler === 'undefined') {
self.eventsListeners[event] = [];
}
else if (self.eventsListeners[event]) {
self.eventsListeners[event].forEach((eventHandler, index) => {
if (eventHandler === handler ||
(eventHandler.__emitterProxy && eventHandler.__emitterProxy === handler)) {
self.eventsListeners[event].splice(index, 1);
}
});
}
});
return self;
},
emit(...args) {
const self = this;
if (!self.eventsListeners || self.destroyed)
return self;
if (!self.eventsListeners)
return self;
let events;
let data;
let context;
if (typeof args[0] === 'string' || Array.isArray(args[0])) {
events = args[0];
data = args.slice(1, args.length);
context = self;
}
else {
const opts = args[0];
events = opts.events;
data = opts.data ?? [];
context = opts.context || self;
}
data.unshift(context);
const eventsArray = Array.isArray(events) ? events : events.split(' ');
eventsArray.forEach((event) => {
if (self.eventsAnyListeners && self.eventsAnyListeners.length) {
self.eventsAnyListeners.forEach((eventHandler) => {
eventHandler.apply(context, [event, ...data]);
});
}
if (self.eventsListeners && self.eventsListeners[event]) {
self.eventsListeners[event].forEach((eventHandler) => {
eventHandler.apply(context, data);
});
}
});
return self;
},
};
function onClick(e) {
const swiper = this;
if (swiper.destroyed)
return;
if (!swiper.enabled)
return;
if (!swiper.allowClick) {
if (swiper.params.preventClicks)
e.preventDefault();
if (swiper.params.preventClicksPropagation && swiper.animating) {
e.stopPropagation();
e.stopImmediatePropagation();
}
}
}
function onDocumentTouchStart() {
const swiper = this;
if (swiper.destroyed)
return;
if (swiper.documentTouchHandlerProceeded)
return;
swiper.documentTouchHandlerProceeded = true;
if (swiper.params.touchReleaseOnEdges) {
swiper.el.style.touchAction = 'auto';
}
}
function onLoad(e) {
const swiper = this;
if (swiper.destroyed)
return;
processLazyPreloader(swiper, e.target);
if (swiper.params.cssMode ||
(swiper.params.slidesPerView !== 'auto' && !swiper.params.autoHeight)) {
return;
}
swiper.update();
}
function onResize() {
const swiper = this;
const { params, el } = swiper;
if (el && el.offsetWidth === 0)
return;
// Breakpoints
if (params.breakpoints) {
swiper.setBreakpoint();
}
// Save locks
const { allowSlideNext, allowSlidePrev, snapGrid } = swiper;
const isVirtual = swiper.virtual && swiper.params.virtual?.enabled;
// Disable locks on resize
swiper.allowSlideNext = true;
swiper.allowSlidePrev = true;
swiper.updateSize();
swiper.updateSlides();
swiper.updateSlidesClasses();
const isVirtualLoop = isVirtual && params.loop;
if ((params.slidesPerView === 'auto' || params.slidesPerView > 1) &&
swiper.isEnd &&
!swiper.isBeginning &&
!swiper.params.centeredSlides &&
!isVirtualLoop) {
const slidesLength = isVirtual ? swiper.virtual.slides.length : swiper.slides.length;
swiper.slideTo(slidesLength - 1, 0, false, true);
}
else {
if (swiper.params.loop && !isVirtual) {
swiper.slideToLoop(swiper.realIndex, 0, false, true);
}
else {
swiper.slideTo(swiper.activeIndex, 0, false, true);
}
}
if (swiper.autoplay && swiper.autoplay.running && swiper.autoplay.paused) {
const autoplay = swiper.autoplay;
clearTimeout(autoplay.resizeTimeout);
autoplay.resizeTimeout = setTimeout(() => {
if (swiper.autoplay && swiper.autoplay.running && swiper.autoplay.paused) {
swiper.autoplay.resume();
}
}, 500);
}
// Return locks after resize
swiper.allowSlidePrev = allowSlidePrev;
swiper.allowSlideNext = allowSlideNext;
if (swiper.params.watchOverflow && snapGrid !== swiper.snapGrid) {
swiper.checkOverflow();
}
}
function onScroll() {
const swiper = this;
if (swiper.destroyed)
return;
const { wrapperEl, rtlTranslate, enabled } = swiper;
if (!enabled)
return;
swiper.previousTranslate = swiper.translate;
if (swiper.isHorizontal()) {
swiper.translate = -wrapperEl.scrollLeft;
}
else {
swiper.translate = -wrapperEl.scrollTop;
}
if (swiper.translate === 0)
swiper.translate = 0;
swiper.updateActiveIndex();
swiper.updateSlidesClasses();
let newProgress;
const translatesDiff = swiper.maxTranslate() - swiper.minTranslate();
if (translatesDiff === 0) {
newProgress = 0;
}
else {
newProgress = (swiper.translate - swiper.minTranslate()) / translatesDiff;
}
if (newProgress !== swiper.progress) {
swiper.updateProgress(rtlTranslate ? -swiper.translate : swiper.translate);
}
swiper.emit('setTranslate', swiper.translate, false);
}
function onTouchEnd(event) {
const swiper = this;
if (swiper.destroyed)
return;
const data = swiper.touchEventsData;
let e = event.originalEvent ?? event;
const isTouchEvent = e.type === 'touchend' || e.type === 'touchcancel';
if (!isTouchEvent) {
if (data.touchId !== null)
return; // return from pointer if we use touch
const pe = e;
if (pe.pointerId !== data.pointerId)
return;
}
else {
const te = e;
const found = [...te.changedTouches].find((t) => t.identifier === data.touchId);
if (!found || found.identifier !== data.touchId)
return;
}
if (['pointercancel', 'pointerout', 'pointerleave', 'contextmenu'].includes(e.type)) {
const proceed = ['pointercancel', 'contextmenu'].includes(e.type) &&
(swiper.browser.isSafari || swiper.browser.isWebView);
if (!proceed) {
return;
}
}
data.pointerId = null;
data.touchId = null;
const { params, touches, rtlTranslate: rtl, slidesGrid, enabled } = swiper;
if (!enabled)
return;
if (!params.simulateTouch && e.pointerType === 'mouse')
return;
if (data.allowTouchCallbacks) {
swiper.emit('touchEnd', e);
}
data.allowTouchCallbacks = false;
if (!data.isTouched) {
if (data.isMoved && params.grabCursor) {
swiper.setGrabCursor(false);
}
data.isMoved = false;
data.startMoving = false;
return;
}
// Return Grab Cursor
if (params.grabCursor &&
data.isMoved &&
data.isTouched &&
(swiper.allowSlideNext === true || swiper.allowSlidePrev === true)) {
swiper.setGrabCursor(false);
}
// Time diff
const touchEndTime = now();
const timeDiff = touchEndTime - data.touchStartTime;
// Tap, doubleTap, Click
if (swiper.allowClick) {
// Legacy `e.path` was a non-standard Chrome extension; `composedPath()` is the modern API.
const pathTree = e.path ?? (e.composedPath && e.composedPath());
swiper.updateClickedSlide((pathTree && pathTree[0]), pathTree);
swiper.emit('tap click', e);
if (timeDiff < 300 && touchEndTime - data.lastClickTime < 300) {
swiper.emit('doubleTap doubleClick', e);
}
}
data.lastClickTime = now();
nextTick(() => {
if (!swiper.destroyed)
swiper.allowClick = true;
});
if (!data.isTouched ||
!data.isMoved ||
!swiper.swipeDirection ||
(touches.diff === 0 && !data.loopSwapReset) ||
(data.currentTranslate === data.startTranslate && !data.loopSwapReset)) {
data.isTouched = false;
data.isMoved = false;
data.startMoving = false;
return;
}
data.isTouched = false;
data.isMoved = false;
data.startMoving = false;
let currentPos;
if (params.followFinger) {
currentPos = rtl ? swiper.translate : -swiper.translate;
}
else {
currentPos = -(data.currentTranslate ?? 0);
}
if (params.cssMode) {
return;
}
if (params.freeMode && params.freeMode.enabled) {
swiper.freeMode.onTouchEnd({ currentPos });
return;
}
// Find current slide
const swipeToLast = currentPos >= -swiper.maxTranslate() && !swiper.params.loop;
let stopIndex = 0;
let groupSize = swiper.slidesSizesGrid[0];
for (let i = 0; i < slidesGrid.length; i += i < params.slidesPerGroupSkip ? 1 : params.slidesPerGroup) {
const increment = i < params.slidesPerGroupSkip - 1 ? 1 : params.slidesPerGroup;
if (typeof slidesGrid[i + increment] !== 'undefined') {
if (swipeToLast ||
(currentPos >= slidesGrid[i] && currentPos < slidesGrid[i + increment])) {
stopIndex = i;
groupSize = slidesGrid[i + increment] - slidesGrid[i];
}
}
else if (swipeToLast || currentPos >= slidesGrid[i]) {
stopIndex = i;
groupSize = slidesGrid[slidesGrid.length - 1] - slidesGrid[slidesGrid.length - 2];
}
}
let rewindFirstIndex = null;
let rewindLastIndex = null;
if (params.rewind) {
if (swiper.isBeginning) {
rewindLastIndex =
params.virtual?.enabled && swiper.virtual
? swiper.virtual.slides.length - 1
: swiper.slides.length - 1;
}
else if (swiper.isEnd) {
rewindFirstIndex = 0;
}
}
// Find current slide size
const ratio = (currentPos - slidesGrid[stopIndex]) / groupSize;
const increment = stopIndex < params.slidesPerGroupSkip - 1 ? 1 : params.slidesPerGroup;
if (timeDiff > params.longSwipesMs) {
// Long touches
if (!params.longSwipes) {
swiper.slideTo(swiper.activeIndex);
return;
}
if (swiper.swipeDirection === 'next') {
if (ratio >= params.longSwipesRatio)
swiper.slideTo(params.rewind && swiper.isEnd ? rewindFirstIndex : stopIndex + increment);
else
swiper.slideTo(stopIndex);
}
if (swiper.swipeDirection === 'prev') {
if (ratio > 1 - params.longSwipesRatio) {
swiper.slideTo(stopIndex + increment);
}
else if (rewindLastIndex !== null &&
ratio < 0 &&
Math.abs(ratio) > params.longSwipesRatio) {
swiper.slideTo(rewindLastIndex);
}
else {
swiper.slideTo(stopIndex);
}
}
}
else {
// Short swipes
if (!params.shortSwipes) {
swiper.slideTo(swiper.activeIndex);
return;
}
const isNavButtonTarget = swiper.navigation &&
(e.target === swiper.navigation.nextEl || e.target === swiper.navigation.prevEl);
if (!isNavButtonTarget) {
if (swiper.swipeDirection === 'next') {
swiper.slideTo(rewindFirstIndex !== null ? rewindFirstIndex : stopIndex + increment);
}
if (swiper.swipeDirection === 'prev') {
swiper.slideTo(rewindLastIndex !== null ? rewindLastIndex : stopIndex);
}
}
else if (e.target === swiper.navigation.nextEl) {
swiper.slideTo(stopIndex + increment);
}
else {
swiper.slideTo(stopIndex);
}
}
}
function onTouchMove(event) {
const swiper = this;
if (swiper.destroyed)
return;
const data = swiper.touchEventsData;
const { params, touches, rtlTranslate: rtl, enabled } = swiper;
if (!enabled)
return;
if (!params.simulateTouch && event.pointerType === 'mouse')
return;
// Legacy event wrappers nest the native event under .originalEvent.
const wrapped = event;
const e = wrapped.originalEvent ?? wrapped;
if (e.type === 'pointermove') {
if (data.touchId !== null)
return; // return from pointer if we use touch
const pe = e;
if (pe.pointerId !== data.pointerId)
return;
}
let targetTouch;
if (e.type === 'touchmove') {
const te = e;
const found = [...te.changedTouches].find((t) => t.identifier === data.touchId);
if (!found || found.identifier !== data.touchId)
return;
targetTouch = found;
}
else {
targetTouch = e;
}
if (!data.isTouched) {
if (data.startMoving && data.isScrolling) {
swiper.emit('touchMoveOpposite', e);
}
return;
}
const pageX = targetTouch.pageX;
const pageY = targetTouch.pageY;
if (e.preventedByNestedSwiper) {
touches.startX = pageX;
touches.startY = pageY;
return;
}
if (!swiper.allowTouchMove) {
if (!e.target.matches(data.focusableElements)) {
swiper.allowClick = false;
}
if (data.isTouched) {
Object.assign(touches, {
startX: pageX,
startY: pageY,
currentX: pageX,
currentY: pageY,
});
data.touchStartTime = now();
}
return;
}
if (params.touchReleaseOnEdges && !params.loop) {
if (swiper.isVertical()) {
// Vertical
if ((pageY < touches.startY && swiper.translate <= swiper.maxTranslate()) ||
(pageY > touches.startY && swiper.translate >= swiper.minTranslate())) {
data.isTouched = false;
data.isMoved = false;
return;
}
}
else if (rtl &&
((pageX > touches.startX && -swiper.translate <= swiper.maxTranslate()) ||
(pageX < touches.startX && -swiper.translate >= swiper.minTranslate()))) {
return;
}
else if (!rtl &&
((pageX < touches.startX && swiper.translate <= swiper.maxTranslate()) ||
(pageX > touches.startX && swiper.translate >= swiper.minTranslate()))) {
return;
}
}
if (document.activeElement &&
document.activeElement.matches(data.focusableElements) &&
document.activeElement !== e.target &&
e.pointerType !== 'mouse') {
document.activeElement.blur();
}
if (document.activeElement) {
if (e.target === document.activeElement &&
e.target.matches(data.focusableElements)) {
data.isMoved = true;
swiper.allowClick = false;
return;
}
}
if (data.allowTouchCallbacks) {
swiper.emit('touchMove', e);
}
touches.previousX = touches.currentX;
touches.previousY = touches.currentY;
touches.currentX = pageX;
touches.currentY = pageY;
const diffX = touches.currentX - touches.startX;
const diffY = touches.currentY - touches.startY;
if (swiper.params.threshold && Math.sqrt(diffX ** 2 + diffY ** 2) < swiper.params.threshold)
return;
if (typeof data.isScrolling === 'undefined') {
let touchAngle;
if ((swiper.isHorizontal() && touches.currentY === touches.startY) ||
(swiper.isVertical() && touches.currentX === touches.startX)) {
data.isScrolling = false;
}
else {
if (diffX * diffX + diffY * diffY >= 25) {
touchAngle = (Math.atan2(Math.abs(diffY), Math.abs(diffX)) * 180) / Math.PI;
data.isScrolling = swiper.isHorizontal()
? touchAngle > params.touchAngle
: 90 - touchAngle > params.touchAngle;
}
}
}
if (data.isScrolling) {
swiper.emit('touchMoveOpposite', e);
}
if (typeof data.startMoving === 'undefined') {
if (touches.currentX !== touches.startX || touches.currentY !== touches.startY) {
data.startMoving = true;
}
}
if (data.isScrolling || (e.type === 'touchmove' && data.preventTouchMoveFromPointerMove)) {
data.isTouched = false;
return;
}
if (!data.startMoving) {
return;
}
swiper.allowClick = false;
if (!params.cssMode && e.cancelable) {
e.preventDefault();
}
if (params.touchMoveStopPropagation && !params.nested) {
e.stopPropagation();
}
let diff = swiper.isHorizontal() ? diffX : diffY;
let touchesDiff = swiper.isHorizontal()
? touches.currentX - touches.previousX
: touches.currentY - touches.previousY;
if (params.oneWayMovement) {
diff = Math.abs(diff) * (rtl ? 1 : -1);
touchesDiff = Math.abs(touchesDiff) * (rtl ? 1 : -1);
}
touches.diff = diff;
diff *= params.touchRatio;
if (rtl) {
diff = -diff;
touchesDiff = -touchesDiff;
}
const prevTouchesDirection = swiper.touchesDirection;
swiper.swipeDirection = diff > 0 ? 'prev' : 'next';
swiper.touchesDirection = touchesDiff > 0 ? 'prev' : 'next';
const isLoop = swiper.params.loop && !params.cssMode;
const allowLoopFix = (swiper.touchesDirection === 'next' && swiper.allowSlideNext) ||
(swiper.touchesDirection === 'prev' && swiper.allowSlidePrev);
if (!data.isMoved) {
if (isLoop && allowLoopFix) {
swiper.loopFix({ direction: swiper.swipeDirection });
}
data.startTranslate = swiper.getTranslate();
swiper.setTransition(0);
if (swiper.animating) {
const evt = new window.CustomEvent('transitionend', {
bubbles: true,
cancelable: true,
detail: {
bySwiperTouchMove: true,
},
});
swiper.wrapperEl.dispatchEvent(evt);
}
data.allowMomentumBounce = false;
// Grab Cursor
if (params.grabCursor && (swiper.allowSlideNext === true || swiper.allowSlidePrev === true)) {
swiper.setGrabCursor(true);
}
swiper.emit('sliderFirstMove', e);
}
new Date().getTime();
if (params._loopSwapReset !== false &&
data.isMoved &&
data.allowThresholdMove &&
prevTouchesDirection !== swiper.touchesDirection &&
isLoop &&
allowLoopFix &&
Math.abs(diff) >= 1) {
Object.assign(touches, {
startX: pageX,
startY: pageY,
currentX: pageX,
currentY: pageY,
startTranslate: data.currentTranslate,
});
data.loopSwapReset = true;
data.startTranslate = data.currentTranslate;
return;
}
swiper.emit('sliderMove', e);
data.isMoved = true;
// startTranslate is guaranteed to be set by this point (set in onTouchStart-side init).
const startTranslate = data.startTranslate ?? 0;
data.currentTranslate = diff + startTranslate;
let disableParentSwiper = true;
let resistanceRatio = params.resistanceRatio;
if (params.touchReleaseOnEdges) {
resistanceRatio = 0;
}
if (diff > 0) {
if (isLoop &&
allowLoopFix &&
true &&
data.allowThresholdMove &&
data.currentTranslate >
(params.centeredSlides
? swiper.minTranslate() -
swiper.slidesSizesGrid[swiper.activeIndex + 1] -
(params.slidesPerView !== 'auto' &&
swiper.slides.length - params.slidesPerView >= 2
? swiper.slidesSizesGrid[swiper.activeIndex + 1] +
swiper.params.spaceBetween
: 0) -
swiper.params.spaceBetween
: swiper.minTranslate())) {
swiper.loopFix({ direction: 'prev', setTranslate: true, activeSlideIndex: 0 });
}
if (data.currentTranslate > swiper.minTranslate()) {
disableParentSwiper = false;
if (params.resistance) {
data.currentTranslate =
swiper.minTranslate() -
1 +
(-swiper.minTranslate() + startTranslate + diff) ** resistanceRatio;
}
}
}
else if (diff < 0) {
if (isLoop &&
allowLoopFix &&
true &&
data.allowThresholdMove &&
data.currentTranslate <
(params.centeredSlides
? swiper.maxTranslate() +
swiper.slidesSizesGrid[swiper.slidesSizesGrid.length - 1] +
swiper.params.spaceBetween +
(params.slidesPerView !== 'auto' &&
swiper.slides.length - params.slidesPerView >= 2
? swiper.slidesSizesGrid[swiper.slidesSizesGrid.length - 1] +
swiper.params.spaceBetween
: 0)
: swiper.maxTranslate())) {
swiper.loopFix({
direction: 'next',
setTranslate: true,
activeSlideIndex: swiper.slides.length -
(params.slidesPerView === 'auto'
? swiper.slidesPerViewDynamic()
: Math.ceil(parseFloat(String(params.slidesPerView)))),
});
}
if (data.currentTranslate < swiper.maxTranslate()) {
disableParentSwiper = false;
if (params.resistance) {
data.currentTranslate =
swiper.maxTranslate() +
1 -
(swiper.maxTranslate() - startTranslate - diff) ** resistanceRatio;
}
}
}
if (disableParentSwiper) {
e.preventedByNestedSwiper = true;
}
// Directions locks
if (!swiper.allowSlideNext &&
swiper.swipeDirection === 'next' &&
(data.currentTranslate ?? 0) < startTranslate) {
data.currentTranslate = startTranslate;
}
if (!swiper.allowSlidePrev &&
swiper.swipeDirection === 'prev' &&
(data.currentTranslate ?? 0) > startTranslate) {
data.currentTranslate = startTranslate;
}
if (!swiper.allowSlidePrev && !swiper.allowSlideNext) {
data.currentTranslate = startTranslate;
}
// Threshold
if (params.threshold > 0) {
if (Math.abs(diff) > params.threshold || data.allowThresholdMove) {
if (!data.allowThresholdMove) {
data.allowThresholdMove = true;
touches.startX = touches.currentX;
touches.startY = touches.currentY;
data.currentTranslate = data.startTranslate;
touches.diff = swiper.isHorizontal()
? touches.currentX - touches.startX
: touches.currentY - touches.startY;
return;
}
}
else {
data.currentTranslate = data.startTranslate;
return;
}
}
if (!params.followFinger || params.cssMode)
return;
// Update active index in free mode
if ((params.freeMode && params.freeMode.enabled && swiper.freeMode) ||
params.watchSlidesProgress) {
swiper.updateActiveIndex();
swiper.updateSlidesClasses();
}
if (params.freeMode && params.freeMode.enabled && swiper.freeMode) {
swiper.freeMode.onTouchMove();
}
// Update progress
swiper.updateProgress(data.currentTranslate);
// Update translate
swiper.setTranslate(data.currentTranslate ?? 0);
}
// Modified from https://stackoverflow.com/questions/54520554/custom-element-getrootnode-closest-function-crossing-multiple-parent-shadowd
function closestElement(selector, base) {
function __closestFrom(el) {
if (!el || el === document || el === window)
return null;
let cur = el;
if (cur.assignedSlot)
cur = cur.assignedSlot;
const found = cur.closest(selector);
if (!found && !cur.getRootNode) {
return null;
}
const root = cur.getRootNode();
return found || __closestFrom(root.host);
}
return __closestFrom(base);
}
function preventEdgeSwipe(swiper, event, startX) {
const { params } = swiper;
const edgeSwipeDetection = params.edgeSwipeDetection;
const edgeSwipeThreshold = params.edgeSwipeThreshold;
if (edgeSwipeDetection &&
(startX <= edgeSwipeThreshold || startX >= window.innerWidth - edgeSwipeThreshold)) {
if (edgeSwipeDetection === 'prevent') {
event.preventDefault();
return true;
}
return false;
}
return true;
}
function onTouchStart(event) {
const swiper = this;
if (swiper.destroyed)
return;
const e = event.originalEvent ?? event;
const data = swiper.touchEventsData;
if (e.type === 'pointerdown') {
const pe = e;
if (data.pointerId !== null && data.pointerId !== pe.pointerId) {
return;
}
data.pointerId = pe.pointerId;
}
else if (e.type === 'touchstart' && e.targetTouches.length === 1) {
data.touchId = e.targetTouches[0].identifier;
}
if (e.type === 'touchstart') {
// don't proceed touch event
preventEdgeSwipe(swiper, e, e.targetTouches[0].pageX);
return;
}
const { params, touches, enabled } = swiper;
if (!enabled)
return;
if (!params.simulateTouch && e.pointerType === 'mouse')
return;
if (swiper.animating && params.preventInteractionOnTransition) {
return;
}
if (!swiper.animating && params.cssMode && params.loop) {
swiper.loopFix();
}
let targetEl = e.target;
if (params.touchEventsTarget === 'wrapper') {
if (!elementIsChildOf(targetEl, swiper.wrapperEl))
return;
}
// Secondary mouse buttons (right-click / middle-click) shouldn't start a swipe.
const mouseLike = e;
if (typeof mouseLike.which === 'number' && mouseLike.which === 3)
return;
if (typeof mouseLike.button === 'number' && mouseLike.button > 0)
return;
if (data.isTouched && data.isMoved)
return;
// change target el for shadow root component
const swipingClassHasValue = !!params.noSwipingClass && params.noSwipingClass !== '';
// `path` is a non-standard Chrome extension; `composedPath()` is the modern API.
const eventPath = e.composedPath
? e.composedPath()
: e.path;
if (swipingClassHasValue &&
e.target &&
e.target.shadowRoot &&
eventPath) {
targetEl = eventPath[0];
}
const noSwipingSelector = params.noSwipingSelector
? params.noSwipingSelector
: `.${params.noSwipingClass}`;
const isTargetShadow = !!(e.target && e.target.shadowRoot);
// use closestElement for shadow root element to get the actual closest for nested shadow root element
if (params.noSwiping &&
(isTargetShadow
? closestElement(noSwipingSelector, targetEl)
: targetEl.closest(noSwipingSelector))) {
swiper.allowClick = true;
return;
}
if (params.swipeHandler) {
if (typeof params.swipeHandler === 'string' && !targetEl.closest(params.swipeHandler))
return;
}
// At this point `e` is a PointerEvent or MouseEvent (touchstart returned earlier).
const pe = e;
touches.currentX = pe.pageX;
touches.currentY = pe.pageY;
const startX = touches.currentX;
const startY = touches.currentY;
// Do NOT start if iOS edge swipe is detected. Otherwise iOS app cannot swipe-to-go-back anymore
if (!preventEdgeSwipe(swiper, e, startX)) {
return;
}
Object.assign(data, {
isTouched: true,
isMoved: false,
allowTouchCallbacks: true,
isScrolling: undefined,
startMoving: undefined,
});
touches.startX = startX;
touches.startY = startY;
data.touchStartTime = now();
swiper.allowClick = true;
swiper.updateSize();
swiper.swipeDirection = undefined;
if (params.threshold > 0)
data.allowThresholdMove = false;
let preventDefault = true;
if (targetEl.matches(data.focusableElements)) {
preventDefault = false;
if (targetEl.nodeName === 'SELECT') {
data.isTouched = false;
}
}
if (document.activeElement &&
document.activeElement.matches(data.focusableElements) &&
document.activeElement !== targetEl &&
(pe.pointerType === 'mouse' ||
(pe.pointerType !== 'mouse' && !targetEl.matches(data.focusableElements)))) {
document.activeElement.blur();
}
const shouldPreventDefault = preventDefault && swiper.allowTouchMove && params.touchStartPreventDefault;
if ((params.touchStartForcePreventDefault || shouldPreventDefault) &&
!targetEl.isContentEditable) {
e.preventDefault();
}
if (params.freeMode &&
params.freeMode.enabled &&
swiper.freeMode &&
swiper.animating &&
!params.cssMode) {
swiper.freeMode.onTouchStart();
}
swiper.emit('touchStart', e);
}
const events = (swiper, method) => {
const { params, el, wrapperEl, device } = swiper;
const capture = !!params.nested;
const domMethod = method === 'on' ? 'addEventListener' : 'removeEventListener';
const swiperMethod = method;
if (!el || typeof el === 'string')
return;
// Touch Events
document[domMethod]('touchstart', swiper.onDocumentTouchStart, {
passive: false,
capture,
});
el[domMethod]('touchstart', swiper.onTouchStart, { passive: false });
el[domMethod]('pointerdown', swiper.onTouchStart, { passive: false });
document[domMethod]('touchmove', swiper.onTouchMove, {
passive: false,
capture,
});
document[domMethod]('pointermove', swiper.onTouchMove, {
passive: false,
capture,
});
document[domMethod]('touchend', swiper.onTouchEnd, { passive: true });
document[domMethod]('pointerup', swiper.onTouchEnd, { passive: true });
document[domMethod]('pointercancel', swiper.onTouchEnd, { passive: true });
document[domMethod]('touchcancel', swiper.onTouchEnd, { passive: true