UNPKG

react-slidy

Version:

🍃 React Slidy - Minimalistic and smooth touch slider component for React ⚛️

323 lines (261 loc) 8.77 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.translate = translate; exports.infiniteIndex = infiniteIndex; exports.clampNumber = clampNumber; exports["default"] = slidy; var LINEAR_ANIMATION = 'linear'; var VALID_SWIPE_DISTANCE = 50; var TRANSITION_END = 'transitionend'; var abs = Math.abs; var EVENT_OPTIONS = { passive: false }; function translate(to, moveX, percentatge) { if (percentatge === void 0) { percentatge = 100; } var translation = to * percentatge * -1; var x = moveX ? "calc(" + translation + "% - " + moveX + "px)" : translation + "%"; return "translate3d(" + x + ", 0, 0)"; } function infiniteIndex(index, end) { if (index < 0) return end - 1;else if (index >= end) return 0;else return index; } function clampNumber(x, minValue, maxValue) { return Math.min(Math.max(x, minValue), maxValue); } function getTouchCoordinatesFromEvent(e) { return e.targetTouches ? e.targetTouches[0] : e.touches[0]; } /** * * @param {number} duration * @param {string} ease * @param {number} index * @param {number} x * @param {number} percentatge */ function getTranslationCSS(duration, ease, index, x, percentatge) { var easeCssText = ease !== '' ? "transition-timing-function: " + ease + ";" : ''; var durationCssText = duration ? "transition-duration: " + duration + "ms;" : ''; return "" + easeCssText + durationCssText + "transform: " + translate(index, x, percentatge) + ";"; } function cleanContainer(container) { // remove all the elements except the last one as it seems to be old data in the HTML // that's specially useful for dynamic content while (container.childElementCount > 1) { container !== null && container.removeChild(container.lastChild); } // tell that the clean is done return true; } function slidy(containerDOMEl, options) { var doAfterSlide = options.doAfterSlide, doBeforeSlide = options.doBeforeSlide, ease = options.ease, infiniteLoop = options.infiniteLoop, initialSlide = options.initialSlide, numOfSlides = options.numOfSlides, onNext = options.onNext, onPrev = options.onPrev, slidesDOMEl = options.slidesDOMEl, slideSpeed = options.slideSpeed; var items = options.items; // if frameDOMEl is null, then we do nothing if (containerDOMEl === null) return; // initialize some variables var index = initialSlide; var isScrolling = false; var transitionEndCallbackActivated = false; // event handling var deltaX = 0; var deltaY = 0; var touchOffsetX = 0; var touchOffsetY = 0; /** * translates to a given position in a given time in milliseconds * * @param {number} duration time in milliseconds for the transistion * @param {string} ease easing css property * @param {number} x Number of pixels to fine tuning translation */ function _translate(duration, ease, x) { if (ease === void 0) { ease = ''; } if (x === void 0) { x = 0; } var percentatge = 100 / numOfSlides; slidesDOMEl.style.cssText = getTranslationCSS(duration, ease, index, x, percentatge); } /** * slide function called by prev, next & touchend * * determine nextIndex and slide to next postion * under restrictions of the defined options * * @param {boolean} direction 'true' for right, 'false' for left */ function slide(direction) { var movement = direction === true ? 1 : -1; // calculate the nextIndex according to the movement var nextIndex = index + 1 * movement; /** * If the slider has the infiniteLoop option * nextIndex will start from the start when arrives to the end * and vice versa */ if (infiniteLoop) nextIndex = infiniteIndex(nextIndex, items); // nextIndex should be between 0 and items minus 1 nextIndex = clampNumber(nextIndex, 0, items - 1); goTo(nextIndex); } function onTransitionEnd() { if (transitionEndCallbackActivated === true) { _translate(0); transitionEndCallbackActivated = false; } } function onTouchstart(e) { var coords = getTouchCoordinatesFromEvent(e); isScrolling = undefined; touchOffsetX = coords.pageX; touchOffsetY = coords.pageY; } function onTouchmove(e) { // ensure swiping with one touch and not pinching if (e.touches.length > 1 || e.scale && e.scale !== 1) return; var coords = getTouchCoordinatesFromEvent(e); deltaX = coords.pageX - touchOffsetX; deltaY = coords.pageY - touchOffsetY; if (typeof isScrolling === 'undefined') { isScrolling = abs(deltaX) < abs(deltaY); if (!isScrolling) document.ontouchmove = function (e) { return e.preventDefault(); }; return; } if (!isScrolling) { e.preventDefault(); _translate(0, LINEAR_ANIMATION, deltaX * -1); } } function onTouchend() { // hack the document to block scroll document.ontouchmove = function () { return true; }; if (!isScrolling) { /** * is valid if: * -> swipe distance is greater than the specified valid swipe distance * -> swipe distance is more then a third of the swipe area * @isValidSlide {Boolean} */ var isValid = abs(deltaX) > VALID_SWIPE_DISTANCE; /** * is out of bounds if: * -> index is 0 and deltaX is greater than 0 * -> index is the last slide and deltaX is smaller than 0 * @isOutOfBounds {Boolean} */ var direction = deltaX < 0; var isOutOfBounds = direction === false && index === 0 || direction === true && index === items - 1; /** * If the swipe is valid and we're not out of bounds * -> Slide to the direction * otherwise: go back to the previous slide with a linear animation */ isValid === true && isOutOfBounds === false ? slide(direction) : _translate(slideSpeed, LINEAR_ANIMATION); } // reset variables with the initial values deltaX = deltaY = touchOffsetX = touchOffsetY = 0; } /** * public * setup function */ function _setup() { slidesDOMEl.addEventListener(TRANSITION_END, onTransitionEnd); containerDOMEl.addEventListener('touchstart', onTouchstart, EVENT_OPTIONS); containerDOMEl.addEventListener('touchmove', onTouchmove, EVENT_OPTIONS); containerDOMEl.addEventListener('touchend', onTouchend, EVENT_OPTIONS); if (index !== 0) { _translate(0); } } /** * public * clean content of the slider */ function clean() { return cleanContainer(slidesDOMEl); } /** * public * @param {number} nextIndex Index number to go to */ function goTo(nextIndex) { // if the nextIndex and the current is the same, we don't need to do the slide if (nextIndex === index) return; // if the nextIndex is possible according to number of items, then use it if (nextIndex <= items) { // execute the callback from the options before sliding doBeforeSlide({ currentSlide: index, nextSlide: nextIndex }); // execute the internal callback nextIndex > index ? onNext(nextIndex) : onPrev(nextIndex); index = nextIndex; } // translate to the next index by a defined duration and ease function _translate(slideSpeed, ease); // execute the callback from the options after sliding slidesDOMEl.addEventListener(TRANSITION_END, function cb(e) { doAfterSlide({ currentSlide: index }); e.currentTarget.removeEventListener(e.type, cb); }); } /** * public * prev function: called on clickhandler */ function prev(e) { e.preventDefault(); e.stopPropagation(); slide(false); } /** * public * next function: called on clickhandler */ function next(e) { e.preventDefault(); e.stopPropagation(); slide(true); } /** * public * @param {number} newItems Number of items in the slider for dynamic content */ function updateItems(newItems) { items = newItems; } /** * public * destroy function: called to gracefully destroy the slidy instance */ function destroy() { // remove all touch listeners containerDOMEl.removeEventListener('touchstart', onTouchstart, EVENT_OPTIONS); containerDOMEl.removeEventListener('touchmove', onTouchmove, EVENT_OPTIONS); containerDOMEl.removeEventListener('touchend', onTouchend, EVENT_OPTIONS); // remove transition listeners slidesDOMEl.removeEventListener(TRANSITION_END, onTransitionEnd); } // trigger initial setup _setup(); // expose public api return { clean: clean, destroy: destroy, goTo: goTo, next: next, prev: prev, slide: slide, updateItems: updateItems }; }