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
JavaScript
"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