virtual.js 8.71 KB
export default function Virtual({
	swiper,
	extendParams,
	on
}) {
	extendParams({
		virtual: {
			enabled: false,
			slides: [],
			cache: true,
			renderSlide: null,
			renderExternal: null,
			renderExternalUpdate: true,
			addSlidesBefore: 0,
			addSlidesAfter: 0,
		},
	});

	let cssModeTimeout;

	swiper.virtual = {
		cache: {},
		from: undefined,
		to: undefined,
		slides: [],
		offset: 0,
		slidesGrid: [],
	};

	function renderSlide(slide, index) {
		const params = swiper.params.virtual;
		if (params.cache && swiper.virtual.cache[index]) {
			return swiper.virtual.cache[index];
		}
		// const $slideEl = params.renderSlide ?
		// 	$(params.renderSlide.call(swiper, slide, index)) :
		// 	$(
		// 		`<div class="${swiper.params.slideClass}" data-swiper-slide-index="${index}">${slide}</div>`,
		// 	);
		// if (!$slideEl.attr('data-swiper-slide-index')) $slideEl.attr('data-swiper-slide-index', index);
		// if (params.cache) swiper.virtual.cache[index] = $slideEl;
		// return $slideEl;
	}

	function onRendered() {
		swiper.updateSlides();
		swiper.updateProgress();
		swiper.updateSlidesClasses();
		if (swiper.lazy && swiper.params.lazy.enabled) {
			swiper.lazy.load();
		}
	}

	async function update(force) {
		const {
			slidesPerView,
			slidesPerGroup,
			centeredSlides
		} = swiper.params;
		const {
			addSlidesBefore,
			addSlidesAfter
		} = swiper.params.virtual;
		const {
			from: previousFrom,
			to: previousTo,
			slides,
			slidesGrid: previousSlidesGrid,
			offset: previousOffset,
		} = swiper.virtual;
		if (!swiper.params.cssMode) {
			swiper.updateActiveIndex();
		}

		const activeIndex = swiper.activeIndex || 0;

		let offsetProp;
		if (swiper.rtlTranslate) offsetProp = 'right';
		else offsetProp = swiper.isHorizontal() ? 'left' : 'top';

		let slidesAfter;
		let slidesBefore;
		if (centeredSlides) {
			slidesAfter = Math.floor(slidesPerView / 2) + slidesPerGroup + addSlidesAfter;
			slidesBefore = Math.floor(slidesPerView / 2) + slidesPerGroup + addSlidesBefore;
		} else {
			slidesAfter = slidesPerView + (slidesPerGroup - 1) + addSlidesAfter;
			slidesBefore = slidesPerGroup + addSlidesBefore;
		}
		const from = Math.max((activeIndex || 0) - slidesBefore, 0);
		const to = Math.min((activeIndex || 0) + slidesAfter, slides.length - 1);
		const offset = (swiper.slidesGrid[from] || 0) - (swiper.slidesGrid[0] || 0);
		Object.assign(swiper.virtual, {
			from,
			to,
			offset,
			slidesGrid: swiper.slidesGrid,
		});

		function onRendered() {
			swiper.updateSlides();
			swiper.updateProgress();
			swiper.updateSlidesClasses();
			if (swiper.lazy && swiper.params.lazy.enabled) {
				swiper.lazy.load();
			}
		}
		if (previousFrom === from && previousTo === to && !force) {
			if (swiper.slidesGrid !== previousSlidesGrid && offset !== previousOffset) {
				swiper.slides.css(offsetProp, `${offset}px`);
			}
			swiper.updateProgress();
			return;
		}
		if (swiper.params.virtual.renderExternal) {
			swiper.params.virtual.renderExternal.call(swiper, {
				offset,
				from,
				to,
				slides: (function getSlides() {
					const slidesToRender = [];
					if (swiper.params.virtual.type == 'keep') {
						for (let i = 0; i < from; i += 1) {
							slidesToRender.push("");
						}
					}
					for (let i = from; i <= to; i += 1) {
						slidesToRender.push(slides[i]);
					}
					return slidesToRender;
				})(),
			});
			if (swiper.params.virtual.renderExternalUpdate) {
				onRendered();
			}
			return;
		}
		const prependIndexes = [];
		const appendIndexes = [];
		if (force) {
			swiper.$wrapperEl.find(`.${swiper.params.slideClass}`).remove();
		} else {
			for (let i = previousFrom; i <= previousTo; i += 1) {
				if (i < from || i > to) {
					swiper.virtualList.splice(swiper.virtualList.findIndex((item) => {
						return item == slides[i]
					}), 1)

					swiper.virtualIndexList.splice(swiper.virtualIndexList.findIndex((item) => {
						return item == i
					}), 1)
					// swiper.slides[i].virtualShow = false;
				}
			}
		}
		for (let i = 0; i < slides.length; i += 1) {
			if (i >= from && i <= to) {
				if (typeof previousTo === 'undefined' || force) {
					appendIndexes.push(i);
				} else {
					if (i > previousTo) appendIndexes.push(i);
					if (i < previousFrom) prependIndexes.push(i);
				}
			}
		}
		// let list = [];
		appendIndexes.forEach((index) => {


			// if (swiper.slides[index]) {
			// 	swiper.slides[index].virtualShow = true;
			// } else {
			swiper.virtualList.push(slides[index]);
			swiper.virtualIndexList.push(index)
			// }

			// renderSlide(slides[index], index)
			// renderSlide(slides[index], index)
			// swiper.$wrapperEl.append(renderSlide(slides[index], index));
		});
		prependIndexes
			.sort((a, b) => b - a)
			.forEach((index) => {
				// swiper.slides[index].virtualShow = true;
				swiper.virtualList.unshift(slides[index]);
				swiper.virtualIndexList.unshift(index)

				// swiper.$wrapperEl.prepend(renderSlide(slides[index], index));
			});
		swiper.native.emit("input", [swiper.virtualList])
		onRendered();
	}

	function appendSlide(slides) {
		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) {
		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 (swiper.params.virtual.cache) {
			const cache = swiper.virtual.cache;
			const newCache = {};
			Object.keys(cache).forEach((cachedIndex) => {
				const $cachedEl = cache[cachedIndex];
				const cachedElIndex = $cachedEl.attr('data-swiper-slide-index');
				if (cachedElIndex) {
					$cachedEl.attr(
						'data-swiper-slide-index',
						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;
		if (Array.isArray(slidesIndexes)) {
			for (let i = slidesIndexes.length - 1; i >= 0; i -= 1) {
				swiper.virtual.slides.splice(slidesIndexes[i], 1);
				if (swiper.params.virtual.cache) {
					delete swiper.virtual.cache[slidesIndexes[i]];
				}
				if (slidesIndexes[i] < activeIndex) activeIndex -= 1;
				activeIndex = Math.max(activeIndex, 0);
			}
		} else {
			swiper.virtual.slides.splice(slidesIndexes, 1);
			if (swiper.params.virtual.cache) {
				delete swiper.virtual.cache[slidesIndexes];
			}
			if (slidesIndexes < activeIndex) activeIndex -= 1;
			activeIndex = Math.max(activeIndex, 0);
		}
		update(true);
		swiper.slideTo(activeIndex, 0);
	}

	function removeAllSlides() {
		swiper.virtual.slides = [];
		if (swiper.params.virtual.cache) {
			swiper.virtual.cache = {};
		}
		update(true);
		swiper.slideTo(0, 0);
	}

	on('beforeInit', () => {
		if (!swiper.params.virtual.enabled) return;
		swiper.virtual.slides = swiper.params.virtual.slides;
		swiper.classNames.push(`${swiper.params.containerModifierClass}virtual`);

		swiper.params.watchSlidesProgress = true;
		swiper.originalParams.watchSlidesProgress = true;
		if (!swiper.params.initialSlide) {
			update();
		}
	});
	// on('beforeUpdate', () => {
	// 	if (!swiper.params.virtual.enabled) return;
	// 	let offsetProp;
	// 	if (swiper.rtlTranslate) offsetProp = 'right';
	// 	else offsetProp = swiper.isHorizontal() ? 'left' : 'top';
	// 	swiper.slides.forEach((item, index) => {
	// 		item.dataSwiperSlideIndex = swiper.virtualIndexList[index];
	// 		item.css({
	// 			[offsetProp]: `${swiper.virtual.offset}px`
	// 		})
	// 	})
	// })
	on('setTranslate', async () => {
		if (!swiper.params.virtual.enabled) return;
		if (swiper.params.cssMode && !swiper._immediateVirtual) {
			clearTimeout(cssModeTimeout);
			cssModeTimeout = setTimeout(() => {
				update();
			}, 100);
		} else {
			clearTimeout(cssModeTimeout);
			cssModeTimeout = setTimeout(() => {
				update();
			}, 100);
			// update();
		}
	});

	Object.assign(swiper.virtual, {
		appendSlide,
		prependSlide,
		removeSlide,
		removeAllSlides,
		update,
	});
}