vevet
Version:
Vevet is a JavaScript library for creative development that simplifies crafting rich interactions like split text animations, carousels, marquees, preloading, and more.
390 lines • 15.9 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);
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Marquee = void 0;
var math_1 = require("../../utils/math");
var listeners_1 = require("../../utils/listeners");
var Module_1 = require("../../base/Module");
var Raf_1 = require("../Raf");
var initVevet_1 = require("../../global/initVevet");
var utils_1 = require("../../utils");
__exportStar(require("./types"), exports);
/**
* A custom marquee component that smoothly scrolls its child elements.
*
* This component is designed to loop elements horizontally within a container,
* with support for customization such as speed, gap, pause on hover, and more.
*
* [Documentation](https://vevetjs.com/docs/Marquee)
*
* @group Components
*/
var Marquee = /** @class */ (function (_super) {
__extends(Marquee, _super);
function Marquee(props) {
var _this = _super.call(this, props) || this;
/** Current container size (width or height depending on direction) */
_this._size = 0;
/** Initial child nodes of the container */
_this._initialNodes = [];
/** Array of marquee element nodes */
_this._elements = [];
/** Array of sizes of each child element */
_this._sizes = [];
/** Total size of all elements in the marquee */
_this._totalSize = 0;
/** The current marquee coordinate. */
_this._coord = 0;
var container = _this.props.container;
var isVertical = _this.isVertical;
if (!container) {
throw new Error('Marquee container is not defined');
}
// Apply base styles to the container
container.style.position = 'relative';
container.style.display = 'flex';
container.style.flexDirection = isVertical ? 'column' : 'row';
container.style.alignItems = 'center';
container.style.justifyContent = 'flex-start';
container.style.overflow = 'hidden';
if (isVertical) {
container.style.height = '100%';
}
else {
container.style.width = '100%';
}
// Setup elements in the marquee
_this._setup();
// Create animation frame
_this._raf = new Raf_1.Raf({ enabled: _this.props.enabled, fpsRecalcFrames: 1 });
_this._raf.on('frame', function () {
var factor = _this.props.adjustSpeed ? _this._raf.fpsFactor : 1;
var speed = (0, utils_1.toPixels)(_this.props.speed);
_this._render(speed * factor);
});
// Pause on hover
var mouseenter = (0, listeners_1.addEventListener)(container, 'mouseenter', function () {
if (_this.props.pauseOnHover) {
_this._raf.pause();
}
});
_this.onDestroy(function () { return mouseenter(); });
// Resume on mouse leave
var mouseleave = (0, listeners_1.addEventListener)(container, 'mouseleave', function () {
if (_this.props.enabled) {
_this._raf.play();
}
});
_this.onDestroy(function () { return mouseleave(); });
// Intersection observer
_this._intersection = new IntersectionObserver(_this._handleIntersection.bind(_this), { root: null });
_this._intersection.observe(container);
return _this;
}
/** Get default static properties. */
Marquee.prototype._getStatic = function () {
return __assign(__assign({}, _super.prototype._getStatic.call(this)), { resizeDebounce: 0, hasWillChange: true, cloneNodes: true, direction: 'horizontal' });
};
/** Get default mutable properties. */
Marquee.prototype._getMutable = function () {
return __assign(__assign({}, _super.prototype._getMutable.call(this)), { speed: 1, gap: 0, enabled: true, pauseOnHover: false, centered: false, adjustSpeed: true, pauseOnOut: true });
};
Object.defineProperty(Marquee.prototype, "totalSize", {
/** Total size of all elements in the marquee (width or height depending on direction) */
get: function () {
return this._totalSize;
},
enumerable: false,
configurable: true
});
Object.defineProperty(Marquee.prototype, "totalWidth", {
/**
* Total width of all elements in the marquee
* @deprecated Use `totalSize` instead
*/
get: function () {
return this.totalSize;
},
enumerable: false,
configurable: true
});
Object.defineProperty(Marquee.prototype, "coord", {
/** The current marquee coordinate. */
get: function () {
return this._coord;
},
set: function (value) {
this._coord = value;
this.render(0);
},
enumerable: false,
configurable: true
});
Object.defineProperty(Marquee.prototype, "x", {
/**
* The current coordinate of the marquee.
* @deprecated Use `coord` instead
*/
get: function () {
return this.coord;
},
set: function (value) {
this.coord = value;
},
enumerable: false,
configurable: true
});
Object.defineProperty(Marquee.prototype, "isVertical", {
/** Check if the marquee is vertical */
get: function () {
return this.props.direction === 'vertical';
},
enumerable: false,
configurable: true
});
/** Handles property changes */
Marquee.prototype._handleProps = function () {
_super.prototype._handleProps.call(this);
if (this.props.enabled) {
this._raf.play();
}
else {
this._raf.pause();
}
// Rerender the elements
this.resize();
this.render(0);
};
/** Initializes the marquee setup, including resizing and cloning elements */
Marquee.prototype._setup = function () {
var _this = this;
var _a;
(_a = this._lastSetup) === null || _a === void 0 ? void 0 : _a.call(this);
if (this.isDestroyed) {
return;
}
var _b = this.props, container = _b.container, resizeDebounce = _b.resizeDebounce;
// Save initial child nodes
this._initialNodes = __spreadArray([], Array.from(container.childNodes), true);
// Wrap text node if necessary
this._wrapTextNode();
// Store element references
this._elements = Array.from(container.children);
// Add necessary styles to elements
this._elements.forEach(function (element, index) {
return _this._applyNodeStyles(element, index !== 0);
});
// initial resize
this.resize();
// Resize on page load
var onPageLoad = (0, initVevet_1.initVevet)().onLoad(function () { return _this.resize(); });
// Handle resizing
var resizeHandler = (0, listeners_1.onResize)({
callback: function () { return _this.resize(); },
element: __spreadArray([container], this._elements, true),
viewportTarget: 'width',
resizeDebounce: resizeDebounce,
name: this.name,
});
// Setup cleanup function
this._lastSetup = function () {
onPageLoad();
resizeHandler.remove();
};
};
/**
* Wraps the first text node in the container in a span if no other elements exist.
*/
Marquee.prototype._wrapTextNode = function () {
var container = this.props.container;
var hasOneNode = container.childNodes.length === 1;
var node = container.childNodes[0];
// Wrap text node if it's the first child and not already wrapped
if (!node || node.nodeType !== 3 || !hasOneNode) {
return;
}
var wrapper = document.createElement('span');
wrapper.style.position = 'relative';
wrapper.style.display = 'block';
wrapper.style.width = 'max-content';
wrapper.style.whiteSpace = 'nowrap';
wrapper.appendChild(node);
container.appendChild(wrapper);
};
/**
* Adds necessary styles to a given element.
*/
Marquee.prototype._applyNodeStyles = function (element, isAbsolute) {
var el = element;
el.style.position = isAbsolute ? 'absolute' : 'relative';
el.style.top = isAbsolute && !this.isVertical ? '50%' : '0';
el.style.left = isAbsolute && this.isVertical ? '50%' : '0';
el.style.willChange = this.props.hasWillChange ? 'transform' : '';
el.style.flexShrink = '0';
if (this.isVertical) {
el.style.height = element.style.height || 'max-content';
}
else {
el.style.width = element.style.width || 'max-content';
}
};
/** Resizes the marquee, recalculating element positions and cloning if necessary. */
Marquee.prototype.resize = function () {
var _this = this;
if (this.isDestroyed) {
return;
}
var _a = this, props = _a.props, isVertical = _a.isVertical;
var container = props.container;
var gap = (0, utils_1.toPixels)(props.gap);
// Update container width
this._size = isVertical ? container.offsetHeight : container.offsetWidth;
// Update element sizes
this._sizes = this._elements.map(function (element) {
return (isVertical ? element.offsetHeight : element.offsetWidth) + gap;
});
this._totalSize = this._sizes.reduce(function (a, b) { return a + b; }, 0);
// Determine how many times to duplicate elements
var maxSize = Math.max.apply(Math, this._sizes);
var copyQuantity = Math.ceil((this._size + maxSize) / this._totalSize);
// update total size
this._totalSize = Math.max(this._totalSize, this._size + maxSize);
// Clone elements if necessary
if (props.cloneNodes && copyQuantity > 1) {
for (var i = 1; i < copyQuantity; i += 1) {
this._elements.forEach(function (element) {
var copy = element.cloneNode(true);
_this._applyNodeStyles(copy, true);
container.appendChild(copy);
});
}
// Update element references after cloning
this._elements = Array.from(container.children);
this.resize();
}
// Trigger resize callbacks
this.callbacks.emit('resize', undefined);
// Rerender the marquee
setTimeout(function () { return _this.render(0); }, 0);
};
/** Renders the marquee, adjusting element positions. */
Marquee.prototype.render = function (step) {
this._render(step);
};
/**
* Renders the marquee, calculating element positions based on the provided speed.
*/
Marquee.prototype._render = function (step) {
if (step === void 0) { step = this.props.speed; }
if (this.isDestroyed) {
return;
}
var _a = this, isVertical = _a.isVertical, props = _a.props;
// Update animation time
this._coord -= (0, utils_1.toPixels)(step);
// Calculate current position of the elements
var centerCoord = this._size * 0.5 + this._sizes[0] / 2 - (0, utils_1.toPixels)(props.gap);
var position = this._coord + (props.centered ? centerCoord : 0);
// Update each element's position
var prevStaticCoord = 0;
for (var index = 0; index < this._elements.length; index += 1) {
var element = this._elements[index];
var elementSize = this._sizes[index];
var coord = (0, math_1.loop)(position + prevStaticCoord, -elementSize, this._totalSize - elementSize);
// Apply transformations to position the element
if (isVertical) {
var x = element.style.position === 'relative' ? '0' : '-50%';
element.style.transform = "translate(".concat(x, ", ").concat(coord, "px)");
}
else {
var y = element.style.position === 'relative' ? '0' : '-50%';
element.style.transform = "translate(".concat(coord, "px, ").concat(y, ")");
}
prevStaticCoord += elementSize;
}
// Trigger render callbacks
this.callbacks.emit('render', undefined);
};
/**
* Handle intersection observer
*/
Marquee.prototype._handleIntersection = function (entries) {
var _this = this;
if (!this.props.pauseOnOut) {
return;
}
entries.forEach(function (entry) {
if (entry.isIntersecting && _this.props.enabled) {
_this._raf.play();
}
else {
_this._raf.pause();
}
});
};
/** Destroys the instance and cleans up resources */
Marquee.prototype._destroy = function () {
var _a, _b;
var container = this.props.container;
_super.prototype._destroy.call(this);
this._raf.destroy();
(_a = this._intersection) === null || _a === void 0 ? void 0 : _a.disconnect();
(_b = this._lastSetup) === null || _b === void 0 ? void 0 : _b.call(this);
// Remove all children and restore the initial nodes
while (container.firstChild) {
container.removeChild(container.firstChild);
}
this._initialNodes.forEach(function (node) { return container.appendChild(node); });
};
return Marquee;
}(Module_1.Module));
exports.Marquee = Marquee;
//# sourceMappingURL=index.js.map