UNPKG

swiper

Version:

Most modern mobile touch slider and framework with hardware accelerated transitions

1,316 lines (1,293 loc) 472 kB
/** * Swiper Custom Element 14.0.0 * Most modern mobile touch slider and framework with hardware accelerated transitions * https://swiperjs.com * * Copyright 2014-2026 Vladimir Kharlampidi * * Released under the MIT License * * Released on: June 26, 2026 */ (function () { 'use strict'; function classesToTokens(classes = '') { return classes .trim() .split(' ') .filter((c) => !!c.trim()); } function deleteProps(obj) { Object.keys(obj).forEach((key) => { try { obj[key] = null; } catch { // no getter for object } try { delete obj[key]; } catch { // something got wrong } }); } function nextTick(callback, delay = 0) { return setTimeout(callback, delay); } function now() { return Date.now(); } function getComputedStyle$1(el) { return window.getComputedStyle(el, null); } function getTranslate(el, axis = 'x') { const style = getComputedStyle$1(el); const transform = style.transform || style.webkitTransform; if (!transform || transform === 'none') return 0; const matrix = new DOMMatrixReadOnly(transform); return axis === 'x' ? matrix.m41 : matrix.m42; } function isObject$1(o) { return (typeof o === 'object' && o !== null && o.constructor === Object && Object.prototype.toString.call(o).slice(8, -1) === 'Object'); } function isNode(node) { if (typeof HTMLElement !== 'undefined' && node instanceof HTMLElement) return true; return (!!node && typeof node === 'object' && (node.nodeType === 1 || node.nodeType === 11)); } function extend$1(target, ...sources) { const to = Object(target); for (let i = 0; i < sources.length; i += 1) { const nextSource = sources[i]; if (nextSource === undefined || nextSource === null || isNode(nextSource)) continue; const sourceObj = nextSource; const keysArray = Object.keys(Object(sourceObj)).filter((key) => key !== '__proto__' && key !== 'constructor' && key !== 'prototype'); for (const nextKey of keysArray) { const desc = Object.getOwnPropertyDescriptor(sourceObj, nextKey); if (!desc || !desc.enumerable) continue; const sourceVal = sourceObj[nextKey]; if (isObject$1(to[nextKey]) && isObject$1(sourceVal)) { if (sourceVal.__swiper__) { to[nextKey] = sourceVal; } else { extend$1(to[nextKey], sourceVal); } } else if (!isObject$1(to[nextKey]) && isObject$1(sourceVal)) { to[nextKey] = {}; if (sourceVal.__swiper__) { to[nextKey] = sourceVal; } else { extend$1(to[nextKey], sourceVal); } } else { to[nextKey] = sourceVal; } } } return to; } function setCSSProperty(el, varName, varValue) { el.style.setProperty(varName, varValue); } function getSlideTransformEl(slideEl) { const direct = slideEl.querySelector('.swiper-slide-transform'); if (direct) return direct; if (slideEl.shadowRoot) { const shadowed = slideEl.shadowRoot.querySelector('.swiper-slide-transform'); if (shadowed) return shadowed; } return slideEl; } function elementChildren(element, selector = '') { const children = [...element.children]; if (element instanceof HTMLSlotElement) { children.push(...element.assignedElements()); } return selector ? children.filter((el) => el.matches(selector)) : children; } function elementIsChildOfSlot(el, slot) { const queue = [slot]; while (queue.length > 0) { const cur = queue.shift(); if (el === cur) return true; queue.push(...cur.children, ...(cur.shadowRoot ? cur.shadowRoot.children : []), ...(cur.assignedElements ? cur.assignedElements() : [])); } return false; } function elementIsChildOf(el, parent) { let isChild = parent.contains(el); if (!isChild && parent instanceof HTMLSlotElement) { const children = [...parent.assignedElements()]; isChild = children.includes(el); if (!isChild) isChild = elementIsChildOfSlot(el, parent); } return isChild; } function showWarning(text) { try { console.warn(text); } catch { // err } } function createElement(tag, classes = []) { const el = document.createElement(tag); el.classList.add(...(Array.isArray(classes) ? classes : classesToTokens(classes))); return el; } function elementOffset(el) { const box = el.getBoundingClientRect(); return { top: box.top + window.scrollY - (el.clientTop || 0), left: box.left + window.scrollX - (el.clientLeft || 0), }; } function elementPrevAll(el, selector) { const prevEls = []; let prev = el.previousElementSibling; while (prev) { if (!selector || prev.matches(selector)) prevEls.push(prev); prev = prev.previousElementSibling; } return prevEls; } function elementNextAll(el, selector) { const nextEls = []; let next = el.nextElementSibling; while (next) { if (!selector || next.matches(selector)) nextEls.push(next); next = next.nextElementSibling; } return nextEls; } function elementStyle(el, prop) { return window.getComputedStyle(el, null).getPropertyValue(prop); } function elementIndex(el) { if (!el || !el.parentNode) return undefined; return [...el.parentNode.children].indexOf(el); } function elementParents(el, selector) { const parents = []; let parent = el.parentElement; while (parent) { if (!selector || parent.matches(selector)) parents.push(parent); parent = parent.parentElement; } return parents; } function elementTransitionEnd(el, callback) { if (!callback) return; el.addEventListener('transitionend', function fireCallBack(e) { if (e.target !== el) return; callback.call(el, e); }, { once: true }); } function elementOuterSize(el, size, includeMargins) { { const style = window.getComputedStyle(el, null); return (el[size === 'width' ? 'offsetWidth' : 'offsetHeight'] + parseFloat(style.getPropertyValue(size === 'width' ? 'margin-right' : 'margin-top')) + parseFloat(style.getPropertyValue(size === 'width' ? 'margin-left' : 'margin-bottom'))); } } function makeElementsArray(el) { return (Array.isArray(el) ? el : [el]).filter((e) => !!e); } function getRotateFix(swiper) { return (v) => { if (Math.abs(v) > 0 && swiper.browser && swiper.browser.need3dFix && Math.abs(v) % 90 === 0) { return v + 0.001; } return v; }; } function setInnerHTML(el, html = '') { const tt = globalThis.trustedTypes; if (typeof tt !== 'undefined') { el.innerHTML = tt.createPolicy('html', { createHTML: (s) => s }).createHTML(html); } else { el.innerHTML = html; } } 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$1(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.touchesDire