UNPKG

vevet

Version:

Vevet is a JavaScript library for creative development that simplifies crafting rich interactions like split text animations, carousels, marquees, preloading, and more.

512 lines 20.8 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Snap = void 0; var base_1 = require("../../base"); var Timeline_1 = require("../Timeline"); var Raf_1 = require("../Raf"); var utils_1 = require("../../utils"); var Slide_1 = require("./Slide"); var Wheel_1 = require("./Wheel"); var Swipe_1 = require("./Swipe"); var Track_1 = require("./Track"); var Keyboard_1 = require("./Keyboard"); var initVevet_1 = require("../../global/initVevet"); __exportStar(require("./types"), exports); __exportStar(require("./Slide"), exports); // todo: jsdoc /** * Snap/Carousel handler. * This class manages sliding progress with options like swipe, wheel interactions, and smooth transitions. * * Please not that the class does not apply any styles to the slides, it only handles the logic. * * [Documentation](https://antonbobrov.github.io/vevet/docs/components/Snap) * * @group Components */ var Snap = /** @class */ (function (_super) { __extends(Snap, _super); function Snap(props) { var _this = _super.call(this, props) || this; /** Container size */ _this._domSize = 0; /** All slides */ _this._slides = []; /** Scrollable slides (which size is larger than the container) */ _this._scrollableSlides = []; var _a = _this.props, container = _a.container, activeIndex = _a.activeIndex; // set vars _this._activeIndex = activeIndex; // add resize event _this._resizeHandler = (0, utils_1.onResize)({ element: container, callback: function () { return _this._handleResize(); }, name: _this.name, }); // initial resize _this._resizeHandler.debounceResize(); // Create the animation frame _this._raf = new Raf_1.Raf(); _this._raf.on('frame', function () { return _this._handleRaf(); }); _this._raf.on('play', function () { return _this._callbacks.emit('rafPlay', undefined); }); _this._raf.on('pause', function () { return _this._callbacks.emit('rafPause', undefined); }); // fetch slides _this._fetchSlides(); // add wheel listener _this._wheel = new Wheel_1.SnapWheel(_this); // add swipe _this._swipe = new Swipe_1.SnapSwipe(_this); // add track _this._track = new Track_1.SnapTrack(_this); // add keyboard _this._keyboard = new Keyboard_1.SnapKeyboard(_this); return _this; } /** Retrieves the default static properties. */ Snap.prototype._getStatic = function () { return __assign(__assign({}, _super.prototype._getStatic.call(this)), { eventsEmitter: null, activeIndex: 0 }); }; /** Retrieves the default mutable properties. */ Snap.prototype._getMutable = function () { return __assign(__assign({}, _super.prototype._getMutable.call(this)), { slides: false, direction: 'horizontal', centered: false, loop: false, gap: 0, lerp: 0.2, freemode: false, stickOnResize: true, friction: 0.15, edgeFriction: 0.85, duration: 500, easing: utils_1.EaseOutCubic, swipe: true, grabCursor: false, swipeSpeed: 1, swipeAxis: 'auto', followSwipe: true, shortSwipes: true, shortSwipesDuration: 300, shortSwipesThreshold: 30, swipeFriction: false, swipeLerp: (0, initVevet_1.initVevet)().mobile ? 1 : 0.6, swipeThreshold: 5, swipeMinTime: 0, wheel: false, wheelSpeed: 1, wheelAxis: 'auto', followWheel: true, wheelThrottle: 'auto', stickOnWheelEnd: true, slideSize: 'auto' }); }; /** Handles properties change */ Snap.prototype._handleProps = function () { // attach slides this._fetchSlides(); // resize instantly this._resizeHandler.resize(); // update props _super.prototype._handleProps.call(this); }; /** Update slides list and attach them */ Snap.prototype._fetchSlides = function () { var _this = this; this._slides.forEach(function (slide) { return slide.detach(); }); var children = this.props.slides ? this.props.slides : Array.from(this.props.container.children); this._slides = children.map(function (item) { if (item instanceof Slide_1.SnapSlide) { return item; } return new Slide_1.SnapSlide(item); }); this._slides.forEach(function (slide, index) { return slide.attach(_this, index); }); }; /** Request resize (handled with debounce timeout) */ Snap.prototype.resize = function (isManual) { if (isManual === void 0) { isManual = false; } if (isManual) { this._resizeHandler.resize(); } else { this._resizeHandler.debounceResize(); } }; /** Resize the scene and reflow */ Snap.prototype._handleResize = function () { var _a = this.props, direction = _a.direction, container = _a.container; // cancel sticky behavior this.cancelTransition(); // update container size this._domSize = direction === 'horizontal' ? container.offsetWidth : container.offsetHeight; // reflow this._reflow(); // emit callbacks this.callbacks.emit('resize', undefined); }; Object.defineProperty(Snap.prototype, "container", { /** Get container */ get: function () { return this.props.container; }, enumerable: false, configurable: true }); Object.defineProperty(Snap.prototype, "eventsEmitter", { /** Get events emitter */ get: function () { var _a; return (_a = this.props.eventsEmitter) !== null && _a !== void 0 ? _a : this.container; }, enumerable: false, configurable: true }); Object.defineProperty(Snap.prototype, "domSize", { /** Container size depending on direction (width or height) */ get: function () { return this._domSize; }, enumerable: false, configurable: true }); Object.defineProperty(Snap.prototype, "slides", { /** All slides */ get: function () { return this._slides; }, enumerable: false, configurable: true }); Object.defineProperty(Snap.prototype, "scrollableSlides", { /** Scrollable slides (which size is larger than the container) */ get: function () { return this._scrollableSlides; }, enumerable: false, configurable: true }); Object.defineProperty(Snap.prototype, "activeIndex", { /** Active slide index */ get: function () { return this._activeIndex; }, enumerable: false, configurable: true }); Object.defineProperty(Snap.prototype, "activeSlide", { /** Active slide */ get: function () { return this.slides[this._activeIndex]; }, enumerable: false, configurable: true }); Object.defineProperty(Snap.prototype, "isEmpty", { get: function () { return this.slides.length === 0; }, enumerable: false, configurable: true }); Object.defineProperty(Snap.prototype, "axis", { /** Get axis name depending on direction */ get: function () { return this.props.direction === 'horizontal' ? 'x' : 'y'; }, enumerable: false, configurable: true }); Object.defineProperty(Snap.prototype, "track", { /** Snap track */ get: function () { return this._track; }, enumerable: false, configurable: true }); Object.defineProperty(Snap.prototype, "isTransitioning", { /** If transition in progress */ get: function () { return !!this._timeline; }, enumerable: false, configurable: true }); /** Reflow: update static values of slides */ Snap.prototype._reflow = function () { var _this = this; var _a = this, slides = _a.slides, props = _a.props; if (slides.length === 0) { return; } // Reset scrollable slides this._scrollableSlides = []; // Calculate static values slides.reduce(function (prev, slide) { slide.setStaticCoord(prev); if (slide.size > _this.domSize) { _this._scrollableSlides.push(slide); } return prev + slide.size + (0, utils_1.toPixels)(props.gap); }, 0); // Reset to active slide var slide = slides.find(function (_a) { var index = _a.index; return index === _this.activeIndex; }); if (props.stickOnResize && slide) { this.track.clampTarget(); this.track.set(slide.magnets[0]); } // Emit callbacks this.callbacks.emit('reflow', undefined); // Render after resize this._render(); }; /** Handle RAF update, interpolate track values */ Snap.prototype._handleRaf = function () { if (this.isTransitioning) { return; } var _a = this, track = _a.track, props = _a.props, swipe = _a._swipe; // Get lerp factor var lerpFactor = swipe.isSwiping && props.swipeLerp ? props.swipeLerp : props.lerp; // Interpolate track value track.lerp(this._raf.lerpFactor(lerpFactor)); // Stop raf if target reached if (track.isInterpolated) { this._raf.pause(); } // Render the scene this._render(this._raf.duration); }; /** Render slides logic */ Snap.prototype._render = function (frameDuration) { if (frameDuration === void 0) { frameDuration = 0; } if (this.isEmpty) { return; } var _a = this, swipe = _a._swipe, track = _a.track, props = _a.props; // Update values this._updateSlidesCoords(); this._updateSlidesProgress(); // Get magnet after slide coordinates are updated var magnet = this.magnet; // Active index change if (magnet && magnet.slide.index !== this._activeIndex && (typeof this._targetIndex === 'undefined' || magnet.slide.index === this._targetIndex)) { this._activeIndex = magnet.slide.index; this._targetIndex = undefined; this.callbacks.emit('activeSlide', this.activeSlide); } // Check if friction is allowed var canHaveFriction = (swipe.isSwiping && swipe.allowFriction) || !swipe.isSwiping; // Apply friction if (magnet && canHaveFriction && frameDuration > 0 && props.friction >= 0 && !track.isSlideScrolling && !props.freemode) { track.target = (0, utils_1.damp)(track.target, track.current + magnet.diff, props.friction * props.lerp, frameDuration, 0.000001); } // Render slides this.slides.forEach(function (slide) { return slide.render(); }); // Emit Calbacks this.callbacks.emit('update', undefined); }; /** Update slides values */ Snap.prototype._updateSlidesCoords = function () { var _a = this, slides = _a.slides, track = _a.track; var isCentered = this.props.centered; var offset = isCentered ? this._domSize / 2 - this.firstSlideSize / 2 : 0; slides.forEach(function (slide) { var staticCoord = slide.staticCoord, size = slide.size; if (!track.canLoop) { slide.setCoord(staticCoord + offset - track.current); return; } if (isCentered) { slide.setCoord((0, utils_1.loop)(staticCoord + offset - track.current, -track.max / 2 + offset, track.max / 2 + offset)); return; } slide.setCoord((0, utils_1.loop)(staticCoord - track.current, -size, track.max - size)); }); }; Object.defineProperty(Snap.prototype, "firstSlideSize", { /** Get first slide size */ get: function () { return this.slides[0].size; }, enumerable: false, configurable: true }); /** Update slides progress */ Snap.prototype._updateSlidesProgress = function () { var _this = this; var _a = this, slides = _a.slides, domSize = _a.domSize; slides.forEach(function (slide) { var coord = slide.coord, size = slide.size; if (_this.props.centered) { var center = domSize / 2 - size / 2; slide.setProgress((0, utils_1.scoped)(coord, center, center - size)); return; } slide.setProgress((0, utils_1.scoped)(coord, 0, -size)); }); }; Object.defineProperty(Snap.prototype, "magnet", { /** Get nearest magnet */ get: function () { // todo: search only in nearby slides var current = this.track.loopedCurrent; var magnets = this.slides.flatMap(function (slide) { return slide.magnets.map(function (magnet) { return ({ slide: slide, magnet: magnet, index: slide.index }); }); }); if (magnets.length === 0) { return undefined; } var magnet = magnets.reduce(function (p, c) { return Math.abs(c.magnet - current) < Math.abs(p.magnet - current) ? c : p; }); return __assign(__assign({}, magnet), { diff: magnet.magnet - current }); }, enumerable: false, configurable: true }); /** Cancel sticky behavior */ Snap.prototype.cancelTransition = function () { var _a; (_a = this._timeline) === null || _a === void 0 ? void 0 : _a.destroy(); this._timeline = undefined; }; /** Stick to the nearest magnet */ Snap.prototype.stick = function () { var magnet = this.magnet; if (this.track.isSlideScrolling || !magnet) { return; } this.toCoord(this.track.current + magnet.diff); }; /** Go to a definite coordinate */ Snap.prototype.toCoord = function (coordinate, duration) { var _this = this; if (duration === void 0) { duration = this.props.duration; } if (this.isEmpty) { return false; } this.cancelTransition(); var _a = this, track = _a.track, props = _a.props, callbacks = _a.callbacks; var start = track.current; var end = coordinate; var diff = Math.abs(end - start); var tm = new Timeline_1.Timeline({ duration: typeof duration === 'number' ? duration : duration(diff), easing: props.easing, }); this._timeline = tm; tm.on('start', function () { return callbacks.emit('timelineStart', undefined); }); tm.on('update', function (data) { track.current = (0, utils_1.lerp)(start, end, data.eased); track.target = track.current; if (data.progress === 1) { _this._targetIndex = undefined; } _this._render(); callbacks.emit('timelineUpdate', data); }); tm.on('end', function () { tm.destroy(); callbacks.emit('timelineEnd', undefined); _this._timeline = undefined; }); tm.on('destroy', function () { _this._targetIndex = undefined; }); tm.play(); return true; }; /** Go to a slide by index */ Snap.prototype.toSlide = function (targetIndex, _a) { var _b = _a === void 0 ? {} : _a, _c = _b.direction, direction = _c === void 0 ? null : _c, _d = _b.duration, duration = _d === void 0 ? this.props.duration : _d; var _e = this, isEmpty = _e.isEmpty, activeIndex = _e.activeIndex, slides = _e.slides, track = _e.track, props = _e.props; var current = track.current, max = track.max, loopCount = track.loopCount; if (isEmpty) { return false; } var index = (0, utils_1.loop)(targetIndex, 0, this.slides.length); // Stick if the same slide if (index === activeIndex) { this.stick(); return false; } this._targetIndex = index; var slideMagnets = slides[index].magnets; // Use static magnet when not looping if (!props.loop) { return this.toCoord(slideMagnets[0], duration); } // Or calculate closest magnet var targetMagnet = slideMagnets[0] + loopCount * max; var targetMagnetMin = targetMagnet - max; var targetMagnetMax = targetMagnet + max; var allMagnets = [targetMagnetMin, targetMagnet, targetMagnetMax]; if (typeof direction === 'string') { var magnets = allMagnets.filter(function (magnet) { return direction === 'next' ? magnet >= current : magnet <= current; }); var magnet_1 = (0, utils_1.closest)(current, magnets); return this.toCoord(magnet_1, duration); } var magnet = (0, utils_1.closest)(current, allMagnets); return this.toCoord(magnet, duration); }; /** Go to next slide */ Snap.prototype.next = function (_a) { var _b = _a === void 0 ? {} : _a, _c = _b.duration, duration = _c === void 0 ? this.props.duration : _c, _d = _b.skip, skip = _d === void 0 ? 1 : _d; var _e = this, props = _e.props, slides = _e.slides, activeIndex = _e.activeIndex; var index = props.loop ? (0, utils_1.loop)(activeIndex + skip, 0, slides.length) : Math.min(activeIndex + skip, slides.length - 1); return this.toSlide(index, { duration: duration, direction: 'next' }); }; /** Go to previous slide */ Snap.prototype.prev = function (_a) { var _b = _a === void 0 ? {} : _a, _c = _b.duration, duration = _c === void 0 ? this.props.duration : _c, _d = _b.skip, skip = _d === void 0 ? 1 : _d; var _e = this, props = _e.props, slides = _e.slides, activeIndex = _e.activeIndex; var index = props.loop ? (0, utils_1.loop)(activeIndex - skip, 0, slides.length) : Math.max(activeIndex - skip, 0); return this.toSlide(index, { duration: duration, direction: 'prev' }); }; /** * Destroys the component and clears all timeouts and resources. */ Snap.prototype._destroy = function () { _super.prototype._destroy.call(this); this._resizeHandler.remove(); this.cancelTransition(); this._raf.destroy(); this._slides.forEach(function (slide) { return slide.detach(); }); }; return Snap; }(base_1.Module)); exports.Snap = Snap; //# sourceMappingURL=index.js.map