UNPKG

swiper

Version:

Most modern mobile touch slider and framework with hardware accelerated transitions

391 lines (388 loc) 14.8 kB
import { v as setCSSProperty, e as elementChildren, w as setInnerHTML, a as createElement } from '../shared/utils.mjs'; const Virtual = ({ swiper, extendParams, on, emit }) => { extendParams({ virtual: { enabled: false, slides: [], cache: true, slidesPerViewAutoSlideSize: 320, renderSlide: null, renderExternal: null, renderExternalUpdate: true, addSlidesBefore: 0, addSlidesAfter: 0, }, }); let cssModeTimeout; // Methods (appendSlide/prependSlide/removeSlide/removeAllSlides/update) are // attached via Object.assign below once they're defined. Cast through // Partial<> so the initial state-only literal can be assigned without // pretending the methods exist yet. swiper.virtual = { cache: {}, from: 0, to: 0, slides: [], offset: 0, slidesGrid: [], }; function getParams() { return swiper.params.virtual; } // Created lazily so module init does not touch the DOM (SSR / Node safe). let tempDOM; const getTempDOM = () => (tempDOM ??= document.createElement('div')); function renderSlide(slide, index) { const params = getParams(); if (params.cache && swiper.virtual.cache[index]) { return swiper.virtual.cache[index]; } let slideEl; if (params.renderSlide) { const rendered = params.renderSlide.call(swiper, slide, index); if (typeof rendered === 'string') { const el = getTempDOM(); setInnerHTML(el, rendered); slideEl = el.children[0]; } else { slideEl = rendered; } } else if (swiper.isElement) { slideEl = createElement('swiper-slide'); } else { slideEl = createElement('div', swiper.params.slideClass); } slideEl.setAttribute('data-swiper-slide-index', String(index)); if (!params.renderSlide) { setInnerHTML(slideEl, slide); } if (params.cache) { swiper.virtual.cache[index] = slideEl; } return slideEl; } function update(force, beforeInit, forceActiveIndex) { const { slidesPerGroup, centeredSlides, slidesPerView, loop: isLoop, initialSlide, } = swiper.params; if (beforeInit && !isLoop && (initialSlide ?? 0) > 0) { return; } const { addSlidesBefore, addSlidesAfter, slidesPerViewAutoSlideSize } = getParams(); const { from: previousFrom, to: previousTo, slides, slidesGrid: previousSlidesGrid, offset: previousOffset, } = swiper.virtual; if (!swiper.params.cssMode) { swiper.updateActiveIndex(); } const activeIndex = typeof forceActiveIndex === 'undefined' ? swiper.activeIndex || 0 : forceActiveIndex; let offsetProp; if (swiper.rtlTranslate) offsetProp = 'right'; else offsetProp = swiper.isHorizontal() ? 'left' : 'top'; let slidesPerViewNumeric; if (slidesPerView === 'auto') { if (slidesPerViewAutoSlideSize) { let swiperSize = swiper.size; if (!swiperSize) { swiperSize = swiper.isHorizontal() ? swiper.el.getBoundingClientRect().width : swiper.el.getBoundingClientRect().height; } slidesPerViewNumeric = Math.max(1, Math.ceil(swiperSize / slidesPerViewAutoSlideSize)); } else { slidesPerViewNumeric = 1; } } else { slidesPerViewNumeric = slidesPerView ?? 1; } const groupSize = slidesPerGroup ?? 1; let slidesAfter; let slidesBefore; if (centeredSlides) { slidesAfter = Math.floor(slidesPerViewNumeric / 2) + groupSize + addSlidesAfter; slidesBefore = Math.floor(slidesPerViewNumeric / 2) + groupSize + addSlidesBefore; } else { slidesAfter = slidesPerViewNumeric + (groupSize - 1) + addSlidesAfter; slidesBefore = (isLoop ? slidesPerViewNumeric : groupSize) + addSlidesBefore; } 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.setProperty(offsetProp, `${offset - Math.abs(swiper.cssOverflowAdjustment())}px`); }); } swiper.updateProgress(); emit('virtualUpdate'); return; } const virtualParams = getParams(); if (virtualParams.renderExternal) { const slidesToRender = []; for (let i = from; i <= to; i += 1) { slidesToRender.push(slides[i]); } virtualParams.renderExternal.call(swiper, { offset, from, to, slides: slidesToRender, }); if (virtualParams.renderExternalUpdate) { onRendered(); } else { emit('virtualUpdate'); } return; } const prependIndexes = []; const appendIndexes = []; const getSlideIndex = (index) => { 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.matches(`.${swiper.params.slideClass}, swiper-slide`)) .forEach((slideEl) => { slideEl.remove(); }); } else { for (let i = previousFrom; i <= previousTo; i += 1) { if (i < from || i > to) { const slideIndex = getSlideIndex(i); swiper.slides .filter((el) => el.matches(`.${swiper.params.slideClass}[data-swiper-slide-index="${slideIndex}"], swiper-slide[data-swiper-slide-index="${slideIndex}"]`)) .forEach((slideEl) => { slideEl.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.setProperty(offsetProp, `${offset - Math.abs(swiper.cssOverflowAdjustment())}px`); }); onRendered(); } function appendSlide(slides) { if (slides !== null && typeof slides === 'object' && 'length' in slides) { const arr = slides; for (let i = 0; i < arr.length; i += 1) { if (arr[i]) swiper.virtual.slides.push(arr[i]); } } else { swiper.virtual.slides.push(slides); } update(true); } function prependSlide(slides) { const activeIndex = swiper.activeIndex; 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 (getParams().cache) { const cache = swiper.virtual.cache; const newCache = {}; Object.keys(cache).forEach((cachedIndex) => { const cachedEl = cache[Number(cachedIndex)]; const cachedElIndex = cachedEl.getAttribute('data-swiper-slide-index'); if (cachedElIndex) { cachedEl.setAttribute('data-swiper-slide-index', String(parseInt(cachedElIndex, 10) + numberOfNewSlides)); } newCache[parseInt(cachedIndex, 10) + numberOfNewSlides] = cachedEl; }); swiper.virtual.cache = newCache; } update(true); swiper.slideTo(newActiveIndex, 0); } function removeSlide(slidesIndexes) { if (typeof slidesIndexes === 'undefined' || slidesIndexes === null) return; let activeIndex = swiper.activeIndex; const shiftCacheDownFrom = (removedIndex) => { Object.keys(swiper.virtual.cache).forEach((key) => { const numericKey = Number(key); if (numericKey > removedIndex) { const shifted = swiper.virtual.cache[numericKey]; swiper.virtual.cache[numericKey - 1] = shifted; shifted.setAttribute('data-swiper-slide-index', String(numericKey - 1)); delete swiper.virtual.cache[numericKey]; } }); }; if (Array.isArray(slidesIndexes)) { for (let i = slidesIndexes.length - 1; i >= 0; i -= 1) { if (getParams().cache) { delete swiper.virtual.cache[slidesIndexes[i]]; shiftCacheDownFrom(slidesIndexes[i]); } swiper.virtual.slides.splice(slidesIndexes[i], 1); if (slidesIndexes[i] < activeIndex) activeIndex -= 1; activeIndex = Math.max(activeIndex, 0); } } else { if (getParams().cache) { delete swiper.virtual.cache[slidesIndexes]; shiftCacheDownFrom(slidesIndexes); } swiper.virtual.slides.splice(slidesIndexes, 1); if (slidesIndexes < activeIndex) activeIndex -= 1; activeIndex = Math.max(activeIndex, 0); } update(true); swiper.slideTo(activeIndex, 0); } function removeAllSlides() { swiper.virtual.slides = []; if (getParams().cache) { swiper.virtual.cache = {}; } update(true); swiper.slideTo(0, 0); } on('beforeInit', () => { if (!getParams().enabled) return; let domSlidesAssigned = false; const passedVirtual = swiper.passedParams.virtual; const passedSlidesUndefined = !passedVirtual || typeof passedVirtual !== 'object' || passedVirtual.slides === undefined; if (passedSlidesUndefined) { const slides = [...swiper.slidesEl.children].filter((el) => el.matches(`.${swiper.params.slideClass}, swiper-slide`)); if (slides && slides.length) { swiper.virtual.slides = [...slides]; domSlidesAssigned = true; slides.forEach((slideEl, slideIndex) => { slideEl.setAttribute('data-swiper-slide-index', String(slideIndex)); swiper.virtual.cache[slideIndex] = slideEl; slideEl.remove(); }); } } if (!domSlidesAssigned) { swiper.virtual.slides = getParams().slides; } swiper.classNames.push(`${swiper.params.containerModifierClass}virtual`); swiper.params.watchSlidesProgress = true; swiper.originalParams.watchSlidesProgress = true; update(false, true); }); on('setTranslate', () => { if (!getParams().enabled) return; if (swiper.params.cssMode && !swiper._immediateVirtual) { clearTimeout(cssModeTimeout); cssModeTimeout = setTimeout(() => { update(); }, 100); } else { update(); } }); on('init update resize', () => { if (!getParams().enabled) return; if (swiper.params.cssMode) { setCSSProperty(swiper.wrapperEl, '--swiper-virtual-size', `${swiper.virtualSize}px`); } }); Object.assign(swiper.virtual, { appendSlide, prependSlide, removeSlide, removeAllSlides, update, }); }; export { Virtual as default };