UNPKG

@zebra-ui/swiper

Version:

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

487 lines (438 loc) 13.8 kB
import { createElement, elementChildren, isWeb, setCSSProperty } from '../../components/shared/utils' import type { VirtualOptions, VirtualMethods } from '../../types/modules/virtual' import type { SwiperInterface } from '../../types/swiper-class' export default function Virtual({ 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 { extendParams({ virtual: { enabled: false, slides: [], data: [], cache: true, renderSlide: null, renderExternal: null, renderExternalUpdate: true, addSlidesBefore: 0, addSlidesAfter: 0 } as unknown as VirtualOptions }) let cssModeTimeout: number | null swiper.virtual = { cache: {}, from: undefined, to: undefined, slides: [], data: [], offset: 0, slidesGrid: [] } as unknown as VirtualMethods function renderSlide(slide: any, index: number): HTMLElement { const params = swiper.params.virtual as VirtualOptions // @ts-ignore if (params.cache && swiper.virtual.cache[index]) { // @ts-ignore return swiper.virtual.cache[index] } let slideEl: HTMLElement if (params.renderSlide) { slideEl = params.renderSlide.call(swiper, slide, index) if (typeof slideEl === 'string') { const tempDOM = document.createElement('div') tempDOM.innerHTML = slideEl slideEl = tempDOM.children[0] as HTMLElement } } else if (swiper.isElement) { slideEl = createElement('swiper-slide') as HTMLElement } else { slideEl = createElement('div', swiper.params.slideClass) as HTMLElement } slideEl.setAttribute('data-swiper-slide-index', String(index)) if (!params.renderSlide) { slideEl.innerHTML = slide } if (params.cache) { // @ts-ignore swiper.virtual.cache[index] = slideEl } return slideEl } function update(force?: boolean, beforeInit?: boolean) { const { slidesPerView, slidesPerGroup, centeredSlides, loop: isLoop, initialSlide } = swiper.params if (beforeInit && !isLoop && (initialSlide as number) > 0) { return } const { addSlidesBefore, addSlidesAfter } = swiper.params .virtual as VirtualOptions const { from: previousFrom, to: previousTo, slides, slidesGrid: previousSlidesGrid, offset: previousOffset, data } = swiper.virtual if (!swiper.params.cssMode) { swiper.updateActiveIndex() } const activeIndex = swiper.activeIndex || 0 let offsetProp: 'left' | 'right' | 'top' if (swiper.rtlTranslate) offsetProp = 'right' else offsetProp = swiper.isHorizontal() ? 'left' : 'top' let slidesAfter: number let slidesBefore: number if (centeredSlides) { slidesAfter = Math.floor((slidesPerView as number) / 2) + (slidesPerGroup as number) + (addSlidesAfter as number) slidesBefore = Math.floor((slidesPerView as number) / 2) + (slidesPerGroup as number) + (addSlidesBefore as number) } else { slidesAfter = (slidesPerView as number) + (slidesPerGroup as number) - 1 + (addSlidesAfter as number) slidesBefore = (isLoop ? (slidesPerView as number) : (slidesPerGroup as number)) + (addSlidesBefore as number) } let from = activeIndex - slidesBefore let to = activeIndex + slidesAfter if (!isLoop) { from = Math.max(from, 0) to = Math.min(to, slides.length - 1) } let offset = (swiper.slidesGrid[from] || 0) - (swiper.slidesGrid[0] || 0) if (isLoop && activeIndex >= slidesBefore) { from -= slidesBefore if (!centeredSlides) offset += swiper.slidesGrid[0] } else if (isLoop && activeIndex < slidesBefore) { from = -slidesBefore if (centeredSlides) offset += swiper.slidesGrid[0] } Object.assign(swiper.virtual, { from, to, offset, slidesGrid: swiper.slidesGrid, slidesBefore, slidesAfter }) function onRendered() { swiper.updateSlides() swiper.updateProgress() swiper.updateSlidesClasses() emit('virtualUpdate') } if (previousFrom === from && previousTo === to && !force) { if ( swiper.slidesGrid !== previousSlidesGrid && offset !== previousOffset ) { swiper.slides.forEach((slideEl) => { slideEl.style[offsetProp] = `${offset - Math.abs(swiper.cssOverflowAdjustment())}px` }) } swiper.updateProgress() emit('virtualUpdate') return } if ((swiper.params.virtual as VirtualOptions)?.renderExternal) { ;(swiper.params.virtual as VirtualOptions).renderExternal?.call(swiper, { offset, from, to, slides: (function getSlides() { const slidesToRender = [] for (let i = from; i <= to; i += 1) { slidesToRender.push(slides[i]) } return slidesToRender })(), data: (function getSlides() { const slidesToRender = [] for (let i = from; i <= to; i += 1) { // @ts-ignore slidesToRender.push(data[i]) } return slidesToRender })() }) if ((swiper.params.virtual as VirtualOptions)?.renderExternalUpdate) { onRendered() } else { emit('virtualUpdate') } return } const prependIndexes: number[] = [] const appendIndexes: number[] = [] const getSlideIndex = (index: number): number => { let slideIndex = index if (index < 0) { slideIndex = slides.length + index } else if (slideIndex >= slides.length) { slideIndex = slideIndex - slides.length } return slideIndex } if (force) { swiper.slides .filter((el) => (el as HTMLElement).matches( `.${swiper.params.slideClass}, swiper-slide` ) ) .forEach((slideEl) => { ;(slideEl as HTMLElement).remove() }) } else { for (let i = previousFrom; i <= previousTo; i += 1) { if (i < from || i > to) { const slideIndex = getSlideIndex(i) swiper.slides .filter((el) => (el as HTMLElement).matches( `.${swiper.params.slideClass}[data-swiper-slide-index="${slideIndex}"], swiper-slide[data-swiper-slide-index="${slideIndex}"]` ) ) .forEach((slideEl) => { ;(slideEl as HTMLElement).remove() }) } } } const loopFrom = isLoop ? -slides.length : 0 const loopTo = isLoop ? slides.length * 2 : slides.length for (let i = loopFrom; i < loopTo; i += 1) { if (i >= from && i <= to) { const slideIndex = getSlideIndex(i) if (typeof previousTo === 'undefined' || force) { appendIndexes.push(slideIndex) } else { if (i > previousTo) appendIndexes.push(slideIndex) if (i < previousFrom) prependIndexes.push(slideIndex) } } } appendIndexes.forEach((index) => { swiper.slidesEl.append(renderSlide(slides[index], index)) }) if (isLoop) { for (let i = prependIndexes.length - 1; i >= 0; i -= 1) { const index = prependIndexes[i] swiper.slidesEl.prepend(renderSlide(slides[index], index)) } } else { prependIndexes.sort((a, b) => b - a) prependIndexes.forEach((index) => { swiper.slidesEl.prepend(renderSlide(slides[index], index)) }) } elementChildren(swiper.slidesEl, '.swiper-slide, swiper-slide').forEach( (slideEl) => { slideEl.style[offsetProp] = `${offset - Math.abs(swiper.cssOverflowAdjustment())}px` } ) onRendered() } function appendSlide(slides: HTMLElement | HTMLElement[]): void { if (typeof slides === 'object' && 'length' in slides) { for (let i = 0; i < slides.length; i += 1) { if (slides[i]) swiper.virtual.slides.push(slides[i]) } } else { swiper.virtual.slides.push(slides) } update(true) } function prependSlide(slides: HTMLElement | HTMLElement[]): void { const { activeIndex } = swiper let newActiveIndex = activeIndex + 1 let numberOfNewSlides = 1 if (Array.isArray(slides)) { for (let i = 0; i < slides.length; i += 1) { if (slides[i]) swiper.virtual.slides.unshift(slides[i]) } newActiveIndex = activeIndex + slides.length numberOfNewSlides = slides.length } else { swiper.virtual.slides.unshift(slides) } if ((swiper.params.virtual as VirtualOptions).cache) { const { cache } = swiper.virtual const newCache: Record<number, HTMLElement> = {} Object.keys(cache).forEach((cachedIndex) => { // @ts-ignore const cachedEl = cache[cachedIndex] const cachedElIndex = cachedEl.getAttribute('data-swiper-slide-index') if (cachedElIndex) { cachedEl.setAttribute( 'data-swiper-slide-index', String(Number(cachedElIndex) + numberOfNewSlides) ) } newCache[Number(cachedIndex) + numberOfNewSlides] = cachedEl }) swiper.virtual.cache = newCache } update(true) swiper.slideTo(newActiveIndex, 0) } function removeSlide(slideIndexes: number | number[]): void { if (typeof slideIndexes === 'undefined' || slideIndexes === null) return let { activeIndex } = swiper if (Array.isArray(slideIndexes)) { for (let i = slideIndexes.length - 1; i >= 0; i -= 1) { if ((swiper.params.virtual as VirtualOptions).cache) { // @ts-ignore delete swiper.virtual.cache[slideIndexes[i]] Object.keys(swiper.virtual.cache).forEach((key) => { // @ts-ignore if (key > slidesIndexes) { // @ts-ignore swiper.virtual.cache[key - 1] = swiper.virtual.cache[key] // @ts-ignore swiper.virtual.cache[key - 1].setAttribute( 'data-swiper-slide-index', // @ts-ignore key - 1 ) // @ts-ignore delete swiper.virtual.cache[key] } }) } swiper.virtual.slides.splice(slideIndexes[i], 1) if (slideIndexes[i] < activeIndex) activeIndex -= 1 activeIndex = Math.max(activeIndex, 0) } } else { if ((swiper.params.virtual as VirtualOptions).cache) { // @ts-ignore delete swiper.virtual.cache[slideIndexes] // shift cache indexes Object.keys(swiper.virtual.cache).forEach((key) => { // @ts-ignore if (key > slidesIndexes) { // @ts-ignore swiper.virtual.cache[key - 1] = swiper.virtual.cache[key] // @ts-ignore swiper.virtual.cache[key - 1].setAttribute( 'data-swiper-slide-index', // @ts-ignore key - 1 ) // @ts-ignore delete swiper.virtual.cache[key] } }) } swiper.virtual.slides.splice(slideIndexes, 1) if (slideIndexes < activeIndex) activeIndex -= 1 activeIndex = Math.max(activeIndex, 0) } update(true) swiper.slideTo(activeIndex, 0) } function removeAllSlides(): void { swiper.virtual.slides = [] if ((swiper.params.virtual as VirtualOptions).cache) { swiper.virtual.cache = {} } update(true) swiper.slideTo(0, 0) } // Event handlers on('beforeInit', () => { if (!(swiper.params.virtual as VirtualOptions).enabled) return let domSlidesAssigned: boolean // @ts-ignore if (typeof swiper.passedParams.virtual?.slides === 'undefined') { const slides = Array.from(swiper.slidesEl.children).filter((el) => { if (isWeb()) { return el.matches(`.${swiper.params.slideClass}, swiper-slide`) } return el }) if (slides && slides.length) { domSlidesAssigned = true slides.forEach((slideEl, slideIndex) => { slideEl.setAttribute('data-swiper-slide-index', String(slideIndex)) // @ts-ignore swiper.virtual.cache[slideIndex] = slideEl if (isWeb()) { slideEl.remove() } }) } } // @ts-ignore if (!domSlidesAssigned) { swiper.virtual.slides = (swiper.params.virtual as VirtualOptions).slides || [] } swiper.classNames.push(`${swiper.params.containerModifierClass}virtual`) swiper.params.watchSlidesProgress = true swiper.originalParams.watchSlidesProgress = true update(false, true) }) on('setTranslate', () => { if (!(swiper.params.virtual as VirtualOptions).enabled) return // @ts-ignore if (swiper.params.cssMode && !swiper._immediateVirtual) { clearTimeout(cssModeTimeout as number) cssModeTimeout = setTimeout(() => { update() }, 100) } else { update() } }) on('init update resize', () => { if (!(swiper.params.virtual as VirtualOptions).enabled) return if (swiper.params.cssMode) { setCSSProperty( swiper.wrapperEl, '--swiper-virtual-size', `${swiper.virtualSize}px` ) } }) // Export methods Object.assign(swiper.virtual, { appendSlide, prependSlide, removeSlide, removeAllSlides, update }) }