smooth-text-rotator
Version:
Super simple JS library for rotating text with smooth animations
157 lines • 5.81 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TextRotator = void 0;
const type_guards_1 = require("./type-guards");
class TextRotator {
constructor(options) {
this.intervalID = null;
this.started = false;
this.options = Object.assign({ automaticPause: true, interval: 4000 }, options);
if (this.options.element.dataset.phrases === undefined) {
throw {
error: new Error(`The 'phrases' data attribute is not defined in the passed element`),
element: this.options.element
};
}
const phrases = JSON.parse(this.options.element.dataset.phrases);
// Check if the parsed json is an array of string
if (!(0, type_guards_1.isArrayOfString)(phrases)) {
throw {
error: new Error(`The 'phrases' data attribute in the passed element is not a JSON that is an array of string`),
element: this.options.element
};
}
// TODO comment
this.phraseElements = [];
this.options.element.innerText = "";
phrases.forEach((phrase, index) => {
const child = document.createElement('span');
if (index > 0) {
child.style.position = "absolute";
child.style.top = "0";
child.style.left = "0";
}
child.style.display = "inline-block"; // because the resize observer does not work properly on inline elements
child.style.whiteSpace = "nowrap";
child.style.transition = 'opacity .5s cubic-bezier(.23,1,.32,1)';
child.innerText = phrase;
this.options.element.appendChild(child);
this.phraseElements.push(child);
});
this.options.element.style.display = 'inline-block';
this.options.element.style.position = 'relative';
this.options.element.style.transition = 'width .5s cubic-bezier(.23,1,.32,1)';
this.resetPhrases();
this.setupResizeObserver();
if (this.options.automaticPause) {
this.setupIntersectionObserver();
}
// this.phrases = phrases;
this.index = 0;
}
start() {
this.started = true;
this.index = 0;
this.startInterval();
}
stop() {
this.started = false;
this.index = 0;
this.resetPhrases();
// If the rotator is paused, the interval is already stopped
if (this.intervalID) {
this.stopInterval();
}
}
pause() {
this.stopInterval();
}
resume() {
this.startInterval();
}
setupResizeObserver() {
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
if (entry.target === this.phraseElements[this.index]) {
this.options.element.style.width = `${entry.borderBoxSize[0].inlineSize}px`;
}
});
});
this.phraseElements.forEach((phraseElement) => {
resizeObserver.observe(phraseElement);
});
}
setupIntersectionObserver() {
const observer = new IntersectionObserver((entries) => {
if (this.started) {
if (entries[0].isIntersecting) {
if (this.intervalID === null) {
this.resume();
}
}
else {
if (this.intervalID !== null) {
this.pause();
}
}
}
});
observer.observe(this.options.element);
}
startInterval() {
if (this.intervalID !== null) {
throw new Error(`Cannot start multiple ${window.setInterval.name} function`);
}
this.intervalID = window.setInterval(this.doAnimation.bind(this), this.options.interval);
}
stopInterval() {
if (this.intervalID === null) {
throw new Error(`Cannot stop ${window.setInterval.name} that dont exists`);
}
window.clearInterval(this.intervalID);
this.intervalID = null;
}
doAnimation() {
this.displayPhrase(this.index = (this.index + 1) % this.phraseElements.length);
}
displayPhrase(index) {
// hide previously displayed element
this.hidePhrase(index === 0 ? this.phraseElements.length - 1 : index - 1);
// set the width of the parent span according to the width of the child to display
this.setWidth(index);
// display the phrase to display
this.showPhrase(index);
}
showPhrase(index) {
this.phraseElements[index].style.opacity = '1';
this.phraseElements[index].removeAttribute("aria-hidden");
this.phraseElements[index].style.pointerEvents = '';
this.phraseElements[index].style.userSelect = '';
}
hidePhrase(index) {
this.phraseElements[index].style.opacity = '0';
this.phraseElements[index].setAttribute("aria-hidden", "true");
this.phraseElements[index].style.pointerEvents = "none";
this.phraseElements[index].style.userSelect = "none";
}
setWidth(index) {
this.options.element.style.width = `${this.phraseElements[index].offsetWidth}px`;
}
/**
* Hide all the phrases, show the first one and set the width
* @private
*/
resetPhrases() {
for (let i = 0; i < this.phraseElements.length; i++) {
if (i === 0) {
this.setWidth(i);
this.showPhrase(i);
}
else {
this.hidePhrase(i);
}
}
}
}
exports.TextRotator = TextRotator;
//# sourceMappingURL=text-rotator.js.map