Reputation: 57
function SetText(gg = `textttttt 😀`, cmd = `sudo --info`) {
window.scrollTo({ top: 0, behavior: 'smooth' });
if (document.getElementsByClassName('demo').length > 0) {
var i = 0;
var speed = 60;
document.getElementsByClassName('demo')[0].innerHTML = `<code class="shell-session demo hljs nginx"><span class="hljs-attribute">Website</span> <span class="hljs-regexp">~ $</span> ${cmd}`;
function typeItOut() {
if (i < gg.length) {
document.getElementsByClassName('demo')[0].innerHTML += gg.charAt(i);
i++;
setTimeout(typeItOut, speed);
}
}
setTimeout(typeItOut, 1800);
}
}
so that's the code, I want every time I click a button on my website it waits until the recursive finish then starts another one...
Upvotes: 0
Views: 137
Reputation: 5516
Can you use async/await
?
If you can, this will make it much easier to in effect "pause" each iteration through your string by a given timeout duration (see the handleIterateString
class function below).
This async handleIterateString
function will "pause" at each await
keyword, and wait until the promise returned by the await
expression has been resolved. Only then will it continue executing the async
function.
Also, you can "pause" the execution of the async
function where you initiate a new complete iteration through your string (see the await demo.handleIterateString
call inside the async function SetText
below.
In this way, you can wait for the entire iteration (i.e. typing behaviour) to finish before decrementing your click queue count.
If you have click events left in your queue, you can at that point call SetText
recursively.
Simply put: using async/await
makes it much easier to both control the speed of the typing behaviour, and wait for your typing behaviour to complete before doing anything else.
Try running the code snippet below.
class Typer {
/**
* @description delays execution for a given amount of time
* @param {number} ms - time in milliseconds
* @returns {Promise<void>}
*
* @private
*/
#delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
/**
* @description html for for displaying queue information
* @returns {{hasQueue: string, noQueue: string}}
* @private
*/
get #html() {
return {
hasQueue: `Click events waiting in queue: <span class="tag is-danger is-light is-large">${this.state.queueCount}</span>`,
noQueue: 'Queue is clear',
};
}
/**
* @description renders queue count information
* @returns {void}
* @private
*/
#renderCountText = () => {
const hasQueue = this.state.queueCount > 0;
const fn = hasQueue ? 'add' : 'remove';
document.getElementById('type-btn').classList[fn]('is-danger');
const htmlContent = this.#html[hasQueue ? 'hasQueue' : 'noQueue'];
this.render(htmlContent, '.queueCount');
};
/**
* @description accepts a html selector string
* @param {string} selector
*/
constructor(selector) {
this.selector = selector;
}
/**
* @description state of typer instance
* @const {{queueCount: number, speed: number}}
* @public
*/
state = {
queueCount: -1,
speed: 30,
};
/**
* @description appends a html string to the instance's html element
* @param {string} html
* @returns {void}
* @public
*/
append = (html) => {
document.querySelector(this.selector).innerHTML += html;
};
/**
* @description renders given html string inside element with given selector
* @param {string} html
* @param {string} [el]
* @returns {void}
* @public
*/
render = (html, el = this.selector) => {
document.querySelector(el).innerHTML = html;
};
/**
* @description confirms existence of the instance's selector in the DOM
* @returns {boolean}
* @public
*/
exists = () => !!document.querySelector(this.selector);
/**
* @description
* - iterates through the passed string and calls
* the passed listener on each character in the string
* - waits for the given time from the state's 'speed' property,
* before proceeding to the next iteration
*
* @param {string} string
* @param {string} listener - function to call on each character of string
* @returns {Promise<void>}
*
* @async
* @public
*/
handleIterateString = async (string, listener) => {
for (let i of string) {
listener(i);
await this.#delay(this.state.speed);
}
};
/**
* @description increments the queue count in the state by one
* @public
* @returns {void}
*/
incrementQueue = () => {
this.state.queueCount++;
this.#renderCountText();
};
/**
* decrements the queue count in the state by one
* @public
* @returns {void}
*/
decrementQueue = () => {
this.state.queueCount--;
this.#renderCountText();
};
}
// instantiate demo
const demo = new Typer('.demo');
async function SetText(
gg = `the puppy goes woof 🐶 woof woof woof...`,
cmd = `sudo --info`
) {
window.scrollTo({
top: 0,
behavior: 'smooth',
});
if (demo.exists()) {
const html = `<code class="shell-session demo hljs nginx"><span class="hljs-attribute">Website</span> <span class="hljs-regexp">~ $</span> ${cmd}`;
// render HTML container
demo.render(html);
// do typing
await demo.handleIterateString(gg, demo.append);
demo.decrementQueue();
if (demo.state.queueCount >= 0) {
SetText();
}
}
}
document.getElementById('type-btn').addEventListener('click', async () => {
if (demo.state.queueCount === -1) {
SetText();
}
demo.incrementQueue();
});
.form {
display: flex;
justify-content: space-between;
}
.select-box {
display: flex;
align-items: center;
}
label {
margin-right: 10px;
font-size: 0.8em;
}
.queueCount {
min-height: 40px;
}
.demo {
background: #000;
color: #fff;
min-height: 80px;
}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css">
<div class="container">
<div class="form mb-3">
<button class="button is-primary" id="type-btn">
<span>Click me</span>
</button>
<div class="select-box">
<label>speed per character (ms)</label>
<div class="select">
<select></select>
</div>
</div>
</div>
<div class="content is-normal mb-3">
<div class="queueCount is-size-5"></div>
</div>
<div class="content is-normal">
<div class="demo is-size-5"></div>
</div>
</div>
<!-- demo select menu -->
<script>
const select = document.querySelector('select');
Array.from({
length: 50,
},
(_, i) => (i + 1) * 30
).forEach((num) => {
select.innerHTML += `<option value="${num}">${num}</option>`;
});
select.addEventListener('change', (e) => {
demo.state.speed = parseInt(document.querySelector('select').value, 10);
});
</script>
Upvotes: 2
Reputation: 5704
The basic idea is to create a queue which is essentialy just an array where you push the text you want to display after you click. Then keep processing the queue until it's empty.
The code could look like this
function SetText(gg = `textttttt 😀`, cmd = `sudo --info`) {
window.scrollTo({ top: 0, behavior: 'smooth' });
if (document.getElementsByClassName('demo').length > 0) {
var i = 0;
var speed = 60;
document.getElementsByClassName('demo')[0].innerHTML = `<code class="shell-session demo hljs nginx"><span class="hljs-attribute">Website</span> <span class="hljs-regexp">~ $</span> ${cmd}`;
function typeItOut() {
if (i < gg.length) {
document.getElementsByClassName('demo')[0].innerHTML += gg.charAt(i);
i++;
setTimeout(typeItOut, speed);
} else {
processing = false; // add this
next(); // and this
}
}
setTimeout(typeItOut, 1800);
}
}
var processing = false;
var queue = [];
function click() {
queue.push([gg, cmd]); // get gg and cmd somehow
next();
};
function next() {
if (processing || !queue.length)
return;
processing = true;
var args = queue.shift(); // get first item from queue
SetText(args[0], args[1]);
};
I haven't tested it so it might not work but you should get the idea.
Upvotes: 0