From b5db22392f6c6609de292c00d163f9230d0925ee Mon Sep 17 00:00:00 2001 From: Vladimir Kharlampidi Date: Mon, 2 Oct 2023 16:23:54 +0300 Subject: [PATCH] feat(core): loop support for grid, new `loopAddBlankSlides` parameter --- src/components-shared/params-list.mjs | 2 + src/core/core.mjs | 17 +++++ src/core/defaults.mjs | 2 + src/core/loop/loopCreate.mjs | 57 +++++++++++++--- src/core/loop/loopFix.mjs | 91 +++++++++++++++++-------- src/core/slide/slideToLoop.mjs | 37 +++++++--- src/core/update/updateActiveIndex.mjs | 24 +++++-- src/core/update/updateSlides.mjs | 36 ++++------ src/core/update/updateSlidesClasses.mjs | 52 +++++++++----- src/modules/grid/grid.mjs | 36 ++++++++-- src/modules/pagination/pagination.mjs | 2 + src/shared/utils.mjs | 10 ++- src/swiper-vue.d.ts | 5 ++ src/types/modules/grid.d.ts | 4 +- src/types/swiper-options.d.ts | 24 ++++++- src/vue/swiper.mjs | 1 + 16 files changed, 298 insertions(+), 102 deletions(-) diff --git a/src/components-shared/params-list.mjs b/src/components-shared/params-list.mjs index 9ca0b0251..1b10f8207 100644 --- a/src/components-shared/params-list.mjs +++ b/src/components-shared/params-list.mjs @@ -69,6 +69,7 @@ const paramsList = [ '_slideToClickedSlide', '_loop', 'loopedSlides', + 'loopAddBlankSlides', 'loopPreventsSliding', '_rewind', '_allowSlidePrev', @@ -85,6 +86,7 @@ const paramsList = [ 'slideFullyVisibleClass', 'slideNextClass', 'slidePrevClass', + 'slideBlankClass', 'wrapperClass', 'lazyPreloaderClass', 'lazyPreloadPrevNext', diff --git a/src/core/core.mjs b/src/core/core.mjs index 826a81df1..c185b8d20 100644 --- a/src/core/core.mjs +++ b/src/core/core.mjs @@ -228,6 +228,23 @@ class Swiper { return swiper; } + getDirectionLabel(property) { + if (this.isHorizontal()) { + return property; + } + // prettier-ignore + return { + 'width': 'height', + 'margin-top': 'margin-left', + 'margin-bottom ': 'margin-right', + 'margin-left': 'margin-top', + 'margin-right': 'margin-bottom', + 'padding-left': 'padding-top', + 'padding-right': 'padding-bottom', + 'marginRight': 'marginBottom', + }[property]; + } + getSlideIndex(slideEl) { const { slidesEl, params } = this; const slides = elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`); diff --git a/src/core/defaults.mjs b/src/core/defaults.mjs index 4db99e4ce..ae9a91ad2 100644 --- a/src/core/defaults.mjs +++ b/src/core/defaults.mjs @@ -100,6 +100,7 @@ export default { // loop loop: false, + loopAddBlankSlides: true, loopedSlides: null, loopPreventsSliding: true, @@ -122,6 +123,7 @@ export default { // NS containerModifierClass: 'swiper-', // NEW slideClass: 'swiper-slide', + slideBlankClass: 'swiper-slide-blank', slideActiveClass: 'swiper-slide-active', slideVisibleClass: 'swiper-slide-visible', slideFullyVisibleClass: 'swiper-slide-fully-visible', diff --git a/src/core/loop/loopCreate.mjs b/src/core/loop/loopCreate.mjs index c1a304863..ece2b695a 100644 --- a/src/core/loop/loopCreate.mjs +++ b/src/core/loop/loopCreate.mjs @@ -1,24 +1,61 @@ -import { elementChildren } from '../../shared/utils.mjs'; +import { createElement, elementChildren, showWarning } from '../../shared/utils.mjs'; export default function loopCreate(slideRealIndex) { const swiper = this; const { params, slidesEl } = swiper; if (!params.loop || (swiper.virtual && swiper.params.virtual.enabled)) return; - const slides = elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`); + const initSlides = () => { + const slides = elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`); - slides.forEach((el, index) => { - el.setAttribute('data-swiper-slide-index', index); - }); + slides.forEach((el, index) => { + el.setAttribute('data-swiper-slide-index', index); + }); + }; - if (swiper.slides.length % params.slidesPerGroup !== 0) { - try { - console.warn( + const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1; + + const slidesPerGroup = params.slidesPerGroup * (gridEnabled ? params.grid.rows : 1); + + const shouldFillGroup = swiper.slides.length % slidesPerGroup !== 0; + const shouldFillGrid = gridEnabled && swiper.slides.length % params.grid.rows !== 0; + + const addBlankSlides = (amountOfSlides) => { + for (let i = 0; i < amountOfSlides; i += 1) { + const slideEl = swiper.isElement + ? createElement('swiper-slide', [params.slideBlankClass]) + : createElement('div', [params.slideClass, params.slideBlankClass]); + swiper.slidesEl.append(slideEl); + } + }; + + if (shouldFillGroup) { + if (params.loopAddBlankSlides) { + const slidesToAdd = slidesPerGroup - (swiper.slides.length % slidesPerGroup); + addBlankSlides(slidesToAdd); + swiper.recalcSlides(); + swiper.updateSlides(); + } else { + showWarning( 'Swiper Loop Warning: The number of slides is not even to slidesPerGroup, loop mode may not function properly. You need to add more slides (or make duplicates, or empty slides)', ); - } catch (err) { - // err } + + initSlides(); + } else if (shouldFillGrid) { + if (params.loopAddBlankSlides) { + const slidesToAdd = params.grid.rows - (swiper.slides.length % params.grid.rows); + addBlankSlides(slidesToAdd); + swiper.recalcSlides(); + swiper.updateSlides(); + } else { + showWarning( + 'Swiper Loop Warning: The number of slides is not even to grid.rows, loop mode may not function properly. You need to add more slides (or make duplicates, or empty slides)', + ); + } + initSlides(); + } else { + initSlides(); } swiper.loopFix({ slideRealIndex, direction: params.centeredSlides ? undefined : 'next' }); diff --git a/src/core/loop/loopFix.mjs b/src/core/loop/loopFix.mjs index 28597b655..f1af6c6cc 100644 --- a/src/core/loop/loopFix.mjs +++ b/src/core/loop/loopFix.mjs @@ -1,3 +1,5 @@ +import { showWarning } from '../../shared/utils.mjs'; + export default function loopFix({ slideRealIndex, slideTo = true, @@ -45,16 +47,14 @@ export default function loopFix({ loopedSlides += params.slidesPerGroup - (loopedSlides % params.slidesPerGroup); } swiper.loopedSlides = loopedSlides; + const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1; - if (swiper.slides.length < slidesPerView + loopedSlides) { - try { - console.warn( - 'Swiper Loop Warning: The number of slides is not enough for loop mode, it will be disabled and not function properly. You need to add more slides (or make duplicates) or lower the values of slidesPerView and slidesPerGroup parameters', - ); - return; - } catch (err) { - // err - } + if (slides.length < slidesPerView + loopedSlides) { + showWarning( + 'Swiper Loop Warning: The number of slides is not enough for loop mode, it will be disabled and not function properly. You need to add more slides (or make duplicates) or lower the values of slidesPerView and slidesPerGroup parameters', + ); + } else if (gridEnabled && params.grid.fill === 'row') { + showWarning('Swiper Loop Warning: Loop mode is not compatible with grid.fill = `row`'); } const prependSlidesIndexes = []; @@ -64,7 +64,7 @@ export default function loopFix({ if (typeof activeSlideIndex === 'undefined') { activeSlideIndex = swiper.getSlideIndex( - swiper.slides.filter((el) => el.classList.contains(params.slideActiveClass))[0], + slides.filter((el) => el.classList.contains(params.slideActiveClass))[0], ); } else { activeIndex = activeSlideIndex; @@ -75,46 +75,71 @@ export default function loopFix({ let slidesPrepended = 0; let slidesAppended = 0; - const activeSlideIndexWithShift = - activeSlideIndex + + + const cols = gridEnabled ? Math.ceil(slides.length / params.grid.rows) : slides.length; + const activeColIndex = gridEnabled ? slides[activeSlideIndex].column : activeSlideIndex; + const activeColIndexWithShift = + activeColIndex + (centeredSlides && typeof setTranslate === 'undefined' ? -slidesPerView / 2 + 0.5 : 0); // prepend last slides before start - if (activeSlideIndexWithShift < loopedSlides) { - slidesPrepended = Math.max(loopedSlides - activeSlideIndexWithShift, params.slidesPerGroup); - for (let i = 0; i < loopedSlides - activeSlideIndexWithShift; i += 1) { - const index = i - Math.floor(i / slides.length) * slides.length; - prependSlidesIndexes.push(slides.length - index - 1); + if (activeColIndexWithShift < loopedSlides) { + slidesPrepended = Math.max(loopedSlides - activeColIndexWithShift, params.slidesPerGroup); + for (let i = 0; i < loopedSlides - activeColIndexWithShift; i += 1) { + const index = i - Math.floor(i / cols) * cols; + if (gridEnabled) { + const colIndexToPrepend = cols - index - 1; + for (let i = slides.length - 1; i >= 0; i -= 1) { + if (slides[i].column === colIndexToPrepend) prependSlidesIndexes.push(i); + } + // slides.forEach((slide, slideIndex) => { + // if (slide.column === colIndexToPrepend) prependSlidesIndexes.push(slideIndex); + // }); + } else { + prependSlidesIndexes.push(cols - index - 1); + } } - } else if (activeSlideIndexWithShift + slidesPerView > swiper.slides.length - loopedSlides) { + } else if (activeColIndexWithShift + slidesPerView > cols - loopedSlides) { slidesAppended = Math.max( - activeSlideIndexWithShift - (swiper.slides.length - loopedSlides * 2), + activeColIndexWithShift - (cols - loopedSlides * 2), params.slidesPerGroup, ); for (let i = 0; i < slidesAppended; i += 1) { - const index = i - Math.floor(i / slides.length) * slides.length; - appendSlidesIndexes.push(index); + const index = i - Math.floor(i / cols) * cols; + if (gridEnabled) { + slides.forEach((slide, slideIndex) => { + if (slide.column === index) appendSlidesIndexes.push(slideIndex); + }); + } else { + appendSlidesIndexes.push(index); + } } } - if (isPrev) { prependSlidesIndexes.forEach((index) => { - swiper.slides[index].swiperLoopMoveDOM = true; + slides[index].swiperLoopMoveDOM = true; - slidesEl.prepend(swiper.slides[index]); - swiper.slides[index].swiperLoopMoveDOM = false; + slidesEl.prepend(slides[index]); + slides[index].swiperLoopMoveDOM = false; }); } if (isNext) { appendSlidesIndexes.forEach((index) => { - swiper.slides[index].swiperLoopMoveDOM = true; - slidesEl.append(swiper.slides[index]); - swiper.slides[index].swiperLoopMoveDOM = false; + slides[index].swiperLoopMoveDOM = true; + slidesEl.append(slides[index]); + slides[index].swiperLoopMoveDOM = false; }); } swiper.recalcSlides(); if (params.slidesPerView === 'auto') { swiper.updateSlides(); + } else if ( + gridEnabled && + ((prependSlidesIndexes.length > 0 && isPrev) || (appendSlidesIndexes.length > 0 && isNext)) + ) { + swiper.slides.forEach((slide, slideIndex) => { + swiper.grid.updateSlide(slideIndex, slide, swiper.slides); + }); } if (params.watchSlidesProgress) { swiper.updateSlidesOffset(); @@ -137,7 +162,10 @@ export default function loopFix({ } } else { if (setTranslate) { - swiper.slideTo(swiper.activeIndex + prependSlidesIndexes.length, 0, false, true); + const shift = gridEnabled + ? prependSlidesIndexes.length / params.grid.rows + : prependSlidesIndexes.length; + swiper.slideTo(swiper.activeIndex + shift, 0, false, true); swiper.touchEventsData.currentTranslate = swiper.translate; } } @@ -157,7 +185,10 @@ export default function loopFix({ } } } else { - swiper.slideTo(swiper.activeIndex - appendSlidesIndexes.length, 0, false, true); + const shift = gridEnabled + ? appendSlidesIndexes.length / params.grid.rows + : appendSlidesIndexes.length; + swiper.slideTo(swiper.activeIndex - shift, 0, false, true); } } } diff --git a/src/core/slide/slideToLoop.mjs b/src/core/slide/slideToLoop.mjs index c898e266a..1c7ae2721 100644 --- a/src/core/slide/slideToLoop.mjs +++ b/src/core/slide/slideToLoop.mjs @@ -9,16 +9,28 @@ export default function slideToLoop( index = indexAsNumber; } - const swiper = this; + const gridEnabled = swiper.grid && swiper.params.grid && swiper.params.grid.rows > 1; let newIndex = index; if (swiper.params.loop) { if (swiper.virtual && swiper.params.virtual.enabled) { // eslint-disable-next-line newIndex = newIndex + swiper.virtual.slidesBefore; } else { - const targetSlideIndex = swiper.getSlideIndexByData(newIndex); - const slides = swiper.slides.length; + let targetSlideIndex; + if (gridEnabled) { + const slideIndex = newIndex * swiper.params.grid.rows; + targetSlideIndex = swiper.slides.filter( + (slideEl) => slideEl.getAttribute('data-swiper-slide-index') * 1 === slideIndex, + )[0].column; + } else { + targetSlideIndex = swiper.getSlideIndexByData(newIndex); + } + + const cols = gridEnabled + ? Math.ceil(swiper.slides.length / swiper.params.grid.rows) + : swiper.slides.length; + const { centeredSlides } = swiper.params; let slidesPerView = swiper.params.slidesPerView; if (slidesPerView === 'auto') { @@ -29,7 +41,7 @@ export default function slideToLoop( slidesPerView = slidesPerView + 1; } } - let needLoopFix = slides - targetSlideIndex < slidesPerView; + let needLoopFix = cols - targetSlideIndex < slidesPerView; if (centeredSlides) { needLoopFix = needLoopFix || targetSlideIndex < Math.ceil(slidesPerView / 2); } @@ -41,19 +53,28 @@ export default function slideToLoop( : targetSlideIndex - swiper.activeIndex - 1 < swiper.params.slidesPerView ? 'next' : 'prev'; - swiper.loopFix({ direction, slideTo: true, activeSlideIndex: - direction === 'next' ? targetSlideIndex + 1 : targetSlideIndex - slides + 1, + direction === 'next' ? targetSlideIndex + 1 : targetSlideIndex - cols + 1, slideRealIndex: direction === 'next' ? swiper.realIndex : undefined, }); } - newIndex = swiper.getSlideIndexByData(newIndex); + if (gridEnabled) { + const slideIndex = newIndex * swiper.params.grid.rows; + newIndex = swiper.slides.filter( + (slideEl) => slideEl.getAttribute('data-swiper-slide-index') * 1 === slideIndex, + )[0].column; + } else { + newIndex = swiper.getSlideIndexByData(newIndex); + } } } - return swiper.slideTo(newIndex, speed, runCallbacks, internal); + requestAnimationFrame(() => { + swiper.slideTo(newIndex, speed, runCallbacks, internal); + }); + return; } diff --git a/src/core/update/updateActiveIndex.mjs b/src/core/update/updateActiveIndex.mjs index 1582ded7f..cc243d900 100644 --- a/src/core/update/updateActiveIndex.mjs +++ b/src/core/update/updateActiveIndex.mjs @@ -74,15 +74,31 @@ export default function updateActiveIndex(newActiveIndex) { return; } + const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1; + + const normalizeSlideIndexToColumn = (index) => { + if (!gridEnabled) return index; + return Math.floor(index / params.grid.rows); + }; + // Get real index let realIndex; if (swiper.virtual && params.virtual.enabled && params.loop) { realIndex = getVirtualRealIndex(activeIndex); + } else if (gridEnabled) { + const firstSlideInColumn = swiper.slides.filter((slideEl) => slideEl.column === activeIndex)[0]; + let activeSlideIndex = parseInt(firstSlideInColumn.getAttribute('data-swiper-slide-index'), 10); + if (Number.isNaN(activeSlideIndex)) { + activeSlideIndex = Math.max(swiper.slides.indexOf(firstSlideInColumn), 0); + } + realIndex = Math.floor(activeSlideIndex / params.grid.rows); } else if (swiper.slides[activeIndex]) { - realIndex = parseInt( - swiper.slides[activeIndex].getAttribute('data-swiper-slide-index') || activeIndex, - 10, - ); + const slideIndex = swiper.slides[activeIndex].getAttribute('data-swiper-slide-index'); + if (slideIndex) { + realIndex = parseInt(slideIndex, 10); + } else { + realIndex = activeIndex; + } } else { realIndex = activeIndex; } diff --git a/src/core/update/updateSlides.mjs b/src/core/update/updateSlides.mjs index 7b31d52b6..262fcb9a0 100644 --- a/src/core/update/updateSlides.mjs +++ b/src/core/update/updateSlides.mjs @@ -7,24 +7,9 @@ import { export default function updateSlides() { const swiper = this; - function getDirectionLabel(property) { - if (swiper.isHorizontal()) { - return property; - } - // prettier-ignore - return { - 'width': 'height', - 'margin-top': 'margin-left', - 'margin-bottom ': 'margin-right', - 'margin-left': 'margin-top', - 'margin-right': 'margin-bottom', - 'padding-left': 'padding-top', - 'padding-right': 'padding-bottom', - 'marginRight': 'marginBottom', - }[property]; - } + function getDirectionPropertyValue(node, label) { - return parseFloat(node.getPropertyValue(getDirectionLabel(label)) || 0); + return parseFloat(node.getPropertyValue(swiper.getDirectionLabel(label)) || 0); } const params = swiper.params; @@ -85,7 +70,9 @@ export default function updateSlides() { const gridEnabled = params.grid && params.grid.rows > 1 && swiper.grid; if (gridEnabled) { - swiper.grid.initSlides(slidesLength); + swiper.grid.initSlides(slides); + } else if (swiper.grid) { + swiper.grid.unsetSlides(); } // Calc slides @@ -103,13 +90,13 @@ export default function updateSlides() { let slide; if (slides[i]) slide = slides[i]; if (gridEnabled) { - swiper.grid.updateSlide(i, slide, slidesLength, getDirectionLabel); + swiper.grid.updateSlide(i, slide, slides); } if (slides[i] && elementStyle(slide, 'display') === 'none') continue; // eslint-disable-line if (params.slidesPerView === 'auto') { if (shouldResetSlideSize) { - slides[i].style[getDirectionLabel('width')] = ``; + slides[i].style[swiper.getDirectionLabel('width')] = ``; } const slideStyles = getComputedStyle(slide); const currentTransform = slide.style.transform; @@ -157,7 +144,7 @@ export default function updateSlides() { if (params.roundLengths) slideSize = Math.floor(slideSize); if (slides[i]) { - slides[i].style[getDirectionLabel('width')] = `${slideSize}px`; + slides[i].style[swiper.getDirectionLabel('width')] = `${slideSize}px`; } } if (slides[i]) { @@ -199,11 +186,11 @@ export default function updateSlides() { wrapperEl.style.width = `${swiper.virtualSize + spaceBetween}px`; } if (params.setWrapperSize) { - wrapperEl.style[getDirectionLabel('width')] = `${swiper.virtualSize + spaceBetween}px`; + wrapperEl.style[swiper.getDirectionLabel('width')] = `${swiper.virtualSize + spaceBetween}px`; } if (gridEnabled) { - swiper.grid.updateWrapperSize(slideSize, snapGrid, getDirectionLabel); + swiper.grid.updateWrapperSize(slideSize, snapGrid); } // Remove last grid elements depending on width @@ -247,7 +234,8 @@ export default function updateSlides() { if (snapGrid.length === 0) snapGrid = [0]; if (spaceBetween !== 0) { - const key = swiper.isHorizontal() && rtl ? 'marginLeft' : getDirectionLabel('marginRight'); + const key = + swiper.isHorizontal() && rtl ? 'marginLeft' : swiper.getDirectionLabel('marginRight'); slides .filter((_, slideIndex) => { if (!params.cssMode || params.loop) return true; diff --git a/src/core/update/updateSlidesClasses.mjs b/src/core/update/updateSlidesClasses.mjs index 12e6f1dff..ed34d91ea 100644 --- a/src/core/update/updateSlidesClasses.mjs +++ b/src/core/update/updateSlidesClasses.mjs @@ -5,6 +5,7 @@ export default function updateSlidesClasses() { const { slides, params, slidesEl, activeIndex } = swiper; const isVirtual = swiper.virtual && params.virtual.enabled; + const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1; const getFilteredSlide = (selector) => { return elementChildren( @@ -17,6 +18,8 @@ export default function updateSlidesClasses() { }); let activeSlide; + let prevSlide; + let nextSlide; if (isVirtual) { if (params.loop) { let slideIndex = activeIndex - swiper.virtual.slidesBefore; @@ -27,28 +30,43 @@ export default function updateSlidesClasses() { activeSlide = getFilteredSlide(`[data-swiper-slide-index="${activeIndex}"]`); } } else { - activeSlide = slides[activeIndex]; + if (gridEnabled) { + activeSlide = slides.filter((slideEl) => slideEl.column === activeIndex)[0]; + nextSlide = slides.filter((slideEl) => slideEl.column === activeIndex + 1)[0]; + prevSlide = slides.filter((slideEl) => slideEl.column === activeIndex - 1)[0]; + } else { + activeSlide = slides[activeIndex]; + } } - if (activeSlide) { // Active classes activeSlide.classList.add(params.slideActiveClass); - // Next Slide - let nextSlide = elementNextAll(activeSlide, `.${params.slideClass}, swiper-slide`)[0]; - if (params.loop && !nextSlide) { - nextSlide = slides[0]; - } - if (nextSlide) { - nextSlide.classList.add(params.slideNextClass); - } - // Prev Slide - let prevSlide = elementPrevAll(activeSlide, `.${params.slideClass}, swiper-slide`)[0]; - if (params.loop && !prevSlide === 0) { - prevSlide = slides[slides.length - 1]; - } - if (prevSlide) { - prevSlide.classList.add(params.slidePrevClass); + if (gridEnabled) { + if (nextSlide) { + nextSlide.classList.add(params.slideNextClass); + } + if (prevSlide) { + prevSlide.classList.add(params.slidePrevClass); + } + } else { + // Next Slide + nextSlide = elementNextAll(activeSlide, `.${params.slideClass}, swiper-slide`)[0]; + if (params.loop && !nextSlide) { + nextSlide = slides[0]; + } + if (nextSlide) { + nextSlide.classList.add(params.slideNextClass); + } + + // Prev Slide + prevSlide = elementPrevAll(activeSlide, `.${params.slideClass}, swiper-slide`)[0]; + if (params.loop && !prevSlide === 0) { + prevSlide = slides[slides.length - 1]; + } + if (prevSlide) { + prevSlide.classList.add(params.slidePrevClass); + } } } diff --git a/src/modules/grid/grid.mjs b/src/modules/grid/grid.mjs index 8077cdf71..035b1f3d7 100644 --- a/src/modules/grid/grid.mjs +++ b/src/modules/grid/grid.mjs @@ -21,9 +21,13 @@ export default function Grid({ swiper, extendParams, on }) { return spaceBetween; }; - const initSlides = (slidesLength) => { + const initSlides = (slides) => { const { slidesPerView } = swiper.params; const { rows, fill } = swiper.params.grid; + const slidesLength = + swiper.virtual && swiper.params.virtual.enabled + ? swiper.virtual.slides.length + : slides.length; numFullColumns = Math.floor(slidesLength / rows); if (Math.floor(slidesLength / rows) === slidesLength / rows) { slidesNumberEvenToRows = slidesLength; @@ -36,10 +40,25 @@ export default function Grid({ swiper, extendParams, on }) { slidesPerRow = slidesNumberEvenToRows / rows; }; - const updateSlide = (i, slide, slidesLength, getDirectionLabel) => { + const unsetSlides = () => { + if (swiper.slides) { + swiper.slides.forEach((slide) => { + if (slide.swiperSlideGridSet) { + slide.style.height = ''; + slide.style[swiper.getDirectionLabel('margin-top')] = ''; + } + }); + } + }; + + const updateSlide = (i, slide, slides) => { const { slidesPerGroup } = swiper.params; const spaceBetween = getSpaceBetween(); const { rows, fill } = swiper.params.grid; + const slidesLength = + swiper.virtual && swiper.params.virtual.enabled + ? swiper.virtual.slides.length + : slides.length; // Set slides order let newSlideOrderIndex; let column; @@ -75,17 +94,23 @@ export default function Grid({ swiper, extendParams, on }) { } slide.row = row; slide.column = column; - slide.style[getDirectionLabel('margin-top')] = + slide.style.height = `calc((100% - ${(rows - 1) * spaceBetween}px) / ${rows})`; + slide.style[swiper.getDirectionLabel('margin-top')] = row !== 0 ? spaceBetween && `${spaceBetween}px` : ''; + slide.swiperSlideGridSet = true; }; - const updateWrapperSize = (slideSize, snapGrid, getDirectionLabel) => { + const updateWrapperSize = (slideSize, snapGrid) => { const { centeredSlides, roundLengths } = swiper.params; const spaceBetween = getSpaceBetween(); const { rows } = swiper.params.grid; swiper.virtualSize = (slideSize + spaceBetween) * slidesNumberEvenToRows; swiper.virtualSize = Math.ceil(swiper.virtualSize / rows) - spaceBetween; - swiper.wrapperEl.style[getDirectionLabel('width')] = `${swiper.virtualSize + spaceBetween}px`; + if (!swiper.params.cssMode) { + swiper.wrapperEl.style[swiper.getDirectionLabel('width')] = `${ + swiper.virtualSize + spaceBetween + }px`; + } if (centeredSlides) { const newSlidesGrid = []; for (let i = 0; i < snapGrid.length; i += 1) { @@ -127,6 +152,7 @@ export default function Grid({ swiper, extendParams, on }) { swiper.grid = { initSlides, + unsetSlides, updateSlide, updateWrapperSize, }; diff --git a/src/modules/pagination/pagination.mjs b/src/modules/pagination/pagination.mjs index 08599c15e..f61e157a8 100644 --- a/src/modules/pagination/pagination.mjs +++ b/src/modules/pagination/pagination.mjs @@ -256,6 +256,8 @@ export default function Pagination({ swiper, extendParams, on, emit }) { const slidesLength = swiper.virtual && swiper.params.virtual.enabled ? swiper.virtual.slides.length + : swiper.grid && swiper.params.grid.rows > 1 + ? swiper.slides.length / Math.ceil(swiper.params.grid.rows) : swiper.slides.length; let el = swiper.pagination.el; diff --git a/src/shared/utils.mjs b/src/shared/utils.mjs index 3adb28f6d..43d4e5225 100644 --- a/src/shared/utils.mjs +++ b/src/shared/utils.mjs @@ -204,7 +204,14 @@ function findElementsInElements(elements = [], selector = '') { function elementChildren(element, selector = '') { return [...element.children].filter((el) => el.matches(selector)); } - +function showWarning(text) { + try { + console.warn(text); + return; + } catch (err) { + // err + } +} function createElement(tag, classes = []) { const el = document.createElement(tag); el.classList.add(...(Array.isArray(classes) ? classes : [classes])); @@ -320,6 +327,7 @@ export { getComputedStyle, setCSSProperty, getSlideTransformEl, + showWarning, // dom findElementsInElements, createElement, diff --git a/src/swiper-vue.d.ts b/src/swiper-vue.d.ts index 3a7b9c9cb..777db920f 100644 --- a/src/swiper-vue.d.ts +++ b/src/swiper-vue.d.ts @@ -252,6 +252,7 @@ declare const Swiper: DefineComponent< default: undefined; }; loop: { type: BooleanConstructor; default: undefined }; + loopAddBlankSlides: { type: BooleanConstructor; default: undefined }; loopedSlides: { type: NumberConstructor; default: undefined; @@ -306,6 +307,10 @@ declare const Swiper: DefineComponent< type: StringConstructor; default: undefined; }; + slideBlankClass: { + type: StringConstructor; + default: undefined; + }; slideNextClass: { type: StringConstructor; default: undefined; diff --git a/src/types/modules/grid.d.ts b/src/types/modules/grid.d.ts index a7626c8c6..24baa6c32 100644 --- a/src/types/modules/grid.d.ts +++ b/src/types/modules/grid.d.ts @@ -6,8 +6,6 @@ export interface GridOptions { /** * Number of slides rows, for multirow layout * - * @note `rows` > 1 is currently not compatible with loop mode (`loop: true`) - * * @default 1 */ rows?: number; @@ -15,6 +13,8 @@ export interface GridOptions { /** * Can be `'column'` or `'row'`. Defines how slides should fill rows, by column or by row * + * @note `row` fill is currently not compatible with loop mode (`loop: true`) + * * @default 'column' */ fill?: 'row' | 'column'; diff --git a/src/types/swiper-options.d.ts b/src/types/swiper-options.d.ts index dcbf573f6..8d6aef52f 100644 --- a/src/types/swiper-options.d.ts +++ b/src/types/swiper-options.d.ts @@ -628,13 +628,26 @@ export interface SwiperOptions { /** * Set to `true` to enable continuous loop mode * - * Because of nature of how the loop mode works (it will rearrange slides), total number of slides must be >= slidesPerView * 2 + * Because of nature of how the loop mode works (it will rearrange slides), total number of slides must be: + * + * - >= `slidesPerView` + `slidesPerGroup` + * - even to `slidesPerGroup` (or use `loopAddBlankSlides` parameter) + * - even to `grid.rows` (or use `loopAddBlankSlides` parameter) * * @default false * */ loop?: boolean; + /** + * Automatically adds blank slides if you use Grid or `slidesPerGroup` and the total amount of slides is not even to `slidesPerGroup` or to `grid.rows` + * + * + * @default false + * + */ + loopAddBlankSlides?: boolean; + /** * Defines how many slides before end/beginning it should rearrange (loop) slides. If not specified, defaults to `slidesPerView` * @@ -793,6 +806,15 @@ export interface SwiperOptions { */ slideFullyVisibleClass?: string; + /** + * CSS class name of the blank slide added by the loop mode (when `loopAddBlankSlides` is enabled) + * + * @default 'swiper-slide-blank' + * + * @note Not supported in Swiper React/Vue + */ + slideBlankClass?: string; + /** * CSS class name of slide which is right after currently active slide * diff --git a/src/vue/swiper.mjs b/src/vue/swiper.mjs index d90ef84ae..3fde52381 100644 --- a/src/vue/swiper.mjs +++ b/src/vue/swiper.mjs @@ -103,6 +103,7 @@ const Swiper = { slideActiveClass: { type: String, default: undefined }, slideVisibleClass: { type: String, default: undefined }, slideFullyVisibleClass: { type: String, default: undefined }, + slideBlankClass: { type: String, default: undefined }, slideNextClass: { type: String, default: undefined }, slidePrevClass: { type: String, default: undefined }, wrapperClass: { type: String, default: undefined },