core.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  1. /* eslint no-param-reassign: "off" */
  2. import { getDocument } from 'ssr-window';
  3. import { extend, deleteProps, createElement, elementChildren, elementStyle, elementIndex } from '../shared/utils.js';
  4. import { getSupport } from '../shared/get-support.js';
  5. import { getDevice } from '../shared/get-device.js';
  6. import { getBrowser } from '../shared/get-browser.js';
  7. import Resize from './modules/resize/resize.js';
  8. import Observer from './modules/observer/observer.js';
  9. import eventsEmitter from './events-emitter.js';
  10. import update from './update/index.js';
  11. import translate from './translate/index.js';
  12. import transition from './transition/index.js';
  13. import slide from './slide/index.js';
  14. import loop from './loop/index.js';
  15. import grabCursor from './grab-cursor/index.js';
  16. import events from './events/index.js';
  17. import breakpoints from './breakpoints/index.js';
  18. import classes from './classes/index.js';
  19. import checkOverflow from './check-overflow/index.js';
  20. import defaults from './defaults.js';
  21. import moduleExtendParams from './moduleExtendParams.js';
  22. import { processLazyPreloader, preload } from '../shared/process-lazy-preloader.js';
  23. const prototypes = {
  24. eventsEmitter,
  25. update,
  26. translate,
  27. transition,
  28. slide,
  29. loop,
  30. grabCursor,
  31. events,
  32. breakpoints,
  33. checkOverflow,
  34. classes
  35. };
  36. const extendedDefaults = {};
  37. class Swiper {
  38. constructor(...args) {
  39. let el;
  40. let params;
  41. if (args.length === 1 && args[0].constructor && Object.prototype.toString.call(args[0]).slice(8, -1) === 'Object') {
  42. params = args[0];
  43. } else {
  44. [el, params] = args;
  45. }
  46. if (!params) params = {};
  47. params = extend({}, params);
  48. if (el && !params.el) params.el = el;
  49. const document = getDocument();
  50. if (params.el && typeof params.el === 'string' && document.querySelectorAll(params.el).length > 1) {
  51. const swipers = [];
  52. document.querySelectorAll(params.el).forEach(containerEl => {
  53. const newParams = extend({}, params, {
  54. el: containerEl
  55. });
  56. swipers.push(new Swiper(newParams));
  57. });
  58. // eslint-disable-next-line no-constructor-return
  59. return swipers;
  60. }
  61. // Swiper Instance
  62. const swiper = this;
  63. swiper.__swiper__ = true;
  64. swiper.support = getSupport();
  65. swiper.device = getDevice({
  66. userAgent: params.userAgent
  67. });
  68. swiper.browser = getBrowser();
  69. swiper.eventsListeners = {};
  70. swiper.eventsAnyListeners = [];
  71. swiper.modules = [...swiper.__modules__];
  72. if (params.modules && Array.isArray(params.modules)) {
  73. swiper.modules.push(...params.modules);
  74. }
  75. const allModulesParams = {};
  76. swiper.modules.forEach(mod => {
  77. mod({
  78. params,
  79. swiper,
  80. extendParams: moduleExtendParams(params, allModulesParams),
  81. on: swiper.on.bind(swiper),
  82. once: swiper.once.bind(swiper),
  83. off: swiper.off.bind(swiper),
  84. emit: swiper.emit.bind(swiper)
  85. });
  86. });
  87. // Extend defaults with modules params
  88. const swiperParams = extend({}, defaults, allModulesParams);
  89. // Extend defaults with passed params
  90. swiper.params = extend({}, swiperParams, extendedDefaults, params);
  91. swiper.originalParams = extend({}, swiper.params);
  92. swiper.passedParams = extend({}, params);
  93. // add event listeners
  94. if (swiper.params && swiper.params.on) {
  95. Object.keys(swiper.params.on).forEach(eventName => {
  96. swiper.on(eventName, swiper.params.on[eventName]);
  97. });
  98. }
  99. if (swiper.params && swiper.params.onAny) {
  100. swiper.onAny(swiper.params.onAny);
  101. }
  102. // Extend Swiper
  103. Object.assign(swiper, {
  104. enabled: swiper.params.enabled,
  105. el,
  106. // Classes
  107. classNames: [],
  108. // Slides
  109. slides: [],
  110. slidesGrid: [],
  111. snapGrid: [],
  112. slidesSizesGrid: [],
  113. // isDirection
  114. isHorizontal() {
  115. return swiper.params.direction === 'horizontal';
  116. },
  117. isVertical() {
  118. return swiper.params.direction === 'vertical';
  119. },
  120. // Indexes
  121. activeIndex: 0,
  122. realIndex: 0,
  123. //
  124. isBeginning: true,
  125. isEnd: false,
  126. // Props
  127. translate: 0,
  128. previousTranslate: 0,
  129. progress: 0,
  130. velocity: 0,
  131. animating: false,
  132. cssOverflowAdjustment() {
  133. // Returns 0 unless `translate` is > 2**23
  134. // Should be subtracted from css values to prevent overflow
  135. return Math.trunc(this.translate / 2 ** 23) * 2 ** 23;
  136. },
  137. // Locks
  138. allowSlideNext: swiper.params.allowSlideNext,
  139. allowSlidePrev: swiper.params.allowSlidePrev,
  140. // Touch Events
  141. touchEventsData: {
  142. isTouched: undefined,
  143. isMoved: undefined,
  144. allowTouchCallbacks: undefined,
  145. touchStartTime: undefined,
  146. isScrolling: undefined,
  147. currentTranslate: undefined,
  148. startTranslate: undefined,
  149. allowThresholdMove: undefined,
  150. // Form elements to match
  151. focusableElements: swiper.params.focusableElements,
  152. // Last click time
  153. lastClickTime: 0,
  154. clickTimeout: undefined,
  155. // Velocities
  156. velocities: [],
  157. allowMomentumBounce: undefined,
  158. startMoving: undefined,
  159. evCache: []
  160. },
  161. // Clicks
  162. allowClick: true,
  163. // Touches
  164. allowTouchMove: swiper.params.allowTouchMove,
  165. touches: {
  166. startX: 0,
  167. startY: 0,
  168. currentX: 0,
  169. currentY: 0,
  170. diff: 0
  171. },
  172. // Images
  173. imagesToLoad: [],
  174. imagesLoaded: 0
  175. });
  176. swiper.emit('_swiper');
  177. // Init
  178. if (swiper.params.init) {
  179. swiper.init();
  180. }
  181. // Return app instance
  182. // eslint-disable-next-line no-constructor-return
  183. return swiper;
  184. }
  185. getSlideIndex(slideEl) {
  186. const {
  187. slidesEl,
  188. params
  189. } = this;
  190. const slides = elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`);
  191. const firstSlideIndex = elementIndex(slides[0]);
  192. return elementIndex(slideEl) - firstSlideIndex;
  193. }
  194. getSlideIndexByData(index) {
  195. return this.getSlideIndex(this.slides.filter(slideEl => slideEl.getAttribute('data-swiper-slide-index') * 1 === index)[0]);
  196. }
  197. recalcSlides() {
  198. const swiper = this;
  199. const {
  200. slidesEl,
  201. params
  202. } = swiper;
  203. swiper.slides = elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`);
  204. }
  205. enable() {
  206. const swiper = this;
  207. if (swiper.enabled) return;
  208. swiper.enabled = true;
  209. if (swiper.params.grabCursor) {
  210. swiper.setGrabCursor();
  211. }
  212. swiper.emit('enable');
  213. }
  214. disable() {
  215. const swiper = this;
  216. if (!swiper.enabled) return;
  217. swiper.enabled = false;
  218. if (swiper.params.grabCursor) {
  219. swiper.unsetGrabCursor();
  220. }
  221. swiper.emit('disable');
  222. }
  223. setProgress(progress, speed) {
  224. const swiper = this;
  225. progress = Math.min(Math.max(progress, 0), 1);
  226. const min = swiper.minTranslate();
  227. const max = swiper.maxTranslate();
  228. const current = (max - min) * progress + min;
  229. swiper.translateTo(current, typeof speed === 'undefined' ? 0 : speed);
  230. swiper.updateActiveIndex();
  231. swiper.updateSlidesClasses();
  232. }
  233. emitContainerClasses() {
  234. const swiper = this;
  235. if (!swiper.params._emitClasses || !swiper.el) return;
  236. const cls = swiper.el.className.split(' ').filter(className => {
  237. return className.indexOf('swiper') === 0 || className.indexOf(swiper.params.containerModifierClass) === 0;
  238. });
  239. swiper.emit('_containerClasses', cls.join(' '));
  240. }
  241. getSlideClasses(slideEl) {
  242. const swiper = this;
  243. if (swiper.destroyed) return '';
  244. return slideEl.className.split(' ').filter(className => {
  245. return className.indexOf('swiper-slide') === 0 || className.indexOf(swiper.params.slideClass) === 0;
  246. }).join(' ');
  247. }
  248. emitSlidesClasses() {
  249. const swiper = this;
  250. if (!swiper.params._emitClasses || !swiper.el) return;
  251. const updates = [];
  252. swiper.slides.forEach(slideEl => {
  253. const classNames = swiper.getSlideClasses(slideEl);
  254. updates.push({
  255. slideEl,
  256. classNames
  257. });
  258. swiper.emit('_slideClass', slideEl, classNames);
  259. });
  260. swiper.emit('_slideClasses', updates);
  261. }
  262. slidesPerViewDynamic(view = 'current', exact = false) {
  263. const swiper = this;
  264. const {
  265. params,
  266. slides,
  267. slidesGrid,
  268. slidesSizesGrid,
  269. size: swiperSize,
  270. activeIndex
  271. } = swiper;
  272. let spv = 1;
  273. if (params.centeredSlides) {
  274. let slideSize = slides[activeIndex] ? slides[activeIndex].swiperSlideSize : 0;
  275. let breakLoop;
  276. for (let i = activeIndex + 1; i < slides.length; i += 1) {
  277. if (slides[i] && !breakLoop) {
  278. slideSize += slides[i].swiperSlideSize;
  279. spv += 1;
  280. if (slideSize > swiperSize) breakLoop = true;
  281. }
  282. }
  283. for (let i = activeIndex - 1; i >= 0; i -= 1) {
  284. if (slides[i] && !breakLoop) {
  285. slideSize += slides[i].swiperSlideSize;
  286. spv += 1;
  287. if (slideSize > swiperSize) breakLoop = true;
  288. }
  289. }
  290. } else {
  291. // eslint-disable-next-line
  292. if (view === 'current') {
  293. for (let i = activeIndex + 1; i < slides.length; i += 1) {
  294. const slideInView = exact ? slidesGrid[i] + slidesSizesGrid[i] - slidesGrid[activeIndex] < swiperSize : slidesGrid[i] - slidesGrid[activeIndex] < swiperSize;
  295. if (slideInView) {
  296. spv += 1;
  297. }
  298. }
  299. } else {
  300. // previous
  301. for (let i = activeIndex - 1; i >= 0; i -= 1) {
  302. const slideInView = slidesGrid[activeIndex] - slidesGrid[i] < swiperSize;
  303. if (slideInView) {
  304. spv += 1;
  305. }
  306. }
  307. }
  308. }
  309. return spv;
  310. }
  311. update() {
  312. const swiper = this;
  313. if (!swiper || swiper.destroyed) return;
  314. const {
  315. snapGrid,
  316. params
  317. } = swiper;
  318. // Breakpoints
  319. if (params.breakpoints) {
  320. swiper.setBreakpoint();
  321. }
  322. [...swiper.el.querySelectorAll('[loading="lazy"]')].forEach(imageEl => {
  323. if (imageEl.complete) {
  324. processLazyPreloader(swiper, imageEl);
  325. }
  326. });
  327. swiper.updateSize();
  328. swiper.updateSlides();
  329. swiper.updateProgress();
  330. swiper.updateSlidesClasses();
  331. function setTranslate() {
  332. const translateValue = swiper.rtlTranslate ? swiper.translate * -1 : swiper.translate;
  333. const newTranslate = Math.min(Math.max(translateValue, swiper.maxTranslate()), swiper.minTranslate());
  334. swiper.setTranslate(newTranslate);
  335. swiper.updateActiveIndex();
  336. swiper.updateSlidesClasses();
  337. }
  338. let translated;
  339. if (params.freeMode && params.freeMode.enabled && !params.cssMode) {
  340. setTranslate();
  341. if (params.autoHeight) {
  342. swiper.updateAutoHeight();
  343. }
  344. } else {
  345. if ((params.slidesPerView === 'auto' || params.slidesPerView > 1) && swiper.isEnd && !params.centeredSlides) {
  346. const slides = swiper.virtual && params.virtual.enabled ? swiper.virtual.slides : swiper.slides;
  347. translated = swiper.slideTo(slides.length - 1, 0, false, true);
  348. } else {
  349. translated = swiper.slideTo(swiper.activeIndex, 0, false, true);
  350. }
  351. if (!translated) {
  352. setTranslate();
  353. }
  354. }
  355. if (params.watchOverflow && snapGrid !== swiper.snapGrid) {
  356. swiper.checkOverflow();
  357. }
  358. swiper.emit('update');
  359. }
  360. changeDirection(newDirection, needUpdate = true) {
  361. const swiper = this;
  362. const currentDirection = swiper.params.direction;
  363. if (!newDirection) {
  364. // eslint-disable-next-line
  365. newDirection = currentDirection === 'horizontal' ? 'vertical' : 'horizontal';
  366. }
  367. if (newDirection === currentDirection || newDirection !== 'horizontal' && newDirection !== 'vertical') {
  368. return swiper;
  369. }
  370. swiper.el.classList.remove(`${swiper.params.containerModifierClass}${currentDirection}`);
  371. swiper.el.classList.add(`${swiper.params.containerModifierClass}${newDirection}`);
  372. swiper.emitContainerClasses();
  373. swiper.params.direction = newDirection;
  374. swiper.slides.forEach(slideEl => {
  375. if (newDirection === 'vertical') {
  376. slideEl.style.width = '';
  377. } else {
  378. slideEl.style.height = '';
  379. }
  380. });
  381. swiper.emit('changeDirection');
  382. if (needUpdate) swiper.update();
  383. return swiper;
  384. }
  385. changeLanguageDirection(direction) {
  386. const swiper = this;
  387. if (swiper.rtl && direction === 'rtl' || !swiper.rtl && direction === 'ltr') return;
  388. swiper.rtl = direction === 'rtl';
  389. swiper.rtlTranslate = swiper.params.direction === 'horizontal' && swiper.rtl;
  390. if (swiper.rtl) {
  391. swiper.el.classList.add(`${swiper.params.containerModifierClass}rtl`);
  392. swiper.el.dir = 'rtl';
  393. } else {
  394. swiper.el.classList.remove(`${swiper.params.containerModifierClass}rtl`);
  395. swiper.el.dir = 'ltr';
  396. }
  397. swiper.update();
  398. }
  399. mount(element) {
  400. const swiper = this;
  401. if (swiper.mounted) return true;
  402. // Find el
  403. let el = element || swiper.params.el;
  404. if (typeof el === 'string') {
  405. el = document.querySelector(el);
  406. }
  407. if (!el) {
  408. return false;
  409. }
  410. el.swiper = swiper;
  411. if (el.shadowEl) {
  412. swiper.isElement = true;
  413. }
  414. const getWrapperSelector = () => {
  415. return `.${(swiper.params.wrapperClass || '').trim().split(' ').join('.')}`;
  416. };
  417. const getWrapper = () => {
  418. if (el && el.shadowRoot && el.shadowRoot.querySelector) {
  419. const res = el.shadowRoot.querySelector(getWrapperSelector());
  420. // Children needs to return slot items
  421. return res;
  422. }
  423. return elementChildren(el, getWrapperSelector())[0];
  424. };
  425. // Find Wrapper
  426. let wrapperEl = getWrapper();
  427. if (!wrapperEl && swiper.params.createElements) {
  428. wrapperEl = createElement('div', swiper.params.wrapperClass);
  429. el.append(wrapperEl);
  430. elementChildren(el, `.${swiper.params.slideClass}`).forEach(slideEl => {
  431. wrapperEl.append(slideEl);
  432. });
  433. }
  434. Object.assign(swiper, {
  435. el,
  436. wrapperEl,
  437. slidesEl: swiper.isElement ? el : wrapperEl,
  438. mounted: true,
  439. // RTL
  440. rtl: el.dir.toLowerCase() === 'rtl' || elementStyle(el, 'direction') === 'rtl',
  441. rtlTranslate: swiper.params.direction === 'horizontal' && (el.dir.toLowerCase() === 'rtl' || elementStyle(el, 'direction') === 'rtl'),
  442. wrongRTL: elementStyle(wrapperEl, 'display') === '-webkit-box'
  443. });
  444. return true;
  445. }
  446. init(el) {
  447. const swiper = this;
  448. if (swiper.initialized) return swiper;
  449. const mounted = swiper.mount(el);
  450. if (mounted === false) return swiper;
  451. swiper.emit('beforeInit');
  452. // Set breakpoint
  453. if (swiper.params.breakpoints) {
  454. swiper.setBreakpoint();
  455. }
  456. // Add Classes
  457. swiper.addClasses();
  458. // Update size
  459. swiper.updateSize();
  460. // Update slides
  461. swiper.updateSlides();
  462. if (swiper.params.watchOverflow) {
  463. swiper.checkOverflow();
  464. }
  465. // Set Grab Cursor
  466. if (swiper.params.grabCursor && swiper.enabled) {
  467. swiper.setGrabCursor();
  468. }
  469. // Slide To Initial Slide
  470. if (swiper.params.loop && swiper.virtual && swiper.params.virtual.enabled) {
  471. swiper.slideTo(swiper.params.initialSlide + swiper.virtual.slidesBefore, 0, swiper.params.runCallbacksOnInit, false, true);
  472. } else {
  473. swiper.slideTo(swiper.params.initialSlide, 0, swiper.params.runCallbacksOnInit, false, true);
  474. }
  475. // Create loop
  476. if (swiper.params.loop) {
  477. swiper.loopCreate();
  478. }
  479. // Attach events
  480. swiper.attachEvents();
  481. [...swiper.el.querySelectorAll('[loading="lazy"]')].forEach(imageEl => {
  482. if (imageEl.complete) {
  483. processLazyPreloader(swiper, imageEl);
  484. } else {
  485. imageEl.addEventListener('load', e => {
  486. processLazyPreloader(swiper, e.target);
  487. });
  488. }
  489. });
  490. preload(swiper);
  491. // Init Flag
  492. swiper.initialized = true;
  493. preload(swiper);
  494. // Emit
  495. swiper.emit('init');
  496. swiper.emit('afterInit');
  497. return swiper;
  498. }
  499. destroy(deleteInstance = true, cleanStyles = true) {
  500. const swiper = this;
  501. const {
  502. params,
  503. el,
  504. wrapperEl,
  505. slides
  506. } = swiper;
  507. if (typeof swiper.params === 'undefined' || swiper.destroyed) {
  508. return null;
  509. }
  510. swiper.emit('beforeDestroy');
  511. // Init Flag
  512. swiper.initialized = false;
  513. // Detach events
  514. swiper.detachEvents();
  515. // Destroy loop
  516. if (params.loop) {
  517. swiper.loopDestroy();
  518. }
  519. // Cleanup styles
  520. if (cleanStyles) {
  521. swiper.removeClasses();
  522. el.removeAttribute('style');
  523. wrapperEl.removeAttribute('style');
  524. if (slides && slides.length) {
  525. slides.forEach(slideEl => {
  526. slideEl.classList.remove(params.slideVisibleClass, params.slideActiveClass, params.slideNextClass, params.slidePrevClass);
  527. slideEl.removeAttribute('style');
  528. slideEl.removeAttribute('data-swiper-slide-index');
  529. });
  530. }
  531. }
  532. swiper.emit('destroy');
  533. // Detach emitter events
  534. Object.keys(swiper.eventsListeners).forEach(eventName => {
  535. swiper.off(eventName);
  536. });
  537. if (deleteInstance !== false) {
  538. swiper.el.swiper = null;
  539. deleteProps(swiper);
  540. }
  541. swiper.destroyed = true;
  542. return null;
  543. }
  544. static extendDefaults(newDefaults) {
  545. extend(extendedDefaults, newDefaults);
  546. }
  547. static get extendedDefaults() {
  548. return extendedDefaults;
  549. }
  550. static get defaults() {
  551. return defaults;
  552. }
  553. static installModule(mod) {
  554. if (!Swiper.prototype.__modules__) Swiper.prototype.__modules__ = [];
  555. const modules = Swiper.prototype.__modules__;
  556. if (typeof mod === 'function' && modules.indexOf(mod) < 0) {
  557. modules.push(mod);
  558. }
  559. }
  560. static use(module) {
  561. if (Array.isArray(module)) {
  562. module.forEach(m => Swiper.installModule(m));
  563. return Swiper;
  564. }
  565. Swiper.installModule(module);
  566. return Swiper;
  567. }
  568. }
  569. Object.keys(prototypes).forEach(prototypeGroup => {
  570. Object.keys(prototypes[prototypeGroup]).forEach(protoMethod => {
  571. Swiper.prototype[protoMethod] = prototypes[prototypeGroup][protoMethod];
  572. });
  573. });
  574. Swiper.use([Resize, Observer]);
  575. export default Swiper;