core.js 17 KB

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