UNPKG

@dschen/slidy

Version:

Slidy is a simple npm package that allows you to create customizable sliders for your web projects.

359 lines (358 loc) 17.6 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; export const init = (options) => document.addEventListener('DOMContentLoaded', () => initSlidy(options)); function initSlidy(options) { class Slider { constructor(slider, index, options) { this.SWIPPER_THRESHOLD = 1; this.isMoving = false; this.currentSlide = 0; this.nSlidesVisible = 0; this.isResizing = false; this.options = { indicatorsAsButtons: true, animationDuration: 0 }; this.slider = slider; this.index = index; this.options = Object.assign(Object.assign({}, this.options), options); this.onInit(); } onInit() { return __awaiter(this, void 0, void 0, function* () { const sliderStage = this.slider.querySelector('.slidy-stage'); const mutationObserver = new MutationObserver(() => __awaiter(this, void 0, void 0, function* () { yield this.delay(500); this.restart(sliderStage); })); mutationObserver.observe(sliderStage, { childList: true, subtree: true }); if (!sliderStage) return; // records number of childs this.nSlides = sliderStage.childElementCount; const slides = [...sliderStage.children].map((c, i) => { const el = c; el.dataset.id = `${i}`; return el; }); this.slider.id = `slidy-${this.index}`; this.sliderElements = { slider: this.slider, sliderStage, slides }; this.initNavigationButtons(); yield this.updateNumberVisibleSlides(); this.initNavigationIndicators(); let options = { root: document, rootMargin: "0px", threshold: .5, }; let observer = new IntersectionObserver(e => { e.forEach((entry) => __awaiter(this, void 0, void 0, function* () { this.initSlidesObserver(entry.isIntersecting); this.sliderIsVisible = entry.isIntersecting; if (entry.isIntersecting) { yield this.updateNumberVisibleSlides(); this.handleIndicators(); } })); observer.disconnect(); }, options); observer.observe(this.slider); }); } restart(sliderStage) { return __awaiter(this, void 0, void 0, function* () { // records number of childs this.nSlides = sliderStage.childElementCount; const slides = [...sliderStage.children].map((c, i) => { const el = c; el.dataset.id = `${i}`; return el; }); this.slider.id = `slidy-${this.index}`; this.sliderElements = { slider: this.slider, sliderStage, slides }; this.initNavigationButtons(true); yield this.updateNumberVisibleSlides(); this.initNavigationIndicators(); let options = { root: document, rootMargin: "0px", threshold: .5, }; let observer = new IntersectionObserver(e => { e.forEach((entry) => __awaiter(this, void 0, void 0, function* () { this.initSlidesObserver(entry.isIntersecting); this.sliderIsVisible = entry.isIntersecting; if (entry.isIntersecting) { yield this.updateNumberVisibleSlides(); this.handleIndicators(); } })); }, options); observer.observe(this.slider); }); } toggleActiveClass(element, isIntersecting) { if (this.isResizing || !element) return; if (isIntersecting) { element.classList.add("slidy-active"); element.ariaCurrent = "true"; element.ariaHidden = "false"; element.ariaDisabled = "false"; [...element.querySelectorAll("a, button")].map(p => p.setAttribute("tabIndex", "0")); } else { element.classList.remove("slidy-active"); element.ariaCurrent = "false"; element.ariaHidden = "true"; element.ariaDisabled = "true"; [...element.querySelectorAll("a, button")].map(p => p.setAttribute("tabIndex", "-1")); } } updateNumberVisibleSlides() { const observeElements = (resolve) => { if (!this.sliderElements) return; const { slides, sliderStage } = this.sliderElements; let options = { root: sliderStage, rootMargin: "0px", threshold: this.SWIPPER_THRESHOLD, }; let observer = new IntersectionObserver(e => { this.nSlidesVisible = 0; e.forEach(entry => { if (!entry.isIntersecting) return; this.nSlidesVisible += 1; }); observer.disconnect(); resolve(); }, options); slides.forEach((s) => { observer.observe(s); }); }; return new Promise((resolve, _) => { observeElements(resolve); }); } initSlidesObserver(connect) { if (!this.sliderElements) return; const { slides, sliderStage, sliderIndicators } = this.sliderElements; let options = { root: sliderStage, rootMargin: "0px", threshold: this.SWIPPER_THRESHOLD - 0.01, }; let observer = new IntersectionObserver(e => { e.forEach((entry) => { var _a, _b; this.toggleActiveClass(entry.target, entry.isIntersecting); if (!entry.isIntersecting || !sliderIndicators || this.isMoving || this.isResizing) return; const id = Number((_b = (_a = entry.target) === null || _a === void 0 ? void 0 : _a.dataset) === null || _b === void 0 ? void 0 : _b.id); const hasIndicator = sliderIndicators.querySelector(`.slidy-indicator-${id}`); if (hasIndicator) { this.handleActiveIndicators(id); } if (id === this.nSlides - 1) { this.handleNavigationButtonsState("end"); } if (id === 0) { this.handleNavigationButtonsState("start"); } }); }, options); if (connect) { slides.forEach((s, i) => { observer.observe(s); }); } else { observer.disconnect(); } } delay(time) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, _) => setTimeout(() => resolve(true), time)); }); } initNavigationIndicators() { const sliderIndicators = this.slider.querySelector('.slidy-indicators'); if (!sliderIndicators) return; this.sliderElements = Object.assign(Object.assign({}, this.sliderElements), { sliderIndicators }); this.handleIndicators(); window.addEventListener('resize', () => __awaiter(this, void 0, void 0, function* () { this.isResizing = true; if (!this.sliderIsVisible) { this.isResizing = false; return; } yield this.updateNumberVisibleSlides(); this.handleIndicators(); this.isResizing = false; })); } initNavigationButtons(restart) { var _a, _b; // add previous/next buttons events let sliderBtnNext = this.slider.querySelector('.slidy-next'); let sliderBtnPrevious = this.slider.querySelector('.slidy-previous'); if (restart) { const newNext = sliderBtnNext.cloneNode(true); const newPrevious = sliderBtnPrevious.cloneNode(true); (_a = sliderBtnNext.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(newNext, sliderBtnNext); (_b = sliderBtnPrevious.parentNode) === null || _b === void 0 ? void 0 : _b.replaceChild(newPrevious, sliderBtnPrevious); sliderBtnNext = newNext; sliderBtnPrevious = newPrevious; } if (!sliderBtnNext && !sliderBtnPrevious) return; const sliderButtons = {}; if (sliderBtnPrevious) { Object.assign(sliderButtons, { sliderBtnPrevious }); sliderBtnPrevious.addEventListener('click', () => this.nextSlide(-1)); } if (sliderBtnNext) { Object.assign(sliderButtons, { sliderBtnNext }); sliderBtnNext.addEventListener('click', () => this.nextSlide(1)); } this.sliderElements = Object.assign(Object.assign({}, this.sliderElements), { sliderButtons }); } getActiveIndex(direction) { const currentSlide = this.currentSlide; const nSlides = this.nSlides; let nSlidesVisible = this.nSlidesVisible; let index = currentSlide + direction * nSlidesVisible >= nSlides ? currentSlide : currentSlide + direction * nSlidesVisible; index = index < 0 ? 0 : index; return index; } nextSlide(direction) { if (!this.sliderElements) return; const index = this.getActiveIndex(direction); this.handleActiveIndicators(index); this.goToSlide(index); this.currentSlide = index; } handleIndicators() { if (!this.sliderElements) return; const { sliderIndicators } = this.sliderElements; let nSlidesVisible = this.nSlidesVisible; if (sliderIndicators && nSlidesVisible > 0) { let nIndicators = Math.ceil(this.nSlides / nSlidesVisible); sliderIndicators.innerHTML = ''; // adds the number of indicators according to the number of visible cards // adds event on click sliderIndicators.innerHTML = ""; for (let i = 0; i < nIndicators; i++) { if (nIndicators === 1) break; const temp = document.createElement('div'); const id = nSlidesVisible * i; if (this.options.indicatorsAsButtons) { temp.innerHTML = ` <button type="button" class="slidy-indicator slidy-indicator-${id} ${id === this.currentSlide ? 'active ' : ''}btn btn-indicator" aria-label="Slide ${id}"> </button>`; temp.children[0].addEventListener('click', () => { this.goToSlide(id); this.handleActiveIndicators(id); }); } else { temp.innerHTML = ` <div class="slidy-indicator slidy-indicator-${id} ${id === this.currentSlide ? 'active ' : ''}btn btn-indicator"> </div>`; } sliderIndicators.append(temp.children[0]); temp.remove(); } // this.handleEventIndicators(sliderElements); // moves to slide after resizing const regexMobile = /Mobi|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i; if (this.isResizing && !regexMobile.test(navigator === null || navigator === void 0 ? void 0 : navigator.userAgent)) { this.goToSlide(this.currentSlide); } } } handleActiveIndicators(index) { var _a; if (!this.sliderElements) return; const { sliderIndicators } = this.sliderElements; sliderIndicators === null || sliderIndicators === void 0 ? void 0 : sliderIndicators.querySelectorAll('.slidy-indicator').forEach(btn => btn.classList.remove('active')); (_a = sliderIndicators === null || sliderIndicators === void 0 ? void 0 : sliderIndicators.querySelector(`.slidy-indicator-${index}`)) === null || _a === void 0 ? void 0 : _a.classList.add('active'); } goToSlide(index) { var _a, _b, _c, _d, _e; return __awaiter(this, void 0, void 0, function* () { this.isMoving = true; if (!this.sliderElements) return; const { sliderStage } = this.sliderElements; const currentSlide = sliderStage.children[index]; const previousSlide = sliderStage.children[this.currentSlide]; this.toggleActiveClass(previousSlide, false); yield this.delay((_a = this.options.animationDuration) !== null && _a !== void 0 ? _a : 0); currentSlide.scrollIntoView({ inline: 'start', block: 'nearest' }); this.toggleActiveClass(currentSlide, true); let gap = ((_b = sliderStage.children[index - 1]) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect().left) - ((_c = sliderStage.children[index]) === null || _c === void 0 ? void 0 : _c.getBoundingClientRect().left) + ((_d = sliderStage.children[index - 1]) === null || _d === void 0 ? void 0 : _d.clientWidth); gap = gap ? Math.abs(gap) : 0; let point; this.currentSlide = index; yield this.delay((_e = this.options.animationDuration) !== null && _e !== void 0 ? _e : 0); if (Math.round(sliderStage.scrollLeft + currentSlide.clientWidth + gap) + 2 >= sliderStage.scrollWidth) point = 'end'; if (sliderStage.scrollLeft === 0) point = 'start'; this.handleNavigationButtonsState(point); this.isMoving = false; }); } handleNavigationButtonsState(point) { if (!this.sliderElements) return; const { sliderButtons } = this.sliderElements; if (!sliderButtons) return; const sliderBtnNext = sliderButtons.sliderBtnNext; const sliderBtnPrevious = sliderButtons.sliderBtnPrevious; if (sliderBtnNext) { sliderBtnNext.disabled = point === "end" ? true : false; sliderBtnNext.ariaDisabled = point === "end" ? "true" : "false"; } if (sliderBtnPrevious) { sliderBtnPrevious.disabled = point === "start" ? true : false; sliderBtnPrevious.ariaDisabled = point === "start" ? "true" : "false"; } } } const sliders = [...document.querySelectorAll('.slidy')]; sliders.forEach((slide, i) => { var _a; let sliderOptions; if (Array.isArray(options)) { sliderOptions = (_a = options.find(op => op.id === i)) !== null && _a !== void 0 ? _a : {}; } else { sliderOptions = options.id === i ? options : {}; } new Slider(slide, i, sliderOptions); }); } export default { init };