mirror of
https://github.com/acid-info/new-codex.storage.git
synced 2026-08-31 13:21:07 +00:00
540 lines
13 KiB
JavaScript
540 lines
13 KiB
JavaScript
export const waitForVariable = (V, checkInterval = 100) => {
|
|
return new Promise((resolve) => {
|
|
const interval = setInterval(() => {
|
|
if (window[V] !== undefined) {
|
|
clearInterval(interval);
|
|
resolve(window[V]);
|
|
}
|
|
}, checkInterval);
|
|
});
|
|
};
|
|
|
|
export const waitScript = (src, isModule = false) => {
|
|
return new Promise((resolve) => {
|
|
console.info("Loading script:", src);
|
|
const script = document.createElement("script");
|
|
script.src = src;
|
|
if (isModule) {
|
|
script.type = "module";
|
|
}
|
|
script.onload = resolve;
|
|
document.head.appendChild(script);
|
|
});
|
|
};
|
|
|
|
export const waitStyle = (href) => {
|
|
const link = document.createElement("link");
|
|
link.rel = "stylesheet";
|
|
link.href = href;
|
|
document.head.appendChild(link);
|
|
};
|
|
|
|
export function waitForPageLoaded() {
|
|
return new Promise((resolve) => {
|
|
if (document.readyState === "complete") {
|
|
// Page already loaded
|
|
resolve();
|
|
} else {
|
|
// Wait for load event
|
|
window.addEventListener("load", () => resolve(), { once: true });
|
|
}
|
|
});
|
|
}
|
|
|
|
export function loadGoogleFont(href) {
|
|
const link = document.createElement("link");
|
|
link.rel = "stylesheet";
|
|
link.href = href;
|
|
document.head.appendChild(link);
|
|
}
|
|
|
|
export const addAutoplayProgressListeners = (emblaApi, progressNode) => {
|
|
const progressBarNode = document.querySelector(".embla-progress-bar");
|
|
|
|
let animationName = "";
|
|
let timeoutId = 0;
|
|
let rafId = 0;
|
|
|
|
const startProgress = (emblaApi) => {
|
|
const autoplay = emblaApi?.plugins()?.autoplay;
|
|
if (!autoplay) return;
|
|
|
|
const timeUntilNext = autoplay.timeUntilNext();
|
|
if (timeUntilNext === null) return;
|
|
|
|
if (!animationName) {
|
|
const style = window.getComputedStyle(progressBarNode);
|
|
animationName = style.animationName;
|
|
}
|
|
|
|
progressBarNode.style.animationName = "none";
|
|
progressBarNode.style.transform = "translate3d(0,0,0)";
|
|
|
|
rafId = window.requestAnimationFrame(() => {
|
|
timeoutId = window.setTimeout(() => {
|
|
progressBarNode.style.animationName = animationName;
|
|
progressBarNode.style.animationDuration = `${timeUntilNext}ms`;
|
|
}, 0);
|
|
});
|
|
|
|
progressNode.removeAttribute("aria-hidden");
|
|
};
|
|
|
|
const stopProgress = (emblaApi) => {
|
|
const autoplay = emblaApi?.plugins()?.autoplay;
|
|
if (!autoplay) return;
|
|
|
|
progressNode.setAttribute("aria-hidden", "true");
|
|
};
|
|
|
|
emblaApi
|
|
.on("autoplay:timerset", startProgress)
|
|
.on("autoplay:timerstopped", stopProgress);
|
|
|
|
return () => {
|
|
emblaApi
|
|
.off("autoplay:timerset", startProgress)
|
|
.off("autoplay:timerstopped", stopProgress);
|
|
};
|
|
};
|
|
|
|
export const addTogglePrevNextBtnsActive = (emblaApi, prevBtn, nextBtn) => {
|
|
const togglePrevNextBtnsState = () => {
|
|
if (emblaApi.canScrollPrev()) prevBtn.removeAttribute("disabled");
|
|
else prevBtn.setAttribute("disabled", "disabled");
|
|
|
|
if (emblaApi.canScrollNext()) nextBtn.removeAttribute("disabled");
|
|
else nextBtn.setAttribute("disabled", "disabled");
|
|
};
|
|
|
|
emblaApi
|
|
.on("select", togglePrevNextBtnsState)
|
|
.on("init", togglePrevNextBtnsState)
|
|
.on("reInit", togglePrevNextBtnsState);
|
|
|
|
return () => {
|
|
prevBtn.removeAttribute("disabled");
|
|
nextBtn.removeAttribute("disabled");
|
|
};
|
|
};
|
|
|
|
export const addPrevNextBtnsClickHandlers = (emblaApi, prevBtn, nextBtn) => {
|
|
const scrollPrev = () => {
|
|
emblaApi.scrollPrev();
|
|
};
|
|
const scrollNext = () => {
|
|
emblaApi.scrollNext();
|
|
};
|
|
prevBtn.addEventListener("click", scrollPrev, false);
|
|
nextBtn.addEventListener("click", scrollNext, false);
|
|
|
|
const removeTogglePrevNextBtnsActive = addTogglePrevNextBtnsActive(
|
|
emblaApi,
|
|
prevBtn,
|
|
nextBtn
|
|
);
|
|
|
|
return () => {
|
|
removeTogglePrevNextBtnsActive();
|
|
prevBtn.removeEventListener("click", scrollPrev, false);
|
|
nextBtn.removeEventListener("click", scrollNext, false);
|
|
};
|
|
};
|
|
|
|
export const addDotBtnsAndClickHandlers = (emblaApi, dotsNode) => {
|
|
let dotNodes = [];
|
|
|
|
const addDotBtnsWithClickHandlers = () => {
|
|
dotsNode.innerHTML = emblaApi
|
|
.scrollSnapList()
|
|
.map(() => '<button class="dot" type="button"></button>')
|
|
.join("");
|
|
|
|
const scrollTo = (index) => {
|
|
emblaApi.scrollTo(index);
|
|
};
|
|
|
|
dotNodes = Array.from(dotsNode.querySelectorAll(".dot"));
|
|
dotNodes.forEach((dotNode, index) => {
|
|
dotNode.addEventListener("click", () => scrollTo(index), false);
|
|
});
|
|
};
|
|
|
|
const toggleDotBtnsActive = () => {
|
|
const previous = emblaApi.previousScrollSnap();
|
|
const selected = emblaApi.selectedScrollSnap();
|
|
dotNodes[previous].removeAttribute("aria-selected");
|
|
dotNodes[selected].setAttribute("aria-selected", "true");
|
|
};
|
|
|
|
emblaApi
|
|
.on("init", addDotBtnsWithClickHandlers)
|
|
.on("reInit", addDotBtnsWithClickHandlers)
|
|
.on("init", toggleDotBtnsActive)
|
|
.on("reInit", toggleDotBtnsActive)
|
|
.on("select", toggleDotBtnsActive);
|
|
|
|
return () => {
|
|
dotsNode.innerHTML = "";
|
|
};
|
|
};
|
|
|
|
const TWEEN_FACTOR_BASE = 0.52;
|
|
let tweenFactor = 0;
|
|
let tweenNodes = [];
|
|
|
|
const numberWithinRange = (number, min, max) =>
|
|
Math.min(Math.max(number, min), max);
|
|
|
|
const setTweenNodes = (emblaApi) => {
|
|
tweenNodes = emblaApi.slideNodes().map((slideNode) => {
|
|
return slideNode;
|
|
});
|
|
};
|
|
|
|
const setTweenFactor = (emblaApi) => {
|
|
tweenFactor = TWEEN_FACTOR_BASE * emblaApi.scrollSnapList().length;
|
|
};
|
|
|
|
const tweenScale = (emblaApi, eventName) => {
|
|
const engine = emblaApi.internalEngine();
|
|
const scrollProgress = emblaApi.scrollProgress();
|
|
const slidesInView = emblaApi.slidesInView();
|
|
const isScrollEvent = eventName === "scroll";
|
|
|
|
emblaApi.scrollSnapList().forEach((scrollSnap, snapIndex) => {
|
|
let diffToTarget = scrollSnap - scrollProgress;
|
|
const slidesInSnap = engine.slideRegistry[snapIndex];
|
|
|
|
slidesInSnap.forEach((slideIndex) => {
|
|
if (isScrollEvent && !slidesInView.includes(slideIndex)) return;
|
|
|
|
if (engine.options.loop) {
|
|
engine.slideLooper.loopPoints.forEach((loopItem) => {
|
|
const target = loopItem.target();
|
|
|
|
if (slideIndex === loopItem.index && target !== 0) {
|
|
const sign = Math.sign(target);
|
|
|
|
if (sign === -1) {
|
|
diffToTarget = scrollSnap - (1 + scrollProgress);
|
|
}
|
|
if (sign === 1) {
|
|
diffToTarget = scrollSnap + (1 - scrollProgress);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
const tweenValue = 1 - Math.abs(diffToTarget * tweenFactor);
|
|
const scale = numberWithinRange(tweenValue, 0, 1).toString();
|
|
const tweenNode = tweenNodes[slideIndex];
|
|
const opacity = numberWithinRange(tweenValue, 0, 1).toString();
|
|
tweenNode.style.transform = `scale(${scale})`;
|
|
tweenNode.style.opacity = opacity;
|
|
});
|
|
});
|
|
};
|
|
|
|
export const setupTweenScale = (emblaApi) => {
|
|
setTweenNodes(emblaApi);
|
|
setTweenFactor(emblaApi);
|
|
tweenScale(emblaApi);
|
|
|
|
emblaApi
|
|
.on("reInit", setTweenNodes)
|
|
.on("reInit", setTweenFactor)
|
|
.on("reInit", tweenScale)
|
|
.on("scroll", tweenScale)
|
|
.on("slideFocus", tweenScale);
|
|
|
|
return () => {
|
|
tweenNodes.forEach((slide) => slide.removeAttribute("style"));
|
|
};
|
|
};
|
|
|
|
if (!window.Splitting) {
|
|
await Promise.all([
|
|
waitStyle(
|
|
"https://cdn.jsdelivr.net/npm/splitting@1.1.0/dist/splitting.css"
|
|
),
|
|
waitStyle(
|
|
"https://cdn.jsdelivr.net/npm/splitting@1.1.0/dist/splitting-cells.css"
|
|
),
|
|
waitScript(
|
|
"https://cdn.jsdelivr.net/npm/splitting@1.1.0/dist/splitting.min.js"
|
|
),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Class representing one line
|
|
*/
|
|
class Line {
|
|
// line position
|
|
position = -1;
|
|
// cells/chars
|
|
cells = [];
|
|
|
|
/**
|
|
* Constructor.
|
|
* @param {Element} DOM_el - the char element (<span>)
|
|
*/
|
|
constructor(linePosition) {
|
|
this.position = linePosition;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class representing one cell/char
|
|
*/
|
|
class Cell {
|
|
// DOM elements
|
|
DOM = {
|
|
// the char element (<span>)
|
|
el: null,
|
|
};
|
|
// cell position
|
|
position = -1;
|
|
// previous cell position
|
|
previousCellPosition = -1;
|
|
// original innerHTML
|
|
original;
|
|
// current state/innerHTML
|
|
state;
|
|
color;
|
|
originalColor;
|
|
// cached values
|
|
cache;
|
|
|
|
/**
|
|
* Constructor.
|
|
* @param {Element} DOM_el - the char element (<span>)
|
|
*/
|
|
constructor(DOM_el, { position, previousCellPosition } = {}) {
|
|
this.DOM.el = DOM_el;
|
|
this.original = this.DOM.el.innerHTML;
|
|
this.state = this.original;
|
|
this.color = this.originalColor = getComputedStyle(
|
|
document.documentElement
|
|
).getPropertyValue("--color-text");
|
|
this.position = position;
|
|
this.previousCellPosition = previousCellPosition;
|
|
}
|
|
/**
|
|
* @param {string} value
|
|
*/
|
|
set(value) {
|
|
this.state = value;
|
|
this.DOM.el.innerHTML = this.state;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Class representing the TypeShuffle object
|
|
*/
|
|
export class TypeShuffle {
|
|
// DOM elements
|
|
DOM = {
|
|
// the main text element
|
|
el: null,
|
|
};
|
|
// array of Line objs
|
|
lines = [];
|
|
// array of letters and symbols
|
|
lettersAndSymbols = [
|
|
"A",
|
|
"B",
|
|
"C",
|
|
"D",
|
|
"E",
|
|
"F",
|
|
"G",
|
|
"H",
|
|
"I",
|
|
"J",
|
|
"K",
|
|
"L",
|
|
"M",
|
|
"N",
|
|
"O",
|
|
"P",
|
|
"Q",
|
|
"R",
|
|
"S",
|
|
"T",
|
|
"U",
|
|
"V",
|
|
"W",
|
|
"X",
|
|
"Y",
|
|
"Z",
|
|
"!",
|
|
"@",
|
|
"#",
|
|
"$",
|
|
"&",
|
|
"*",
|
|
"(",
|
|
")",
|
|
"-",
|
|
"_",
|
|
"+",
|
|
"=",
|
|
"/",
|
|
"[",
|
|
"]",
|
|
"{",
|
|
"}",
|
|
";",
|
|
":",
|
|
"<",
|
|
">",
|
|
",",
|
|
"0",
|
|
"1",
|
|
"2",
|
|
"3",
|
|
"4",
|
|
"5",
|
|
"6",
|
|
"7",
|
|
"8",
|
|
"9",
|
|
];
|
|
// effects and respective methods
|
|
effects = {
|
|
fx6: () => this.fx6(),
|
|
};
|
|
totalChars = 0;
|
|
|
|
/**
|
|
* Constructor.
|
|
* @param {Element} DOM_el - main text element
|
|
*/
|
|
constructor(DOM_el) {
|
|
this.DOM.el = DOM_el;
|
|
// Apply Splitting (two times to have lines, words and chars)
|
|
const results = Splitting({
|
|
target: this.DOM.el,
|
|
by: "lines",
|
|
});
|
|
results.forEach((s) => Splitting({ target: s.words }));
|
|
|
|
// for every line
|
|
for (const [linePosition, lineArr] of results[0].lines.entries()) {
|
|
// create a new Line
|
|
const line = new Line(linePosition);
|
|
let cells = [];
|
|
let charCount = 0;
|
|
// for every word of each line
|
|
for (const word of lineArr) {
|
|
// for every character of each line
|
|
for (const char of [...word.querySelectorAll(".char")]) {
|
|
cells.push(
|
|
new Cell(char, {
|
|
position: charCount,
|
|
previousCellPosition: charCount === 0 ? -1 : charCount - 1,
|
|
})
|
|
);
|
|
++charCount;
|
|
}
|
|
}
|
|
line.cells = cells;
|
|
this.lines.push(line);
|
|
this.totalChars += charCount;
|
|
}
|
|
|
|
// TODO
|
|
// window.addEventListener('resize', () => this.resize());
|
|
}
|
|
/**
|
|
* clear all the cells chars
|
|
*/
|
|
clearCells() {
|
|
for (const line of this.lines) {
|
|
for (const cell of line.cells) {
|
|
cell.set(" ");
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
*
|
|
* @returns {string} a random char from this.lettersAndSymbols
|
|
*/
|
|
getRandomChar() {
|
|
return this.lettersAndSymbols[
|
|
Math.floor(Math.random() * this.lettersAndSymbols.length)
|
|
];
|
|
}
|
|
fx6() {
|
|
for (const line of this.lines) {
|
|
for (const cell of line.cells) {
|
|
cell.cache = { state: cell.state, color: cell.color };
|
|
}
|
|
}
|
|
// max iterations for each cell to change the current value
|
|
const MAX_CELL_ITERATIONS = 10;
|
|
let finished = 0;
|
|
const loop = (line, cell, iteration = 0) => {
|
|
cell.cache = { state: cell.state, color: cell.color };
|
|
|
|
if (iteration === MAX_CELL_ITERATIONS - 1) {
|
|
cell.set(cell.original);
|
|
|
|
cell.color = cell.originalColor;
|
|
cell.DOM.el.style.color = cell.color;
|
|
|
|
++finished;
|
|
if (finished === this.totalChars) {
|
|
this.isAnimating = false;
|
|
}
|
|
|
|
// const lines = this.lines;
|
|
// setTimeout(function () {
|
|
// console.info("TypeShuffle fx6 finished");
|
|
// cell.set(cell.original);
|
|
// }, 500);
|
|
} else {
|
|
cell.set(this.getRandomChar());
|
|
|
|
cell.color = ["#2b4539", "#61dca3", "#61b3dc"][
|
|
Math.floor(Math.random() * 3)
|
|
];
|
|
cell.DOM.el.style.color = cell.color;
|
|
}
|
|
|
|
++iteration;
|
|
if (iteration < MAX_CELL_ITERATIONS) {
|
|
setTimeout(() => loop(line, cell, iteration), 75);
|
|
}
|
|
};
|
|
|
|
for (const line of this.lines) {
|
|
for (const cell of line.cells) {
|
|
setTimeout(() => loop(line, cell), (line.position + 1) * 10);
|
|
}
|
|
}
|
|
}
|
|
|
|
reset() {
|
|
for (const line of this.lines) {
|
|
for (const cell of line.cells) {
|
|
cell.set(cell.original);
|
|
cell.color = cell.originalColor;
|
|
cell.DOM.el.style.color = cell.originalColor;
|
|
}
|
|
}
|
|
this.isAnimating = false;
|
|
}
|
|
/**
|
|
* call the right effect method (defined in this.effects)
|
|
* @param {string} effect - effect type
|
|
*/
|
|
trigger(effect = "fx1") {
|
|
if (!(effect in this.effects) || this.isAnimating) return;
|
|
this.isAnimating = true;
|
|
this.effects[effect]();
|
|
}
|
|
}
|