crawling-typer
Version:
Transform your text with dynamic typing animations! crawling-typer lets you display an array of strings one at a time, each with its own color. Customize typing speed, delete speed, and pauses between strings. Enjoy full control with loop counts, post-loo
272 lines (232 loc) • 10.2 kB
JavaScript
/****************************************************************************************
______ ___ _ ____ ________ ____
/ ____/________ __ __/ (_)___ ____ | | / / /_ ____ ______/ __/ __ \/ __ \
/ / / ___/ __ `/ | /| / / / / __ \/ __ `/ | /| / / __ \/ __ `/ ___/ /_/ /_/ / / / /
/ /___/ / / /_/ /| |/ |/ / / / / / / /_/ /| |/ |/ / / / / /_/ / / / __/\__, / /_/ /
\____/_/ \__,_/ |__/|__/_/_/_/ /_/\__, / |__/|__/_/ /_/\__,_/_/ /_/ /____/\____/
/____/
***************************************************************************************/
class Word
{
constructor(text, color)
{
this.text = text;
this.color = color || "black";
}
}
class BHVR
{
constructor(name = "none", index = 0)
{
this.name = name || "none";
this.index = index;
}
}
function TypeWords(words = [], target = null, typeSpeed = 250, deleteSpeed = 75, pauseTime = 1000, loops = 0, state = "play", bhvr = new BHVR()) {
if (target == null) {
console.error("No target assigned to TypeWords function");
return;
}
let currentState = state;
let isPaused = (state === "pause");
let pauseTimeouts = [];
let currentWordIndex = 0;
let currentCharIndex = 0;
let isDeleting = false; // To track if we're in the delete phase
let remainingTime = 0; // To track time left for the next action
let currentLoops = loops;
let lastTypedTimestamp = 0; // To track when the last character was typed or deleted
// Default cursor settings
let cursorVisible = false;
let cursorSymbol = '';
let cursorBlinkInterval = null;
let cursorBlinkSpeed = 500; // Default blink speed in ms
// Update the target text with or without the cursor symbol
let updateTargetText = (newText, textColor) => {
target.style.color = textColor;
let textWithCursor = cursorVisible ? newText + cursorSymbol : newText; // Append cursor symbol if visible
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
target.value = textWithCursor;
} else {
target.innerText = textWithCursor;
}
};
/*EDIT: In version v.1.1.1 cursor visable checks were added because it's inivisibility caused issue like the text being set to an empty string*/
// Function to toggle cursor visibility (blinking effect)
let blinkCursor = () => {
// Clear the interval if it's already running
if (cursorBlinkInterval) clearInterval(cursorBlinkInterval);
// If blink speed is 0, don't blink the cursor, keep it visible or hidden as is
if (cursorBlinkSpeed === 0) {
if(cursorVisible)
{
updateTargetText(target.innerText.slice(0, -cursorSymbol.length), target.style.color);
return;
}
}
// Start the blink animation independent of the typing/deleting
cursorBlinkInterval = setInterval(() => {
// Ensure we're not slicing to an empty string
if (target.innerText.length > 0 && cursorVisible) {
cursorVisible = !cursorVisible; // Toggle cursor visibility
let textWithoutCursor = target.innerText.slice(0, -cursorSymbol.length);
updateTargetText(cursorVisible ? textWithoutCursor : target.innerText, target.style.color);
}
}, cursorBlinkSpeed); // Blink at intervals of cursorBlinkSpeed
};
// Clear the blinking interval
let clearCursorBlink = () => {
if (cursorBlinkInterval) {
clearInterval(cursorBlinkInterval);
cursorBlinkInterval = null;
}
};
if (words.length === 0) {
console.warn("The 'words' array is empty, please enter words");
words.push(new Word("Please provide text...", "black"));
}
let wordArray = words.map(word => new Word(word.text, word.color));
typeSpeed = CheckForAcceptableValue("typeSpeed", typeSpeed, 10, "lt");
deleteSpeed = CheckForAcceptableValue("deleteSpeed", deleteSpeed, 10, "lt");
pauseTime = CheckForAcceptableValue("pauseTime", pauseTime, 10, "lt");
loops = CheckForAcceptableValue("loops", loops, 0, "lt");
bhvr.index = CheckForAcceptableValue("behavior index", bhvr.index, wordArray.length - 1, "gt");
bhvr.index = CheckForAcceptableValue("behavior index", bhvr.index, 0, "lt");
if (bhvr.name != "forwards") bhvr.name = CheckForAcceptableValue("behavior name", bhvr.name, "none", "eq");
let effectBhvr = new BHVR(bhvr.name, bhvr.index);
function performTypingAnimation(currentLoops) {
if (currentLoops < 1 && loops !== 0) return; // Stop if loops are finished
if (currentState === "pause") return; // Stop if paused
let word = wordArray[currentWordIndex];
let delay = isDeleting ? deleteSpeed : typeSpeed;
// Reset the blinking cursor every time a char is typed or deleted
cursorVisible = true; // Ensure the cursor is visible while typing/deleting
updateTargetText(word.text.substring(0, currentCharIndex), word.color); // Ensure the cursor resets after the char update
clearCursorBlink(); // Clear the blink timer to stop blinking
blinkCursor(); // Restart blinking cursor, but only after typing pauses
// Typing phase
if (!isDeleting && currentCharIndex < word.text.length) {
updateTargetText(word.text.substring(0, currentCharIndex + 1), word.color);
currentCharIndex++;
remainingTime = typeSpeed;
}
// Deleting phase
else if (isDeleting && currentCharIndex > 0) {
updateTargetText(word.text.substring(0, currentCharIndex - 1), word.color);
currentCharIndex--;
remainingTime = deleteSpeed;
}
// Finished typing, pause before deleting
else if (!isDeleting && currentCharIndex === word.text.length) {
// Check if it's the last loop and should not delete
if (loops !== 0 && currentLoops === 1 && effectBhvr.name === "forwards" && currentWordIndex === effectBhvr.index) {
// On the last loop, don't delete the word and stop
return;
}
isDeleting = true;
remainingTime = pauseTime;
}
// Finished deleting, move to next word
else if (isDeleting && currentCharIndex === 0) {
currentWordIndex++;
// Check if it's the last loop
if (currentWordIndex >= wordArray.length) {
currentWordIndex = 0;
currentLoops--;
}
isDeleting = false;
remainingTime = pauseTime;
}
pauseTimeouts.push(setTimeout(() => {
performTypingAnimation(currentLoops);
}, remainingTime));
}
function pause() {
currentState = "pause";
isPaused = true;
pauseTimeouts.forEach(timeout => clearTimeout(timeout));
pauseTimeouts = [];
clearCursorBlink(); // Stop cursor blinking when paused
}
function resume() {
if (isPaused) {
currentState = "play";
isPaused = false;
// Resume from the saved state
performTypingAnimation(currentLoops);
}
}
function reset() {
currentState = "play";
isPaused = false;
currentWordIndex = 0;
currentCharIndex = 0;
isDeleting = false;
remainingTime = 0;
currentLoops = loops;
pauseTimeouts.forEach(timeout => clearTimeout(timeout));
pauseTimeouts = [];
clearCursorBlink();
performTypingAnimation(currentLoops);
}
function cursor(visible = false, blinkSpeed = 0, symbol = '_') {
if(visible != true) visible = CheckForAcceptableValue("Cursor Visibility", visible, false, "eq");
cursorVisible = visible;
cursorSymbol = symbol;
blinkSpeed = CheckForAcceptableValue("Blink Speed", blinkSpeed, 0, "lt");
cursorBlinkSpeed = blinkSpeed;
if (cursorVisible) {
blinkCursor(); // Start blinking cursor independently
} else {
clearCursorBlink(); // Stop blinking if cursor is not visible
}
}
// Start the animation
performTypingAnimation(currentLoops);
// Return pause, resume, reset, and cursor methods
return {
pause,
resume,
reset,
cursor
};
}
function CheckForAcceptableValue(varName = "", value = null, def = null, checkType = null)
{
if(value == null || def == null || checkType == null)
{
console.error("Please provide all data needed when checking if value is acceptable for " + varName + "\nvalue: " + value + "\ndefault value: " + def + "\ncheckType: " + checkType);
return;
}
switch(checkType.toLowerCase())
{
case "lt":
if(value < def)
{
console.warn(varName + " cannot be less than " + def + " -> it's value was set to " + def + " by default");
value = def;
}
break;
case "gt":
if(value > def)
{
console.warn(varName + " cannot be greater than " + def + " -> it's value was set to " + def + " by default");
value = def;
}
break;
case "eq":
if(value != def)
{
console.warn(varName + " doesn't recognize: " + value + " -> " + varName + " was set to " + def + " by default");
value = def;
}
break;
default:
console.error(checkType + " is not an acceptable variable check type");
value = null;
break;
}
return value;
}
//export { TypeWords }; //used for local testing
export default TypeWords;