UNPKG

@zebra-ui/swiper

Version:

专为多端设计的高性能swiper轮播组件库,支持多种复杂的 3D swiper轮播效果。

881 lines (815 loc) 25.7 kB
import classesToSelector from '../../components/shared/classes-to-selector' import createElementIfNotDefined from '../../components/shared/create-element-if-not-defined' import { elementIndex, elementOuterSize, elementParents, isWeb, makeElementsArray } from '../../components/shared/utils' import type { PaginationOptions, PaginationMethods } from '../../types/module/pagination' import type { SwiperInterface } from '../../types/swiper-class' import { convertSingleValue } from '../../components/components-shared/utils' import type { FreeModeOptions } from '../../types/modules/free-mode' import { ClassList } from '../../components/adapter/index' import { reactive } from 'vue' export default function Pagination({ swiper, extendParams, on, emit }: { swiper: SwiperInterface extendParams: (params: Record<string, any>) => void on: (event: string, handler: (...args: any[]) => void) => void emit: (event: string, ...args: any[]) => void }): void { const pfx = 'swiper-pagination' extendParams({ pagination: { el: null, bulletElement: 'span', bulletSize: 8, clickable: false, hideOnClick: false, renderBullet: undefined, renderProgressbar: undefined, renderFraction: undefined, renderCustom: undefined, progressbarOpposite: false, type: 'bullets', dynamicBullets: false, dynamicMainBullets: 1, formatFractionCurrent: (number: number) => number, formatFractionTotal: (number: number) => number, bulletClass: `${pfx}-bullet`, bulletActiveClass: `${pfx}-bullet-active`, modifierClass: `${pfx}-`, currentClass: `${pfx}-current`, totalClass: `${pfx}-total`, hiddenClass: `${pfx}-hidden`, progressbarFillClass: `${pfx}-progressbar-fill`, progressbarOppositeClass: `${pfx}-progressbar-opposite`, clickableClass: `${pfx}-clickable`, lockClass: `${pfx}-lock`, horizontalClass: `${pfx}-horizontal`, verticalClass: `${pfx}-vertical`, paginationDisabledClass: `${pfx}-disabled` } as PaginationOptions }) // @ts-ignore swiper.pagination = { // @ts-ignore el: null, bullets: [] } as PaginationMethods let bulletSize = 0 let dynamicBulletIndex = 0 function isPaginationDisabled(): boolean { return ( !(swiper.params.pagination as PaginationOptions)?.el || !swiper.pagination.el || (Array.isArray(swiper.pagination.el) && swiper.pagination.el.length === 0) ) } function setSideBullets( bulletEl: HTMLElement, position: 'prev' | 'next', bullets?: HTMLElement[] ) { const params = swiper.params.pagination as PaginationOptions if (!bulletEl) return if (isWeb()) { bulletEl = bulletEl[ `${position === 'prev' ? 'previous' : 'next'}ElementSibling` ] as HTMLElement } else { if (!bullets) return const bulletIndex = bullets.findIndex((item) => item === bulletEl) bulletEl = bullets[ position === 'prev' ? bulletIndex - 1 : bulletIndex + 1 ] as HTMLElement } if (bulletEl) { bulletEl.classList.add(`${params.bulletActiveClass}-${position}`) const nextBulletEl = bulletEl[ `${position === 'prev' ? 'previous' : 'next'}ElementSibling` ] as HTMLElement if (nextBulletEl) { nextBulletEl.classList.add( `${params.bulletActiveClass}-${position}-${position}` ) } } } function getMoveDirection( prevIndex: number, nextIndex: number, length: number ): 'next' | 'previous' | undefined { prevIndex = prevIndex % length nextIndex = nextIndex % length if (nextIndex === prevIndex + 1) { return 'next' } else if (nextIndex === prevIndex - 1) { return 'previous' } return undefined } function onBulletClick(e: Event, paginationIndex?: number) { let targetIndex = 0 if (isWeb()) { const bulletEl = (e.target as HTMLElement).closest( classesToSelector( (swiper.params.pagination as PaginationOptions).bulletClass as string ) ) if (!bulletEl) return e.preventDefault() targetIndex = (elementIndex(bulletEl) as number) * (swiper.params.slidesPerGroup as number) } else { targetIndex = paginationIndex as number } if (swiper.params.loop) { if (swiper.realIndex === targetIndex) return const moveDirection = getMoveDirection( swiper.realIndex, targetIndex, swiper.slides.length ) if (moveDirection === 'next') { swiper.slideNext() } else if (moveDirection === 'previous') { swiper.slidePrev() } else { swiper.slideToLoop(targetIndex) } } else { swiper.slideTo(targetIndex) } } function update() { if (isPaginationDisabled()) return const params = swiper.params.pagination as PaginationOptions const { rtl } = swiper if (isPaginationDisabled()) return const el = makeElementsArray(swiper.pagination.el) // Current/Total let current: number let previousIndex: number const slidesLength = swiper.virtual && typeof swiper.params.virtual === 'object' && swiper.params.virtual?.enabled ? swiper.virtual.slides.length : swiper.slides.length const total = swiper.params.loop ? Math.ceil(slidesLength / (swiper.params.slidesPerGroup as number)) : swiper.snapGrid.length if (swiper.params.loop) { previousIndex = swiper.previousRealIndex || 0 current = (swiper.params.slidesPerGroup as number) > 1 ? Math.floor( swiper.realIndex / (swiper.params.slidesPerGroup as number) ) : swiper.realIndex } else if (typeof swiper.snapIndex !== 'undefined') { current = swiper.snapIndex previousIndex = swiper.previousSnapIndex } else { previousIndex = swiper.previousIndex || 0 current = swiper.activeIndex || 0 } // Types if ( params.type === 'bullets' && swiper.pagination.bullets && swiper.pagination.bullets.length > 0 ) { const { bullets } = swiper.pagination let firstIndex: number let lastIndex: number let midIndex: number if (params.dynamicBullets) { if (isWeb()) { bulletSize = elementOuterSize( bullets[0], swiper.isHorizontal() ? 'width' : 'height', true ) } else { bulletSize = convertSingleValue(params.bulletSize, 'number') + 8 } el.forEach((subEl) => { subEl.style[swiper.isHorizontal() ? 'width' : 'height'] = `${bulletSize * ((params.dynamicMainBullets as number) + 4)}px` }) if ( (params.dynamicMainBullets as number) > 1 && previousIndex !== undefined ) { dynamicBulletIndex += current - (previousIndex || 0) if (dynamicBulletIndex > (params.dynamicMainBullets as number) - 1) { dynamicBulletIndex = (params.dynamicMainBullets as number) - 1 } else if (dynamicBulletIndex < 0) { dynamicBulletIndex = 0 } } firstIndex = Math.max(current - dynamicBulletIndex, 0) lastIndex = firstIndex + (Math.min(bullets.length, params.dynamicMainBullets as number) - 1) midIndex = (lastIndex + firstIndex) / 2 } bullets.forEach((bulletEl) => { const classesToRemove = [ '', '-next', '-next-next', '-prev', '-prev-prev', '-main' ] .map((suffix) => `${params.bulletActiveClass}${suffix}`) .map((s) => typeof s === 'string' && s.includes(' ') ? s.split(' ') : s ) // @ts-ignore .flat() bulletEl.classList.remove(...classesToRemove) }) if (el.length > 1) { bullets.forEach((bullet) => { const bulletIndex = elementIndex(bullet) if (bulletIndex === current) { bullet.classList.add( ...(params.bulletActiveClass as string).split(' ') ) } if (params.dynamicBullets) { if ( (bulletIndex as number) >= firstIndex && (bulletIndex as number) <= lastIndex ) { bullet.classList.add( ...`${params.bulletActiveClass}-main`.split(' ') ) } if (bulletIndex === firstIndex) { setSideBullets(bullet, 'prev') } if (bulletIndex === lastIndex) { setSideBullets(bullet, 'next') } } }) } else { const bullet = bullets[current] if (bullet) { bullet.classList.add( ...(params.bulletActiveClass as string).split(' ') ) } if (swiper.isElement) { bullets.forEach((bulletEl, bulletIndex) => { bulletEl.setAttribute( 'part', bulletIndex === current ? 'bullet-active' : 'bullet' ) }) } if (params.dynamicBullets) { // @ts-ignore const firstDisplayedBullet = bullets[firstIndex] // @ts-ignore const lastDisplayedBullet = bullets[lastIndex] // @ts-ignore for (let i = firstIndex; i <= lastIndex; i += 1) { if (bullets[i]) { bullets[i].classList.add( ...`${params.bulletActiveClass}-main`.split(' ') ) } } setSideBullets(firstDisplayedBullet, 'prev', bullets) setSideBullets(lastDisplayedBullet, 'next', bullets) } } if (params.dynamicBullets) { const dynamicBulletsLength = Math.min( bullets.length, (params.dynamicMainBullets as number) + 4 ) const bulletsOffset = (bulletSize * dynamicBulletsLength - bulletSize) / 2 - // @ts-ignore midIndex * bulletSize const offsetProp = rtl ? 'right' : 'left' bullets.forEach((bullet) => { bullet.style[swiper.isHorizontal() ? offsetProp : 'top'] = `${bulletsOffset}px` }) } } el.forEach((subEl, subElIndex) => { if (params.type === 'fraction') { if (isWeb()) { subEl .querySelectorAll(classesToSelector(params.currentClass)) .forEach((fractionEl) => { // @ts-ignore fractionEl.textContent = params.formatFractionCurrent(current + 1) }) subEl .querySelectorAll(classesToSelector(params.totalClass)) .forEach((totalEl) => { // @ts-ignore totalEl.textContent = params.formatFractionTotal(total) }) } else { // @ts-ignore subEl.paginationData.fractions.current.content = // @ts-ignore params.formatFractionCurrent(current + 1) // @ts-ignore subEl.paginationData.fractions.total.content = // @ts-ignore params.formatFractionTotal(total) } } if (params.type === 'progressbar') { let progressbarDirection: string if (params.progressbarOpposite) { progressbarDirection = swiper.isHorizontal() ? 'vertical' : 'horizontal' } else { progressbarDirection = swiper.isHorizontal() ? 'horizontal' : 'vertical' } const scale = (current + 1) / total let scaleX = 1 let scaleY = 1 if (progressbarDirection === 'horizontal') { scaleX = scale } else { scaleY = scale } if (isWeb()) { subEl .querySelectorAll(classesToSelector(params.progressbarFillClass)) // @ts-ignore .forEach((progressEl: HTMLElement) => { progressEl.style.transform = `translate3d(0,0,0) scaleX(${scaleX}) scaleY(${scaleY})` progressEl.style.transitionDuration = `${swiper.params.speed}ms` }) } else { // @ts-ignore subEl.paginationData.progressbar.style = { transform: `translate3d(0,0,0) scaleX(${scaleX}) scaleY(${scaleY})`, transitionDuration: `${swiper.params.speed}ms` } } } if (params.type === 'custom' && params.renderCustom) { subEl.innerHTML = params.renderCustom(swiper, current + 1, total) if (subElIndex === 0) emit('paginationRender', subEl) } else { if (subElIndex === 0) emit('paginationRender', subEl) emit('paginationUpdate', subEl) } if (swiper.params.watchOverflow && swiper.enabled) { subEl.classList[swiper.isLocked ? 'add' : 'remove']( params.lockClass as string ) } }) } function render() { const params = swiper.params.pagination as PaginationOptions if (isPaginationDisabled()) return const slidesLength = swiper.virtual && typeof swiper.params.virtual === 'object' && swiper.params.virtual?.enabled ? swiper.virtual.slides.length : swiper.grid && swiper.params.grid?.rows && swiper.params.grid.rows > 1 ? swiper.slides.length / Math.ceil(swiper.params.grid.rows) : swiper.slides.length let { el } = swiper.pagination // @ts-ignore el = makeElementsArray(el) as HTMLElement[] let paginationHTML = '' const paginationBullet: { classList: ClassList style: Record<string, string> }[] = [] const paginationFraction = { current: { classList: '', content: '' }, total: { classList: '', content: '' } } const paginationProgressbar = { classList: '' } if (params.type === 'bullets') { let numberOfBullets = swiper.params.loop ? Math.ceil(slidesLength / (swiper.params.slidesPerGroup as number)) : swiper.snapGrid.length if ( swiper.params.freeMode && (swiper.params.freeMode as FreeModeOptions).enabled && numberOfBullets > slidesLength ) { numberOfBullets = slidesLength } for (let i = 0; i < numberOfBullets; i += 1) { if (params.renderBullet) { paginationHTML += params.renderBullet.call( swiper, i, params.bulletClass as string ) } else { paginationHTML += `<${params.bulletElement} ${swiper.isElement ? 'part="bullet"' : ''} class="${ params.bulletClass }"></${params.bulletElement}>` if (!isWeb()) { paginationBullet.push({ // @ts-ignore classList: reactive(new ClassList(params.bulletClass)), style: reactive({}) }) } } } } if (params.type === 'fraction') { if (params.renderFraction) { paginationHTML = params.renderFraction.call( swiper, params.currentClass as string, params.totalClass as string ) } else { paginationHTML = `<span class="${params.currentClass}"></span>` + ' / ' + `<span class="${params.totalClass}"></span>` if (!isWeb()) { // @ts-ignore paginationFraction.current.classList = reactive( new ClassList(params.currentClass) ) // @ts-ignore paginationFraction.total.classList = reactive( new ClassList(params.totalClass) ) } } } if (params.type === 'progressbar') { if (params.renderProgressbar) { paginationHTML = params.renderProgressbar.call( swiper, params.progressbarFillClass as string ) } else { paginationHTML = `<span class="${params.progressbarFillClass}"></span>` if (!isWeb()) { // @ts-ignore paginationProgressbar.classList = reactive( new ClassList(params.progressbarFillClass) ) } } } swiper.pagination.bullets = [] // @ts-ignore el.forEach((subEl) => { if (params.type !== 'custom') { subEl.innerHTML = paginationHTML || '' } if (params.type === 'bullets') { if (isWeb()) { swiper.pagination.bullets.push( ...subEl.querySelectorAll(classesToSelector(params.bulletClass)) ) } else { // @ts-ignore swiper.pagination.bullets.push(...paginationBullet) subEl.paginationData.bullets.push(...paginationBullet) } } if (params.type === 'fraction' && !isWeb()) { subEl.paginationData.fractions = paginationFraction } if (params.type === 'progressbar' && !isWeb()) { subEl.paginationData.progressbar = paginationProgressbar } }) if (params.type !== 'custom') { // @ts-ignore emit('paginationRender', el[0]) } } function init() { swiper.params.pagination = createElementIfNotDefined( swiper, // @ts-ignore swiper.originalParams.pagination, swiper.params.pagination, { el: 'swiper-pagination' } ) const params = swiper.params.pagination as PaginationOptions if (!params.el) return let el: HTMLElement | HTMLElement[] | null = null if (typeof params.el === 'string' && swiper.isElement && isWeb()) { el = (swiper.el as HTMLElement).querySelector(params.el) as HTMLElement } if (!el && typeof params.el === 'string' && isWeb()) { el = Array.from(document.querySelectorAll(params.el)) as HTMLElement[] } if (!el) { el = params.el as HTMLElement } if (!el || (el as HTMLElement[]).length === 0) return if ( swiper.params.uniqueNavElements && typeof params.el === 'string' && Array.isArray(el) && el.length > 1 && isWeb() ) { el = Array.from( (swiper.el as HTMLElement).querySelectorAll(params.el) ) as HTMLElement[] if (el.length > 1) { el = el.filter((subEl) => { // @ts-ignore if (elementParents(subEl, '.swiper')[0] !== swiper.el) return false return true })[0] } } if (Array.isArray(el) && el.length === 1) el = el[0] Object.assign(swiper.pagination, { el }) el = makeElementsArray(el) el.forEach((subEl) => { if (params.type === 'bullets' && params.clickable) { subEl.classList.add(...(params.clickableClass || '').split(' ')) } subEl.classList.add( (params.modifierClass as string) + (params.type as string) ) subEl.classList.add( swiper.isHorizontal() ? (params.horizontalClass as string) : (params.verticalClass as string) ) if (params.type === 'bullets' && params.dynamicBullets) { subEl.classList.add(`${params.modifierClass}${params.type}-dynamic`) dynamicBulletIndex = 0 if ((params.dynamicMainBullets as number) < 1) { params.dynamicMainBullets = 1 } } if (params.type === 'progressbar' && params.progressbarOpposite) { subEl.classList.add(params.progressbarOppositeClass as string) } if (params.clickable) { if (isWeb()) { subEl.addEventListener('click', onBulletClick) } else { // @ts-ignore subEl.addEventListener('click', onBulletClick.bind(this), 'onClick') } } if (!swiper.enabled) { subEl.classList.add(params.lockClass as string) } }) } function destroy() { const params = swiper.params.pagination as PaginationOptions if (isPaginationDisabled()) return let { el } = swiper.pagination if (el) { // @ts-ignore el = makeElementsArray(el) as HTMLElement[] // @ts-ignore el.forEach((subEl) => { subEl.classList.remove(params.hiddenClass as string) subEl.classList.remove( (params.modifierClass as string) + (params.type as string) ) subEl.classList.remove( swiper.isHorizontal() ? params.horizontalClass : params.verticalClass ) if (params.clickable) { subEl.classList.remove(...(params.clickableClass || '').split(' ')) if (isWeb()) { subEl.removeEventListener('click', onBulletClick) } else { subEl.removeEventListener( 'click', // @ts-ignore onBulletClick.bind(this), 'onClick' ) } } }) } if (swiper.pagination.bullets) { swiper.pagination.bullets.forEach((subEl) => subEl.classList.remove(...(params.bulletActiveClass || '').split(' ')) ) } } // 添加事件监听 on('changeDirection', () => { if (!swiper.pagination || !swiper.pagination.el) return const params = swiper.params.pagination as PaginationOptions let { el } = swiper.pagination // @ts-ignore el = makeElementsArray(el) as HTMLElement[] // @ts-ignore el.forEach((subEl) => { subEl.classList.remove(params.horizontalClass, params.verticalClass) subEl.classList.add( swiper.isHorizontal() ? params.horizontalClass : params.verticalClass ) }) }) on('init', () => { if ((swiper.params.pagination as PaginationOptions).enabled === false) { disable() } else { init() render() update() } }) on('activeIndexChange', () => { if (typeof swiper.snapIndex === 'undefined') { update() } }) on('snapIndexChange', () => { update() }) on('snapGridLengthChange', () => { render() update() }) on('destroy', () => { destroy() }) on('enable disable', () => { let { el } = swiper.pagination if (el) { // @ts-ignore el = makeElementsArray(el) as HTMLElement[] // @ts-ignore el.forEach((subEl) => subEl.classList[swiper.enabled ? 'remove' : 'add']( (swiper.params.pagination as PaginationOptions).lockClass ) ) } }) on('lock unlock', () => { update() }) on('click', (_s, e) => { const targetEl = e.target as HTMLElement const el = makeElementsArray(swiper.pagination.el) if (isWeb()) { if ( (swiper.params.pagination as PaginationOptions)?.el && (swiper.params.pagination as PaginationOptions).hideOnClick && el && el.length > 0 && !targetEl.classList.contains( (swiper.params.pagination as PaginationOptions).bulletClass as string ) ) { if ( swiper.navigation && ((swiper.navigation.nextEl && targetEl === swiper.navigation.nextEl) || (swiper.navigation.prevEl && targetEl === swiper.navigation.prevEl)) ) return const isHidden = el[0].classList.contains( (swiper.params.pagination as PaginationOptions).hiddenClass as string ) if (isHidden === true) { emit('paginationShow') } else { emit('paginationHide') } el.forEach((subEl) => subEl.classList.toggle( (swiper.params.pagination as PaginationOptions) .hiddenClass as string ) ) } } else { if ( (swiper.params.pagination as PaginationOptions)?.el && (swiper.params.pagination as PaginationOptions).hideOnClick && el && el.length > 0 ) { if ( swiper.navigation && ((swiper.navigation.nextEl && targetEl === swiper.navigation.nextEl) || (swiper.navigation.prevEl && targetEl === swiper.navigation.prevEl)) ) return const isHidden = el[0].classList.contains( (swiper.params.pagination as PaginationOptions).hiddenClass as string ) if (isHidden === true) { emit('paginationShow') } else { emit('paginationHide') } el.forEach((subEl) => subEl.classList.toggle( (swiper.params.pagination as PaginationOptions) .hiddenClass as string ) ) } } }) const enable = () => { swiper.el.classList.remove( (swiper.params.pagination as PaginationOptions) .paginationDisabledClass as string ) let { el } = swiper.pagination if (el) { // @ts-ignore el = makeElementsArray(el) as HTMLElement[] // @ts-ignore el.forEach((subEl) => subEl.classList.remove( (swiper.params.pagination as PaginationOptions) .paginationDisabledClass ) ) } init() render() update() } const disable = () => { swiper.el.classList.add( (swiper.params.pagination as PaginationOptions) .paginationDisabledClass as string ) let { el } = swiper.pagination if (el) { // @ts-ignore el = makeElementsArray(el) as HTMLElement[] // @ts-ignore el.forEach((subEl) => subEl.classList.add( (swiper.params.pagination as PaginationOptions) .paginationDisabledClass ) ) } destroy() } Object.assign(swiper.pagination, { enable, disable, render, update, init, destroy }) }