/* eslint no-param-reassign: "off" */ import { getDocument } from 'ssr-window'; import { extend, deleteProps, createElement, elementChildren, elementStyle, elementIndex } from '../shared/utils.js'; import { getSupport } from '../shared/get-support.js'; import { getDevice } from '../shared/get-device.js'; import { getBrowser } from '../shared/get-browser.js'; import Resize from './modules/resize/resize.js'; import Observer from './modules/observer/observer.js'; import eventsEmitter from './events-emitter.js'; import update from './update/index.js'; import translate from './translate/index.js'; import transition from './transition/index.js'; import slide from './slide/index.js'; import loop from './loop/index.js'; import grabCursor from './grab-cursor/index.js'; import events from './events/index.js'; import breakpoints from './breakpoints/index.js'; import classes from './classes/index.js'; import checkOverflow from './check-overflow/index.js'; import defaults from './defaults.js'; import moduleExtendParams from './moduleExtendParams.js'; import { processLazyPreloader, preload } from '../shared/process-lazy-preloader.js'; const prototypes = { eventsEmitter, update, translate, transition, slide, loop, grabCursor, events, breakpoints, checkOverflow, classes }; const extendedDefaults = {}; class Swiper { constructor(...args) { let el; let params; if (args.length === 1 && args[0].constructor && Object.prototype.toString.call(args[0]).slice(8, -1) === 'Object') { params = args[0]; } else { [el, params] = args; } if (!params) params = {}; params = extend({}, params); if (el && !params.el) params.el = el; const document = getDocument(); if (params.el && typeof params.el === 'string' && document.querySelectorAll(params.el).length > 1) { const swipers = []; document.querySelectorAll(params.el).forEach(containerEl => { const newParams = extend({}, params, { el: containerEl }); swipers.push(new Swiper(newParams)); }); // eslint-disable-next-line no-constructor-return return swipers; } // Swiper Instance const swiper = this; swiper.__swiper__ = true; swiper.support = getSupport(); swiper.device = getDevice({ userAgent: params.userAgent }); swiper.browser = getBrowser(); swiper.eventsListeners = {}; swiper.eventsAnyListeners = []; swiper.modules = [...swiper.__modules__]; if (params.modules && Array.isArray(params.modules)) { swiper.modules.push(...params.modules); } const allModulesParams = {}; swiper.modules.forEach(mod => { mod({ params, swiper, extendParams: moduleExtendParams(params, allModulesParams), on: swiper.on.bind(swiper), once: swiper.once.bind(swiper), off: swiper.off.bind(swiper), emit: swiper.emit.bind(swiper) }); }); // Extend defaults with modules params const swiperParams = extend({}, defaults, allModulesParams); // Extend defaults with passed params swiper.params = extend({}, swiperParams, extendedDefaults, params); swiper.originalParams = extend({}, swiper.params); swiper.passedParams = extend({}, params); // add event listeners if (swiper.params && swiper.params.on) { Object.keys(swiper.params.on).forEach(eventName => { swiper.on(eventName, swiper.params.on[eventName]); }); } if (swiper.params && swiper.params.onAny) { swiper.onAny(swiper.params.onAny); } // Extend Swiper Object.assign(swiper, { enabled: swiper.params.enabled, el, // Classes classNames: [], // Slides slides: [], slidesGrid: [], snapGrid: [], slidesSizesGrid: [], // isDirection isHorizontal() { return swiper.params.direction === 'horizontal'; }, isVertical() { return swiper.params.direction === 'vertical'; }, // Indexes activeIndex: 0, realIndex: 0, // isBeginning: true, isEnd: false, // Props translate: 0, previousTranslate: 0, progress: 0, velocity: 0, animating: false, cssOverflowAdjustment() { // Returns 0 unless `translate` is > 2**23 // Should be subtracted from css values to prevent overflow return Math.trunc(this.translate / 2 ** 23) * 2 ** 23; }, // Locks allowSlideNext: swiper.params.allowSlideNext, allowSlidePrev: swiper.params.allowSlidePrev, // Touch Events touchEventsData: { isTouched: undefined, isMoved: undefined, allowTouchCallbacks: undefined, touchStartTime: undefined, isScrolling: undefined, currentTranslate: undefined, startTranslate: undefined, allowThresholdMove: undefined, // Form elements to match focusableElements: swiper.params.focusableElements, // Last click time lastClickTime: 0, clickTimeout: undefined, // Velocities velocities: [], allowMomentumBounce: undefined, startMoving: undefined, evCache: [] }, // Clicks allowClick: true, // Touches allowTouchMove: swiper.params.allowTouchMove, touches: { startX: 0, startY: 0, currentX: 0, currentY: 0, diff: 0 }, // Images imagesToLoad: [], imagesLoaded: 0 }); swiper.emit('_swiper'); // Init if (swiper.params.init) { swiper.init(); } // Return app instance // eslint-disable-next-line no-constructor-return return swiper; } getSlideIndex(slideEl) { const { slidesEl, params } = this; const slides = elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`); const firstSlideIndex = elementIndex(slides[0]); return elementIndex(slideEl) - firstSlideIndex; } getSlideIndexByData(index) { return this.getSlideIndex(this.slides.filter(slideEl => slideEl.getAttribute('data-swiper-slide-index') * 1 === index)[0]); } recalcSlides() { const swiper = this; const { slidesEl, params } = swiper; swiper.slides = elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`); } enable() { const swiper = this; if (swiper.enabled) return; swiper.enabled = true; if (swiper.params.grabCursor) { swiper.setGrabCursor(); } swiper.emit('enable'); } disable() { const swiper = this; if (!swiper.enabled) return; swiper.enabled = false; if (swiper.params.grabCursor) { swiper.unsetGrabCursor(); } swiper.emit('disable'); } setProgress(progress, speed) { const swiper = this; progress = Math.min(Math.max(progress, 0), 1); const min = swiper.minTranslate(); const max = swiper.maxTranslate(); const current = (max - min) * progress + min; swiper.translateTo(current, typeof speed === 'undefined' ? 0 : speed); swiper.updateActiveIndex(); swiper.updateSlidesClasses(); } emitContainerClasses() { const swiper = this; if (!swiper.params._emitClasses || !swiper.el) return; const cls = swiper.el.className.split(' ').filter(className => { return className.indexOf('swiper') === 0 || className.indexOf(swiper.params.containerModifierClass) === 0; }); swiper.emit('_containerClasses', cls.join(' ')); } getSlideClasses(slideEl) { const swiper = this; if (swiper.destroyed) return ''; return slideEl.className.split(' ').filter(className => { return className.indexOf('swiper-slide') === 0 || className.indexOf(swiper.params.slideClass) === 0; }).join(' '); } emitSlidesClasses() { const swiper = this; if (!swiper.params._emitClasses || !swiper.el) return; const updates = []; swiper.slides.forEach(slideEl => { const classNames = swiper.getSlideClasses(slideEl); updates.push({ slideEl, classNames }); swiper.emit('_slideClass', slideEl, classNames); }); swiper.emit('_slideClasses', updates); } slidesPerViewDynamic(view = 'current', exact = false) { const swiper = this; const { params, slides, slidesGrid, slidesSizesGrid, size: swiperSize, activeIndex } = swiper; let spv = 1; if (params.centeredSlides) { let slideSize = slides[activeIndex] ? slides[activeIndex].swiperSlideSize : 0; let breakLoop; for (let i = activeIndex + 1; i < slides.length; i += 1) { if (slides[i] && !breakLoop) { slideSize += slides[i].swiperSlideSize; spv += 1; if (slideSize > swiperSize) breakLoop = true; } } for (let i = activeIndex - 1; i >= 0; i -= 1) { if (slides[i] && !breakLoop) { slideSize += slides[i].swiperSlideSize; spv += 1; if (slideSize > swiperSize) breakLoop = true; } } } else { // eslint-disable-next-line if (view === 'current') { for (let i = activeIndex + 1; i < slides.length; i += 1) { const slideInView = exact ? slidesGrid[i] + slidesSizesGrid[i] - slidesGrid[activeIndex] < swiperSize : slidesGrid[i] - slidesGrid[activeIndex] < swiperSize; if (slideInView) { spv += 1; } } } else { // previous for (let i = activeIndex - 1; i >= 0; i -= 1) { const slideInView = slidesGrid[activeIndex] - slidesGrid[i] < swiperSize; if (slideInView) { spv += 1; } } } } return spv; } update() { const swiper = this; if (!swiper || swiper.destroyed) return; const { snapGrid, params } = swiper; // Breakpoints if (params.breakpoints) { swiper.setBreakpoint(); } [...swiper.el.querySelectorAll('[loading="lazy"]')].forEach(imageEl => { if (imageEl.complete) { processLazyPreloader(swiper, imageEl); } }); swiper.updateSize(); swiper.updateSlides(); swiper.updateProgress(); swiper.updateSlidesClasses(); function setTranslate() { const translateValue = swiper.rtlTranslate ? swiper.translate * -1 : swiper.translate; const newTranslate = Math.min(Math.max(translateValue, swiper.maxTranslate()), swiper.minTranslate()); swiper.setTranslate(newTranslate); swiper.updateActiveIndex(); swiper.updateSlidesClasses(); } let translated; if (params.freeMode && params.freeMode.enabled && !params.cssMode) { setTranslate(); if (params.autoHeight) { swiper.updateAutoHeight(); } } else { if ((params.slidesPerView === 'auto' || params.slidesPerView > 1) && swiper.isEnd && !params.centeredSlides) { const slides = swiper.virtual && params.virtual.enabled ? swiper.virtual.slides : swiper.slides; translated = swiper.slideTo(slides.length - 1, 0, false, true); } else { translated = swiper.slideTo(swiper.activeIndex, 0, false, true); } if (!translated) { setTranslate(); } } if (params.watchOverflow && snapGrid !== swiper.snapGrid) { swiper.checkOverflow(); } swiper.emit('update'); } changeDirection(newDirection, needUpdate = true) { const swiper = this; const currentDirection = swiper.params.direction; if (!newDirection) { // eslint-disable-next-line newDirection = currentDirection === 'horizontal' ? 'vertical' : 'horizontal'; } if (newDirection === currentDirection || newDirection !== 'horizontal' && newDirection !== 'vertical') { return swiper; } swiper.el.classList.remove(`${swiper.params.containerModifierClass}${currentDirection}`); swiper.el.classList.add(`${swiper.params.containerModifierClass}${newDirection}`); swiper.emitContainerClasses(); swiper.params.direction = newDirection; swiper.slides.forEach(slideEl => { if (newDirection === 'vertical') { slideEl.style.width = ''; } else { slideEl.style.height = ''; } }); swiper.emit('changeDirection'); if (needUpdate) swiper.update(); return swiper; } changeLanguageDirection(direction) { const swiper = this; if (swiper.rtl && direction === 'rtl' || !swiper.rtl && direction === 'ltr') return; swiper.rtl = direction === 'rtl'; swiper.rtlTranslate = swiper.params.direction === 'horizontal' && swiper.rtl; if (swiper.rtl) { swiper.el.classList.add(`${swiper.params.containerModifierClass}rtl`); swiper.el.dir = 'rtl'; } else { swiper.el.classList.remove(`${swiper.params.containerModifierClass}rtl`); swiper.el.dir = 'ltr'; } swiper.update(); } mount(element) { const swiper = this; if (swiper.mounted) return true; // Find el let el = element || swiper.params.el; if (typeof el === 'string') { el = document.querySelector(el); } if (!el) { return false; } el.swiper = swiper; if (el.shadowEl) { swiper.isElement = true; } const getWrapperSelector = () => { return `.${(swiper.params.wrapperClass || '').trim().split(' ').join('.')}`; }; const getWrapper = () => { if (el && el.shadowRoot && el.shadowRoot.querySelector) { const res = el.shadowRoot.querySelector(getWrapperSelector()); // Children needs to return slot items return res; } return elementChildren(el, getWrapperSelector())[0]; }; // Find Wrapper let wrapperEl = getWrapper(); if (!wrapperEl && swiper.params.createElements) { wrapperEl = createElement('div', swiper.params.wrapperClass); el.append(wrapperEl); elementChildren(el, `.${swiper.params.slideClass}`).forEach(slideEl => { wrapperEl.append(slideEl); }); } Object.assign(swiper, { el, wrapperEl, slidesEl: swiper.isElement ? el : wrapperEl, mounted: true, // RTL rtl: el.dir.toLowerCase() === 'rtl' || elementStyle(el, 'direction') === 'rtl', rtlTranslate: swiper.params.direction === 'horizontal' && (el.dir.toLowerCase() === 'rtl' || elementStyle(el, 'direction') === 'rtl'), wrongRTL: elementStyle(wrapperEl, 'display') === '-webkit-box' }); return true; } init(el) { const swiper = this; if (swiper.initialized) return swiper; const mounted = swiper.mount(el); if (mounted === false) return swiper; swiper.emit('beforeInit'); // Set breakpoint if (swiper.params.breakpoints) { swiper.setBreakpoint(); } // Add Classes swiper.addClasses(); // Update size swiper.updateSize(); // Update slides swiper.updateSlides(); if (swiper.params.watchOverflow) { swiper.checkOverflow(); } // Set Grab Cursor if (swiper.params.grabCursor && swiper.enabled) { swiper.setGrabCursor(); } // Slide To Initial Slide if (swiper.params.loop && swiper.virtual && swiper.params.virtual.enabled) { swiper.slideTo(swiper.params.initialSlide + swiper.virtual.slidesBefore, 0, swiper.params.runCallbacksOnInit, false, true); } else { swiper.slideTo(swiper.params.initialSlide, 0, swiper.params.runCallbacksOnInit, false, true); } // Create loop if (swiper.params.loop) { swiper.loopCreate(); } // Attach events swiper.attachEvents(); [...swiper.el.querySelectorAll('[loading="lazy"]')].forEach(imageEl => { if (imageEl.complete) { processLazyPreloader(swiper, imageEl); } else { imageEl.addEventListener('load', e => { processLazyPreloader(swiper, e.target); }); } }); preload(swiper); // Init Flag swiper.initialized = true; preload(swiper); // Emit swiper.emit('init'); swiper.emit('afterInit'); return swiper; } destroy(deleteInstance = true, cleanStyles = true) { const swiper = this; const { params, el, wrapperEl, slides } = swiper; if (typeof swiper.params === 'undefined' || swiper.destroyed) { return null; } swiper.emit('beforeDestroy'); // Init Flag swiper.initialized = false; // Detach events swiper.detachEvents(); // Destroy loop if (params.loop) { swiper.loopDestroy(); } // Cleanup styles if (cleanStyles) { swiper.removeClasses(); el.removeAttribute('style'); wrapperEl.removeAttribute('style'); if (slides && slides.length) { slides.forEach(slideEl => { slideEl.classList.remove(params.slideVisibleClass, params.slideActiveClass, params.slideNextClass, params.slidePrevClass); slideEl.removeAttribute('style'); slideEl.removeAttribute('data-swiper-slide-index'); }); } } swiper.emit('destroy'); // Detach emitter events Object.keys(swiper.eventsListeners).forEach(eventName => { swiper.off(eventName); }); if (deleteInstance !== false) { swiper.el.swiper = null; deleteProps(swiper); } swiper.destroyed = true; return null; } static extendDefaults(newDefaults) { extend(extendedDefaults, newDefaults); } static get extendedDefaults() { return extendedDefaults; } static get defaults() { return defaults; } static installModule(mod) { if (!Swiper.prototype.__modules__) Swiper.prototype.__modules__ = []; const modules = Swiper.prototype.__modules__; if (typeof mod === 'function' && modules.indexOf(mod) < 0) { modules.push(mod); } } static use(module) { if (Array.isArray(module)) { module.forEach(m => Swiper.installModule(m)); return Swiper; } Swiper.installModule(module); return Swiper; } } Object.keys(prototypes).forEach(prototypeGroup => { Object.keys(prototypes[prototypeGroup]).forEach(protoMethod => { Swiper.prototype[protoMethod] = prototypes[prototypeGroup][protoMethod]; }); }); Swiper.use([Resize, Observer]); export default Swiper;