Typewriter animation, implemented using recursive asynchronous function [closed]
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
0
down vote
favorite
In this animation, I am wondering if it's a good idea using async/await with recursive calls and the effect may be caused to the javascript event loop.
Core function:
const wait = (ms) => new Promise(r => setTimeout(r, ms));
async function typeWriter(text, n, elementId, waitAfter)
const el = document.getElementById(elementId);
if (n < text.length)
requestAnimationFrame(() =>
el.innerHTML = (text.substring(0, n + 1));
n++;
await wait(waitAfter) //wait after letter stroke
await typeWriter(text, n, elementId, waitAfter) //recursion till text finish
Full code
https://github.com/Microsmsm/microsmsm.github.io/blob/master/js/typewriter.js
Result:
https://microsmsm.com/
javascript asynchronous animation async-await
closed as off-topic by t3chb0t, Toby Speight, Graipher, Sam Onela, Snowbody Jan 17 at 14:39
This question appears to be off-topic. The users who voted to close gave this specific reason:
- "Questions must involve real code that you own or maintain. Pseudocode, hypothetical code, or stub code should be replaced by a concrete implementation. Questions seeking an explanation of someone else's code are also off-topic." â Toby Speight, Graipher, Sam Onela
add a comment |Â
up vote
0
down vote
favorite
In this animation, I am wondering if it's a good idea using async/await with recursive calls and the effect may be caused to the javascript event loop.
Core function:
const wait = (ms) => new Promise(r => setTimeout(r, ms));
async function typeWriter(text, n, elementId, waitAfter)
const el = document.getElementById(elementId);
if (n < text.length)
requestAnimationFrame(() =>
el.innerHTML = (text.substring(0, n + 1));
n++;
await wait(waitAfter) //wait after letter stroke
await typeWriter(text, n, elementId, waitAfter) //recursion till text finish
Full code
https://github.com/Microsmsm/microsmsm.github.io/blob/master/js/typewriter.js
Result:
https://microsmsm.com/
javascript asynchronous animation async-await
closed as off-topic by t3chb0t, Toby Speight, Graipher, Sam Onela, Snowbody Jan 17 at 14:39
This question appears to be off-topic. The users who voted to close gave this specific reason:
- "Questions must involve real code that you own or maintain. Pseudocode, hypothetical code, or stub code should be replaced by a concrete implementation. Questions seeking an explanation of someone else's code are also off-topic." â Toby Speight, Graipher, Sam Onela
Please add an explanation of what the code is supposed to do. What is it trying to accomplish?
â Snowbody
Jan 17 at 14:39
@snowbody I attached the typewriter result link above
â Microsmsm
Jan 17 at 16:47
Yes, I can see that there is a web link, but in order for this qquestion to be considered you need to explain in the question itself what the code is supposed to do. e.g. "It's supposed to animate the characters appearing one at a time with a random delay".
â Snowbody
Jan 18 at 5:47
add a comment |Â
up vote
0
down vote
favorite
up vote
0
down vote
favorite
In this animation, I am wondering if it's a good idea using async/await with recursive calls and the effect may be caused to the javascript event loop.
Core function:
const wait = (ms) => new Promise(r => setTimeout(r, ms));
async function typeWriter(text, n, elementId, waitAfter)
const el = document.getElementById(elementId);
if (n < text.length)
requestAnimationFrame(() =>
el.innerHTML = (text.substring(0, n + 1));
n++;
await wait(waitAfter) //wait after letter stroke
await typeWriter(text, n, elementId, waitAfter) //recursion till text finish
Full code
https://github.com/Microsmsm/microsmsm.github.io/blob/master/js/typewriter.js
Result:
https://microsmsm.com/
javascript asynchronous animation async-await
In this animation, I am wondering if it's a good idea using async/await with recursive calls and the effect may be caused to the javascript event loop.
Core function:
const wait = (ms) => new Promise(r => setTimeout(r, ms));
async function typeWriter(text, n, elementId, waitAfter)
const el = document.getElementById(elementId);
if (n < text.length)
requestAnimationFrame(() =>
el.innerHTML = (text.substring(0, n + 1));
n++;
await wait(waitAfter) //wait after letter stroke
await typeWriter(text, n, elementId, waitAfter) //recursion till text finish
Full code
https://github.com/Microsmsm/microsmsm.github.io/blob/master/js/typewriter.js
Result:
https://microsmsm.com/
const wait = (ms) => new Promise(r => setTimeout(r, ms));
async function typeWriter(text, n, elementId, waitAfter)
const el = document.getElementById(elementId);
if (n < text.length)
requestAnimationFrame(() =>
el.innerHTML = (text.substring(0, n + 1));
n++;
await wait(waitAfter) //wait after letter stroke
await typeWriter(text, n, elementId, waitAfter) //recursion till text finish
const wait = (ms) => new Promise(r => setTimeout(r, ms));
async function typeWriter(text, n, elementId, waitAfter)
const el = document.getElementById(elementId);
if (n < text.length)
requestAnimationFrame(() =>
el.innerHTML = (text.substring(0, n + 1));
n++;
await wait(waitAfter) //wait after letter stroke
await typeWriter(text, n, elementId, waitAfter) //recursion till text finish
javascript asynchronous animation async-await
edited Jan 17 at 12:46
200_success
123k14143401
123k14143401
asked Jan 17 at 10:39
Microsmsm
1787
1787
closed as off-topic by t3chb0t, Toby Speight, Graipher, Sam Onela, Snowbody Jan 17 at 14:39
This question appears to be off-topic. The users who voted to close gave this specific reason:
- "Questions must involve real code that you own or maintain. Pseudocode, hypothetical code, or stub code should be replaced by a concrete implementation. Questions seeking an explanation of someone else's code are also off-topic." â Toby Speight, Graipher, Sam Onela
closed as off-topic by t3chb0t, Toby Speight, Graipher, Sam Onela, Snowbody Jan 17 at 14:39
This question appears to be off-topic. The users who voted to close gave this specific reason:
- "Questions must involve real code that you own or maintain. Pseudocode, hypothetical code, or stub code should be replaced by a concrete implementation. Questions seeking an explanation of someone else's code are also off-topic." â Toby Speight, Graipher, Sam Onela
Please add an explanation of what the code is supposed to do. What is it trying to accomplish?
â Snowbody
Jan 17 at 14:39
@snowbody I attached the typewriter result link above
â Microsmsm
Jan 17 at 16:47
Yes, I can see that there is a web link, but in order for this qquestion to be considered you need to explain in the question itself what the code is supposed to do. e.g. "It's supposed to animate the characters appearing one at a time with a random delay".
â Snowbody
Jan 18 at 5:47
add a comment |Â
Please add an explanation of what the code is supposed to do. What is it trying to accomplish?
â Snowbody
Jan 17 at 14:39
@snowbody I attached the typewriter result link above
â Microsmsm
Jan 17 at 16:47
Yes, I can see that there is a web link, but in order for this qquestion to be considered you need to explain in the question itself what the code is supposed to do. e.g. "It's supposed to animate the characters appearing one at a time with a random delay".
â Snowbody
Jan 18 at 5:47
Please add an explanation of what the code is supposed to do. What is it trying to accomplish?
â Snowbody
Jan 17 at 14:39
Please add an explanation of what the code is supposed to do. What is it trying to accomplish?
â Snowbody
Jan 17 at 14:39
@snowbody I attached the typewriter result link above
â Microsmsm
Jan 17 at 16:47
@snowbody I attached the typewriter result link above
â Microsmsm
Jan 17 at 16:47
Yes, I can see that there is a web link, but in order for this qquestion to be considered you need to explain in the question itself what the code is supposed to do. e.g. "It's supposed to animate the characters appearing one at a time with a random delay".
â Snowbody
Jan 18 at 5:47
Yes, I can see that there is a web link, but in order for this qquestion to be considered you need to explain in the question itself what the code is supposed to do. e.g. "It's supposed to animate the characters appearing one at a time with a random delay".
â Snowbody
Jan 18 at 5:47
add a comment |Â
1 Answer
1
active
oldest
votes
up vote
2
down vote
accepted
Recursion is a state stack.
Function context
Every time a function is called JS creates a new context (function state), even if the function does nothing, creates no variables, has no arguments, only calls another function, it still requires its own context. The process of creating and pushing to the heap costs memory and processing time.
The minimum memory cost varies between JS engines, but 1K is a reasonable estimate for an empty context.
Note The exception is Proper tail call Functions that by nature of the return, may not require their own context, they use the calling functions context. (No browser current lets you use this ES6 required standard feature)
Recursive state stack
In general recursion is used as a way create a state stack. Each iteration (recursive) call creates a new function context, with closure that is pushed to the heap.
When the a function exits its state is deleted popped from the heap and the calling function's state is reinstated from the heap.
Example showing states pushed and popped
The following illustrates the state stack. The state includes a random value. Each iteration waits 200ms before creating the next. When the recursion exits each state is popped from the heap, the functions complete execution until the stack is clear.
function resolveAfter200ms(x)
return new Promise(resolve =>
setTimeout(() => resolve() , 200);
);
;
async function test(count)
const rand = Math.random() * 100
function log(data)
document.body.innerHTML += `<span>$data</span>`;
test(0);
Why this makes your function bad.
Saving state to a stack is great when you have complex or branching data structures that you need to iterate.
Saving an irreverent state on the other hand is not so great. In fact I would say using an aysnc function to step over animation frames via recursion is about the worst way to create an animation.
If you have 200 characters to animate by the time they have all completed (the last recursive call) you have 200 function states on the heap.
I would estimate that the memory usage of a 200 character animation to be about 200K +, though that is nothing, I have seen people chew 1Meg just to add two integers in JavaScript (crazy eh!).
Luckily JavaScript makes such wasteful resource usage transparent, by providing ample memory and a gaggle of resource management threads to contain and clean up so from your point of view everything is running slick..
Another way.
I am a little long in the tooth and come from a time where 1K was all we had. I can not write code without a device angel whispering thoughts of "conserve, speed and memory"
So I would have written your code as follows if the requirement was to use async functions, and assuming that the waitAfter
value is many times greater than 60fps.
// Note dont need text position (n)
async function typeWriter(text, elementId, waitAfter)
var n = 0;
// Following DOM query only done once saving lots of time
const el = document.getElementById(elementId);
const wait = () => new Promise(r => setTimeout(r, waitAfter));
// Preventing re flow overhead by using textContent rather than innerHTML
const render = () => el.textContent = (text.substring(0, n + 1));
while (n < text.length)
requestAnimationFrame(render); // Calls existing function
// thus avoid unneeded function state capture
// via closure
await wait();
n++; // add after await so render gets
// the correct value
From an external view its behaviour is identical, from a resource point of view its is remarkably different, will much lower memory use and associated GC overhead upon exit. It can also handle any size text, unlike the recursive method was limited to the available call stack size.
That was super helpful to me, thanks too much... unfortunately we as well as many -JavaScript developers- have too many resources to waste... 1KB should have landed a rocket the moon while now 1GB couldn't literally launch an Angular app smoothly!
â Microsmsm
Jan 17 at 19:35
add a comment |Â
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
accepted
Recursion is a state stack.
Function context
Every time a function is called JS creates a new context (function state), even if the function does nothing, creates no variables, has no arguments, only calls another function, it still requires its own context. The process of creating and pushing to the heap costs memory and processing time.
The minimum memory cost varies between JS engines, but 1K is a reasonable estimate for an empty context.
Note The exception is Proper tail call Functions that by nature of the return, may not require their own context, they use the calling functions context. (No browser current lets you use this ES6 required standard feature)
Recursive state stack
In general recursion is used as a way create a state stack. Each iteration (recursive) call creates a new function context, with closure that is pushed to the heap.
When the a function exits its state is deleted popped from the heap and the calling function's state is reinstated from the heap.
Example showing states pushed and popped
The following illustrates the state stack. The state includes a random value. Each iteration waits 200ms before creating the next. When the recursion exits each state is popped from the heap, the functions complete execution until the stack is clear.
function resolveAfter200ms(x)
return new Promise(resolve =>
setTimeout(() => resolve() , 200);
);
;
async function test(count)
const rand = Math.random() * 100
function log(data)
document.body.innerHTML += `<span>$data</span>`;
test(0);
Why this makes your function bad.
Saving state to a stack is great when you have complex or branching data structures that you need to iterate.
Saving an irreverent state on the other hand is not so great. In fact I would say using an aysnc function to step over animation frames via recursion is about the worst way to create an animation.
If you have 200 characters to animate by the time they have all completed (the last recursive call) you have 200 function states on the heap.
I would estimate that the memory usage of a 200 character animation to be about 200K +, though that is nothing, I have seen people chew 1Meg just to add two integers in JavaScript (crazy eh!).
Luckily JavaScript makes such wasteful resource usage transparent, by providing ample memory and a gaggle of resource management threads to contain and clean up so from your point of view everything is running slick..
Another way.
I am a little long in the tooth and come from a time where 1K was all we had. I can not write code without a device angel whispering thoughts of "conserve, speed and memory"
So I would have written your code as follows if the requirement was to use async functions, and assuming that the waitAfter
value is many times greater than 60fps.
// Note dont need text position (n)
async function typeWriter(text, elementId, waitAfter)
var n = 0;
// Following DOM query only done once saving lots of time
const el = document.getElementById(elementId);
const wait = () => new Promise(r => setTimeout(r, waitAfter));
// Preventing re flow overhead by using textContent rather than innerHTML
const render = () => el.textContent = (text.substring(0, n + 1));
while (n < text.length)
requestAnimationFrame(render); // Calls existing function
// thus avoid unneeded function state capture
// via closure
await wait();
n++; // add after await so render gets
// the correct value
From an external view its behaviour is identical, from a resource point of view its is remarkably different, will much lower memory use and associated GC overhead upon exit. It can also handle any size text, unlike the recursive method was limited to the available call stack size.
That was super helpful to me, thanks too much... unfortunately we as well as many -JavaScript developers- have too many resources to waste... 1KB should have landed a rocket the moon while now 1GB couldn't literally launch an Angular app smoothly!
â Microsmsm
Jan 17 at 19:35
add a comment |Â
up vote
2
down vote
accepted
Recursion is a state stack.
Function context
Every time a function is called JS creates a new context (function state), even if the function does nothing, creates no variables, has no arguments, only calls another function, it still requires its own context. The process of creating and pushing to the heap costs memory and processing time.
The minimum memory cost varies between JS engines, but 1K is a reasonable estimate for an empty context.
Note The exception is Proper tail call Functions that by nature of the return, may not require their own context, they use the calling functions context. (No browser current lets you use this ES6 required standard feature)
Recursive state stack
In general recursion is used as a way create a state stack. Each iteration (recursive) call creates a new function context, with closure that is pushed to the heap.
When the a function exits its state is deleted popped from the heap and the calling function's state is reinstated from the heap.
Example showing states pushed and popped
The following illustrates the state stack. The state includes a random value. Each iteration waits 200ms before creating the next. When the recursion exits each state is popped from the heap, the functions complete execution until the stack is clear.
function resolveAfter200ms(x)
return new Promise(resolve =>
setTimeout(() => resolve() , 200);
);
;
async function test(count)
const rand = Math.random() * 100
function log(data)
document.body.innerHTML += `<span>$data</span>`;
test(0);
Why this makes your function bad.
Saving state to a stack is great when you have complex or branching data structures that you need to iterate.
Saving an irreverent state on the other hand is not so great. In fact I would say using an aysnc function to step over animation frames via recursion is about the worst way to create an animation.
If you have 200 characters to animate by the time they have all completed (the last recursive call) you have 200 function states on the heap.
I would estimate that the memory usage of a 200 character animation to be about 200K +, though that is nothing, I have seen people chew 1Meg just to add two integers in JavaScript (crazy eh!).
Luckily JavaScript makes such wasteful resource usage transparent, by providing ample memory and a gaggle of resource management threads to contain and clean up so from your point of view everything is running slick..
Another way.
I am a little long in the tooth and come from a time where 1K was all we had. I can not write code without a device angel whispering thoughts of "conserve, speed and memory"
So I would have written your code as follows if the requirement was to use async functions, and assuming that the waitAfter
value is many times greater than 60fps.
// Note dont need text position (n)
async function typeWriter(text, elementId, waitAfter)
var n = 0;
// Following DOM query only done once saving lots of time
const el = document.getElementById(elementId);
const wait = () => new Promise(r => setTimeout(r, waitAfter));
// Preventing re flow overhead by using textContent rather than innerHTML
const render = () => el.textContent = (text.substring(0, n + 1));
while (n < text.length)
requestAnimationFrame(render); // Calls existing function
// thus avoid unneeded function state capture
// via closure
await wait();
n++; // add after await so render gets
// the correct value
From an external view its behaviour is identical, from a resource point of view its is remarkably different, will much lower memory use and associated GC overhead upon exit. It can also handle any size text, unlike the recursive method was limited to the available call stack size.
That was super helpful to me, thanks too much... unfortunately we as well as many -JavaScript developers- have too many resources to waste... 1KB should have landed a rocket the moon while now 1GB couldn't literally launch an Angular app smoothly!
â Microsmsm
Jan 17 at 19:35
add a comment |Â
up vote
2
down vote
accepted
up vote
2
down vote
accepted
Recursion is a state stack.
Function context
Every time a function is called JS creates a new context (function state), even if the function does nothing, creates no variables, has no arguments, only calls another function, it still requires its own context. The process of creating and pushing to the heap costs memory and processing time.
The minimum memory cost varies between JS engines, but 1K is a reasonable estimate for an empty context.
Note The exception is Proper tail call Functions that by nature of the return, may not require their own context, they use the calling functions context. (No browser current lets you use this ES6 required standard feature)
Recursive state stack
In general recursion is used as a way create a state stack. Each iteration (recursive) call creates a new function context, with closure that is pushed to the heap.
When the a function exits its state is deleted popped from the heap and the calling function's state is reinstated from the heap.
Example showing states pushed and popped
The following illustrates the state stack. The state includes a random value. Each iteration waits 200ms before creating the next. When the recursion exits each state is popped from the heap, the functions complete execution until the stack is clear.
function resolveAfter200ms(x)
return new Promise(resolve =>
setTimeout(() => resolve() , 200);
);
;
async function test(count)
const rand = Math.random() * 100
function log(data)
document.body.innerHTML += `<span>$data</span>`;
test(0);
Why this makes your function bad.
Saving state to a stack is great when you have complex or branching data structures that you need to iterate.
Saving an irreverent state on the other hand is not so great. In fact I would say using an aysnc function to step over animation frames via recursion is about the worst way to create an animation.
If you have 200 characters to animate by the time they have all completed (the last recursive call) you have 200 function states on the heap.
I would estimate that the memory usage of a 200 character animation to be about 200K +, though that is nothing, I have seen people chew 1Meg just to add two integers in JavaScript (crazy eh!).
Luckily JavaScript makes such wasteful resource usage transparent, by providing ample memory and a gaggle of resource management threads to contain and clean up so from your point of view everything is running slick..
Another way.
I am a little long in the tooth and come from a time where 1K was all we had. I can not write code without a device angel whispering thoughts of "conserve, speed and memory"
So I would have written your code as follows if the requirement was to use async functions, and assuming that the waitAfter
value is many times greater than 60fps.
// Note dont need text position (n)
async function typeWriter(text, elementId, waitAfter)
var n = 0;
// Following DOM query only done once saving lots of time
const el = document.getElementById(elementId);
const wait = () => new Promise(r => setTimeout(r, waitAfter));
// Preventing re flow overhead by using textContent rather than innerHTML
const render = () => el.textContent = (text.substring(0, n + 1));
while (n < text.length)
requestAnimationFrame(render); // Calls existing function
// thus avoid unneeded function state capture
// via closure
await wait();
n++; // add after await so render gets
// the correct value
From an external view its behaviour is identical, from a resource point of view its is remarkably different, will much lower memory use and associated GC overhead upon exit. It can also handle any size text, unlike the recursive method was limited to the available call stack size.
Recursion is a state stack.
Function context
Every time a function is called JS creates a new context (function state), even if the function does nothing, creates no variables, has no arguments, only calls another function, it still requires its own context. The process of creating and pushing to the heap costs memory and processing time.
The minimum memory cost varies between JS engines, but 1K is a reasonable estimate for an empty context.
Note The exception is Proper tail call Functions that by nature of the return, may not require their own context, they use the calling functions context. (No browser current lets you use this ES6 required standard feature)
Recursive state stack
In general recursion is used as a way create a state stack. Each iteration (recursive) call creates a new function context, with closure that is pushed to the heap.
When the a function exits its state is deleted popped from the heap and the calling function's state is reinstated from the heap.
Example showing states pushed and popped
The following illustrates the state stack. The state includes a random value. Each iteration waits 200ms before creating the next. When the recursion exits each state is popped from the heap, the functions complete execution until the stack is clear.
function resolveAfter200ms(x)
return new Promise(resolve =>
setTimeout(() => resolve() , 200);
);
;
async function test(count)
const rand = Math.random() * 100
function log(data)
document.body.innerHTML += `<span>$data</span>`;
test(0);
Why this makes your function bad.
Saving state to a stack is great when you have complex or branching data structures that you need to iterate.
Saving an irreverent state on the other hand is not so great. In fact I would say using an aysnc function to step over animation frames via recursion is about the worst way to create an animation.
If you have 200 characters to animate by the time they have all completed (the last recursive call) you have 200 function states on the heap.
I would estimate that the memory usage of a 200 character animation to be about 200K +, though that is nothing, I have seen people chew 1Meg just to add two integers in JavaScript (crazy eh!).
Luckily JavaScript makes such wasteful resource usage transparent, by providing ample memory and a gaggle of resource management threads to contain and clean up so from your point of view everything is running slick..
Another way.
I am a little long in the tooth and come from a time where 1K was all we had. I can not write code without a device angel whispering thoughts of "conserve, speed and memory"
So I would have written your code as follows if the requirement was to use async functions, and assuming that the waitAfter
value is many times greater than 60fps.
// Note dont need text position (n)
async function typeWriter(text, elementId, waitAfter)
var n = 0;
// Following DOM query only done once saving lots of time
const el = document.getElementById(elementId);
const wait = () => new Promise(r => setTimeout(r, waitAfter));
// Preventing re flow overhead by using textContent rather than innerHTML
const render = () => el.textContent = (text.substring(0, n + 1));
while (n < text.length)
requestAnimationFrame(render); // Calls existing function
// thus avoid unneeded function state capture
// via closure
await wait();
n++; // add after await so render gets
// the correct value
From an external view its behaviour is identical, from a resource point of view its is remarkably different, will much lower memory use and associated GC overhead upon exit. It can also handle any size text, unlike the recursive method was limited to the available call stack size.
function resolveAfter200ms(x)
return new Promise(resolve =>
setTimeout(() => resolve() , 200);
);
;
async function test(count)
const rand = Math.random() * 100
function log(data)
document.body.innerHTML += `<span>$data</span>`;
test(0);
function resolveAfter200ms(x)
return new Promise(resolve =>
setTimeout(() => resolve() , 200);
);
;
async function test(count)
const rand = Math.random() * 100
function log(data)
document.body.innerHTML += `<span>$data</span>`;
test(0);
answered Jan 17 at 16:04
Blindman67
5,4001320
5,4001320
That was super helpful to me, thanks too much... unfortunately we as well as many -JavaScript developers- have too many resources to waste... 1KB should have landed a rocket the moon while now 1GB couldn't literally launch an Angular app smoothly!
â Microsmsm
Jan 17 at 19:35
add a comment |Â
That was super helpful to me, thanks too much... unfortunately we as well as many -JavaScript developers- have too many resources to waste... 1KB should have landed a rocket the moon while now 1GB couldn't literally launch an Angular app smoothly!
â Microsmsm
Jan 17 at 19:35
That was super helpful to me, thanks too much... unfortunately we as well as many -JavaScript developers- have too many resources to waste... 1KB should have landed a rocket the moon while now 1GB couldn't literally launch an Angular app smoothly!
â Microsmsm
Jan 17 at 19:35
That was super helpful to me, thanks too much... unfortunately we as well as many -JavaScript developers- have too many resources to waste... 1KB should have landed a rocket the moon while now 1GB couldn't literally launch an Angular app smoothly!
â Microsmsm
Jan 17 at 19:35
add a comment |Â
Please add an explanation of what the code is supposed to do. What is it trying to accomplish?
â Snowbody
Jan 17 at 14:39
@snowbody I attached the typewriter result link above
â Microsmsm
Jan 17 at 16:47
Yes, I can see that there is a web link, but in order for this qquestion to be considered you need to explain in the question itself what the code is supposed to do. e.g. "It's supposed to animate the characters appearing one at a time with a random delay".
â Snowbody
Jan 18 at 5:47