pagination.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. import classesToSelector from '../../shared/classes-to-selector.js';
  2. import createElementIfNotDefined from '../../shared/create-element-if-not-defined.js';
  3. import { elementIndex, elementOuterSize, elementParents } from '../../shared/utils.js';
  4. export default function Pagination({
  5. swiper,
  6. extendParams,
  7. on,
  8. emit
  9. }) {
  10. const pfx = 'swiper-pagination';
  11. extendParams({
  12. pagination: {
  13. el: null,
  14. bulletElement: 'span',
  15. clickable: false,
  16. hideOnClick: false,
  17. renderBullet: null,
  18. renderProgressbar: null,
  19. renderFraction: null,
  20. renderCustom: null,
  21. progressbarOpposite: false,
  22. type: 'bullets',
  23. // 'bullets' or 'progressbar' or 'fraction' or 'custom'
  24. dynamicBullets: false,
  25. dynamicMainBullets: 1,
  26. formatFractionCurrent: number => number,
  27. formatFractionTotal: number => number,
  28. bulletClass: `${pfx}-bullet`,
  29. bulletActiveClass: `${pfx}-bullet-active`,
  30. modifierClass: `${pfx}-`,
  31. currentClass: `${pfx}-current`,
  32. totalClass: `${pfx}-total`,
  33. hiddenClass: `${pfx}-hidden`,
  34. progressbarFillClass: `${pfx}-progressbar-fill`,
  35. progressbarOppositeClass: `${pfx}-progressbar-opposite`,
  36. clickableClass: `${pfx}-clickable`,
  37. lockClass: `${pfx}-lock`,
  38. horizontalClass: `${pfx}-horizontal`,
  39. verticalClass: `${pfx}-vertical`,
  40. paginationDisabledClass: `${pfx}-disabled`
  41. }
  42. });
  43. swiper.pagination = {
  44. el: null,
  45. bullets: []
  46. };
  47. let bulletSize;
  48. let dynamicBulletIndex = 0;
  49. const makeElementsArray = el => {
  50. if (!Array.isArray(el)) el = [el].filter(e => !!e);
  51. return el;
  52. };
  53. function isPaginationDisabled() {
  54. return !swiper.params.pagination.el || !swiper.pagination.el || Array.isArray(swiper.pagination.el) && swiper.pagination.el.length === 0;
  55. }
  56. function setSideBullets(bulletEl, position) {
  57. const {
  58. bulletActiveClass
  59. } = swiper.params.pagination;
  60. if (!bulletEl) return;
  61. bulletEl = bulletEl[`${position === 'prev' ? 'previous' : 'next'}ElementSibling`];
  62. if (bulletEl) {
  63. bulletEl.classList.add(`${bulletActiveClass}-${position}`);
  64. bulletEl = bulletEl[`${position === 'prev' ? 'previous' : 'next'}ElementSibling`];
  65. if (bulletEl) {
  66. bulletEl.classList.add(`${bulletActiveClass}-${position}-${position}`);
  67. }
  68. }
  69. }
  70. function onBulletClick(e) {
  71. const bulletEl = e.target.closest(classesToSelector(swiper.params.pagination.bulletClass));
  72. if (!bulletEl) {
  73. return;
  74. }
  75. e.preventDefault();
  76. const index = elementIndex(bulletEl) * swiper.params.slidesPerGroup;
  77. if (swiper.params.loop) {
  78. if (swiper.realIndex === index) return;
  79. const newSlideIndex = swiper.getSlideIndexByData(index);
  80. const currentSlideIndex = swiper.getSlideIndexByData(swiper.realIndex);
  81. if (newSlideIndex > swiper.slides.length - swiper.loopedSlides) {
  82. swiper.loopFix({
  83. direction: newSlideIndex > currentSlideIndex ? 'next' : 'prev',
  84. activeSlideIndex: newSlideIndex,
  85. slideTo: false
  86. });
  87. }
  88. swiper.slideToLoop(index);
  89. } else {
  90. swiper.slideTo(index);
  91. }
  92. }
  93. function update() {
  94. // Render || Update Pagination bullets/items
  95. const rtl = swiper.rtl;
  96. const params = swiper.params.pagination;
  97. if (isPaginationDisabled()) return;
  98. let el = swiper.pagination.el;
  99. el = makeElementsArray(el);
  100. // Current/Total
  101. let current;
  102. let previousIndex;
  103. const slidesLength = swiper.virtual && swiper.params.virtual.enabled ? swiper.virtual.slides.length : swiper.slides.length;
  104. const total = swiper.params.loop ? Math.ceil(slidesLength / swiper.params.slidesPerGroup) : swiper.snapGrid.length;
  105. if (swiper.params.loop) {
  106. previousIndex = swiper.previousRealIndex || 0;
  107. current = swiper.params.slidesPerGroup > 1 ? Math.floor(swiper.realIndex / swiper.params.slidesPerGroup) : swiper.realIndex;
  108. } else if (typeof swiper.snapIndex !== 'undefined') {
  109. current = swiper.snapIndex;
  110. previousIndex = swiper.previousSnapIndex;
  111. } else {
  112. previousIndex = swiper.previousIndex || 0;
  113. current = swiper.activeIndex || 0;
  114. }
  115. // Types
  116. if (params.type === 'bullets' && swiper.pagination.bullets && swiper.pagination.bullets.length > 0) {
  117. const bullets = swiper.pagination.bullets;
  118. let firstIndex;
  119. let lastIndex;
  120. let midIndex;
  121. if (params.dynamicBullets) {
  122. bulletSize = elementOuterSize(bullets[0], swiper.isHorizontal() ? 'width' : 'height', true);
  123. el.forEach(subEl => {
  124. subEl.style[swiper.isHorizontal() ? 'width' : 'height'] = `${bulletSize * (params.dynamicMainBullets + 4)}px`;
  125. });
  126. if (params.dynamicMainBullets > 1 && previousIndex !== undefined) {
  127. dynamicBulletIndex += current - (previousIndex || 0);
  128. if (dynamicBulletIndex > params.dynamicMainBullets - 1) {
  129. dynamicBulletIndex = params.dynamicMainBullets - 1;
  130. } else if (dynamicBulletIndex < 0) {
  131. dynamicBulletIndex = 0;
  132. }
  133. }
  134. firstIndex = Math.max(current - dynamicBulletIndex, 0);
  135. lastIndex = firstIndex + (Math.min(bullets.length, params.dynamicMainBullets) - 1);
  136. midIndex = (lastIndex + firstIndex) / 2;
  137. }
  138. bullets.forEach(bulletEl => {
  139. const classesToRemove = [...['', '-next', '-next-next', '-prev', '-prev-prev', '-main'].map(suffix => `${params.bulletActiveClass}${suffix}`)].map(s => typeof s === 'string' && s.includes(' ') ? s.split(' ') : s).flat();
  140. bulletEl.classList.remove(...classesToRemove);
  141. });
  142. if (el.length > 1) {
  143. bullets.forEach(bullet => {
  144. const bulletIndex = elementIndex(bullet);
  145. if (bulletIndex === current) {
  146. bullet.classList.add(...params.bulletActiveClass.split(' '));
  147. } else if (swiper.isElement) {
  148. bullet.setAttribute('part', 'bullet');
  149. }
  150. if (params.dynamicBullets) {
  151. if (bulletIndex >= firstIndex && bulletIndex <= lastIndex) {
  152. bullet.classList.add(...`${params.bulletActiveClass}-main`.split(' '));
  153. }
  154. if (bulletIndex === firstIndex) {
  155. setSideBullets(bullet, 'prev');
  156. }
  157. if (bulletIndex === lastIndex) {
  158. setSideBullets(bullet, 'next');
  159. }
  160. }
  161. });
  162. } else {
  163. const bullet = bullets[current];
  164. if (bullet) {
  165. bullet.classList.add(...params.bulletActiveClass.split(' '));
  166. }
  167. if (swiper.isElement) {
  168. bullets.forEach((bulletEl, bulletIndex) => {
  169. bulletEl.setAttribute('part', bulletIndex === current ? 'bullet-active' : 'bullet');
  170. });
  171. }
  172. if (params.dynamicBullets) {
  173. const firstDisplayedBullet = bullets[firstIndex];
  174. const lastDisplayedBullet = bullets[lastIndex];
  175. for (let i = firstIndex; i <= lastIndex; i += 1) {
  176. if (bullets[i]) {
  177. bullets[i].classList.add(...`${params.bulletActiveClass}-main`.split(' '));
  178. }
  179. }
  180. setSideBullets(firstDisplayedBullet, 'prev');
  181. setSideBullets(lastDisplayedBullet, 'next');
  182. }
  183. }
  184. if (params.dynamicBullets) {
  185. const dynamicBulletsLength = Math.min(bullets.length, params.dynamicMainBullets + 4);
  186. const bulletsOffset = (bulletSize * dynamicBulletsLength - bulletSize) / 2 - midIndex * bulletSize;
  187. const offsetProp = rtl ? 'right' : 'left';
  188. bullets.forEach(bullet => {
  189. bullet.style[swiper.isHorizontal() ? offsetProp : 'top'] = `${bulletsOffset}px`;
  190. });
  191. }
  192. }
  193. el.forEach((subEl, subElIndex) => {
  194. if (params.type === 'fraction') {
  195. subEl.querySelectorAll(classesToSelector(params.currentClass)).forEach(fractionEl => {
  196. fractionEl.textContent = params.formatFractionCurrent(current + 1);
  197. });
  198. subEl.querySelectorAll(classesToSelector(params.totalClass)).forEach(totalEl => {
  199. totalEl.textContent = params.formatFractionTotal(total);
  200. });
  201. }
  202. if (params.type === 'progressbar') {
  203. let progressbarDirection;
  204. if (params.progressbarOpposite) {
  205. progressbarDirection = swiper.isHorizontal() ? 'vertical' : 'horizontal';
  206. } else {
  207. progressbarDirection = swiper.isHorizontal() ? 'horizontal' : 'vertical';
  208. }
  209. const scale = (current + 1) / total;
  210. let scaleX = 1;
  211. let scaleY = 1;
  212. if (progressbarDirection === 'horizontal') {
  213. scaleX = scale;
  214. } else {
  215. scaleY = scale;
  216. }
  217. subEl.querySelectorAll(classesToSelector(params.progressbarFillClass)).forEach(progressEl => {
  218. progressEl.style.transform = `translate3d(0,0,0) scaleX(${scaleX}) scaleY(${scaleY})`;
  219. progressEl.style.transitionDuration = `${swiper.params.speed}ms`;
  220. });
  221. }
  222. if (params.type === 'custom' && params.renderCustom) {
  223. subEl.innerHTML = params.renderCustom(swiper, current + 1, total);
  224. if (subElIndex === 0) emit('paginationRender', subEl);
  225. } else {
  226. if (subElIndex === 0) emit('paginationRender', subEl);
  227. emit('paginationUpdate', subEl);
  228. }
  229. if (swiper.params.watchOverflow && swiper.enabled) {
  230. subEl.classList[swiper.isLocked ? 'add' : 'remove'](params.lockClass);
  231. }
  232. });
  233. }
  234. function render() {
  235. // Render Container
  236. const params = swiper.params.pagination;
  237. if (isPaginationDisabled()) return;
  238. const slidesLength = swiper.virtual && swiper.params.virtual.enabled ? swiper.virtual.slides.length : swiper.slides.length;
  239. let el = swiper.pagination.el;
  240. el = makeElementsArray(el);
  241. let paginationHTML = '';
  242. if (params.type === 'bullets') {
  243. let numberOfBullets = swiper.params.loop ? Math.ceil(slidesLength / swiper.params.slidesPerGroup) : swiper.snapGrid.length;
  244. if (swiper.params.freeMode && swiper.params.freeMode.enabled && numberOfBullets > slidesLength) {
  245. numberOfBullets = slidesLength;
  246. }
  247. for (let i = 0; i < numberOfBullets; i += 1) {
  248. if (params.renderBullet) {
  249. paginationHTML += params.renderBullet.call(swiper, i, params.bulletClass);
  250. } else {
  251. // prettier-ignore
  252. paginationHTML += `<${params.bulletElement} ${swiper.isElement ? 'part="bullet"' : ''} class="${params.bulletClass}"></${params.bulletElement}>`;
  253. }
  254. }
  255. }
  256. if (params.type === 'fraction') {
  257. if (params.renderFraction) {
  258. paginationHTML = params.renderFraction.call(swiper, params.currentClass, params.totalClass);
  259. } else {
  260. paginationHTML = `<span class="${params.currentClass}"></span>` + ' / ' + `<span class="${params.totalClass}"></span>`;
  261. }
  262. }
  263. if (params.type === 'progressbar') {
  264. if (params.renderProgressbar) {
  265. paginationHTML = params.renderProgressbar.call(swiper, params.progressbarFillClass);
  266. } else {
  267. paginationHTML = `<span class="${params.progressbarFillClass}"></span>`;
  268. }
  269. }
  270. swiper.pagination.bullets = [];
  271. el.forEach(subEl => {
  272. if (params.type !== 'custom') {
  273. subEl.innerHTML = paginationHTML || '';
  274. }
  275. if (params.type === 'bullets') {
  276. swiper.pagination.bullets.push(...subEl.querySelectorAll(classesToSelector(params.bulletClass)));
  277. }
  278. });
  279. if (params.type !== 'custom') {
  280. emit('paginationRender', el[0]);
  281. }
  282. }
  283. function init() {
  284. swiper.params.pagination = createElementIfNotDefined(swiper, swiper.originalParams.pagination, swiper.params.pagination, {
  285. el: 'swiper-pagination'
  286. });
  287. const params = swiper.params.pagination;
  288. if (!params.el) return;
  289. let el;
  290. if (typeof params.el === 'string' && swiper.isElement) {
  291. el = swiper.el.shadowRoot.querySelector(params.el);
  292. }
  293. if (!el && typeof params.el === 'string') {
  294. el = [...document.querySelectorAll(params.el)];
  295. }
  296. if (!el) {
  297. el = params.el;
  298. }
  299. if (!el || el.length === 0) return;
  300. if (swiper.params.uniqueNavElements && typeof params.el === 'string' && Array.isArray(el) && el.length > 1) {
  301. el = [...swiper.el.querySelectorAll(params.el)];
  302. // check if it belongs to another nested Swiper
  303. if (el.length > 1) {
  304. el = el.filter(subEl => {
  305. if (elementParents(subEl, '.swiper')[0] !== swiper.el) return false;
  306. return true;
  307. })[0];
  308. }
  309. }
  310. if (Array.isArray(el) && el.length === 1) el = el[0];
  311. Object.assign(swiper.pagination, {
  312. el
  313. });
  314. el = makeElementsArray(el);
  315. el.forEach(subEl => {
  316. if (params.type === 'bullets' && params.clickable) {
  317. subEl.classList.add(params.clickableClass);
  318. }
  319. subEl.classList.add(params.modifierClass + params.type);
  320. subEl.classList.add(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass);
  321. if (params.type === 'bullets' && params.dynamicBullets) {
  322. subEl.classList.add(`${params.modifierClass}${params.type}-dynamic`);
  323. dynamicBulletIndex = 0;
  324. if (params.dynamicMainBullets < 1) {
  325. params.dynamicMainBullets = 1;
  326. }
  327. }
  328. if (params.type === 'progressbar' && params.progressbarOpposite) {
  329. subEl.classList.add(params.progressbarOppositeClass);
  330. }
  331. if (params.clickable) {
  332. subEl.addEventListener('click', onBulletClick);
  333. }
  334. if (!swiper.enabled) {
  335. subEl.classList.add(params.lockClass);
  336. }
  337. });
  338. }
  339. function destroy() {
  340. const params = swiper.params.pagination;
  341. if (isPaginationDisabled()) return;
  342. let el = swiper.pagination.el;
  343. if (el) {
  344. el = makeElementsArray(el);
  345. el.forEach(subEl => {
  346. subEl.classList.remove(params.hiddenClass);
  347. subEl.classList.remove(params.modifierClass + params.type);
  348. subEl.classList.remove(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass);
  349. if (params.clickable) {
  350. subEl.removeEventListener('click', onBulletClick);
  351. }
  352. });
  353. }
  354. if (swiper.pagination.bullets) swiper.pagination.bullets.forEach(subEl => subEl.classList.remove(...params.bulletActiveClass.split(' ')));
  355. }
  356. on('changeDirection', () => {
  357. if (!swiper.pagination || !swiper.pagination.el) return;
  358. const params = swiper.params.pagination;
  359. let {
  360. el
  361. } = swiper.pagination;
  362. el = makeElementsArray(el);
  363. el.forEach(subEl => {
  364. subEl.classList.remove(params.horizontalClass, params.verticalClass);
  365. subEl.classList.add(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass);
  366. });
  367. });
  368. on('init', () => {
  369. if (swiper.params.pagination.enabled === false) {
  370. // eslint-disable-next-line
  371. disable();
  372. } else {
  373. init();
  374. render();
  375. update();
  376. }
  377. });
  378. on('activeIndexChange', () => {
  379. if (typeof swiper.snapIndex === 'undefined') {
  380. update();
  381. }
  382. });
  383. on('snapIndexChange', () => {
  384. update();
  385. });
  386. on('snapGridLengthChange', () => {
  387. render();
  388. update();
  389. });
  390. on('destroy', () => {
  391. destroy();
  392. });
  393. on('enable disable', () => {
  394. let {
  395. el
  396. } = swiper.pagination;
  397. if (el) {
  398. el = makeElementsArray(el);
  399. el.forEach(subEl => subEl.classList[swiper.enabled ? 'remove' : 'add'](swiper.params.pagination.lockClass));
  400. }
  401. });
  402. on('lock unlock', () => {
  403. update();
  404. });
  405. on('click', (_s, e) => {
  406. const targetEl = e.target;
  407. let {
  408. el
  409. } = swiper.pagination;
  410. if (!Array.isArray(el)) el = [el].filter(element => !!element);
  411. if (swiper.params.pagination.el && swiper.params.pagination.hideOnClick && el && el.length > 0 && !targetEl.classList.contains(swiper.params.pagination.bulletClass)) {
  412. if (swiper.navigation && (swiper.navigation.nextEl && targetEl === swiper.navigation.nextEl || swiper.navigation.prevEl && targetEl === swiper.navigation.prevEl)) return;
  413. const isHidden = el[0].classList.contains(swiper.params.pagination.hiddenClass);
  414. if (isHidden === true) {
  415. emit('paginationShow');
  416. } else {
  417. emit('paginationHide');
  418. }
  419. el.forEach(subEl => subEl.classList.toggle(swiper.params.pagination.hiddenClass));
  420. }
  421. });
  422. const enable = () => {
  423. swiper.el.classList.remove(swiper.params.pagination.paginationDisabledClass);
  424. let {
  425. el
  426. } = swiper.pagination;
  427. if (el) {
  428. el = makeElementsArray(el);
  429. el.forEach(subEl => subEl.classList.remove(swiper.params.pagination.paginationDisabledClass));
  430. }
  431. init();
  432. render();
  433. update();
  434. };
  435. const disable = () => {
  436. swiper.el.classList.add(swiper.params.pagination.paginationDisabledClass);
  437. let {
  438. el
  439. } = swiper.pagination;
  440. if (el) {
  441. el = makeElementsArray(el);
  442. el.forEach(subEl => subEl.classList.add(swiper.params.pagination.paginationDisabledClass));
  443. }
  444. destroy();
  445. };
  446. Object.assign(swiper.pagination, {
  447. enable,
  448. disable,
  449. render,
  450. update,
  451. init,
  452. destroy
  453. });
  454. }