Ghost type is a small Javascript utility used to display automated typing.
The best way to illustrate what it does is simply to show some examples.
Usage:
1
2
3
4
5
6
7
8
9
10
11
12
13
// initialize the class passing it a base id,
// an array of nodes, and an options object (optional)
const ghostType = new GhostType(
'base-id', // id of the element where type will show up
[
{
text: 'This is example text being typed',
typingDelay: 100
}
]
);
// call the instances start method
ghostType.start();
Key | Description | Type | Required | Default |
---|---|---|---|---|
anchorEl | The id of the element within which the typing will be displayed | String | Yes | none |
nodes | An array of nodes (node structure is explained below) | Array | Yes | none |
cursor | An object controling features of the cursor | Object | No | none |
cursor.hide | Specifies if cursor should be shown or not | Boolean | No | False |
cursor.color | Sets cursors color (accepts all values that work in css) | String | No | black |
options | An object controling various options | Object | No | none |
options.globalDelay | Time delay after each charcter is typed. Passing a [min,max] array makes the delay dynamic. | Int or [Int,Int] | No | 100 |
options.repeat | Object specifying whether to and how to repeat. | Object | No | none |
options.repeat.clear | Determines whether previous text should be cleared before repeating | Boolean | No | False |
options.repeat.count | Specifies the number of times the nodes should be repeated. If left undefined the it will repeat infinitely | Int | No | Infinity |
options.onComplete | A function that will be run after all of the nodes have completed. | Function | No | none |
Key | Description | Type | Required | Default |
---|---|---|---|---|
text | string of text to be typed | String | No | None |
html | string of html they will appear on screen all at once | String | No | None |
typingDelay | time delay after each charcter is typed. Passing a [min,max] array makes the delay dynamic. | Int or [Int,Int] | No | None |
pause | pause in microseconds before typing is started. | Int | No | None |
style | a string that uses css styled syntax | String | No | None |
backspace.delay | same as typingDelay | |||
backspace.pause | same as pause | |||
backspace.count | number of characters that get deleted after the nodes typing has completed. | Int | No | Defaults to the length of text that was typed. |
jumpBack | Number of nodes to move back to before continuing. Does not clear already typed content. | Int | No | none |
The whole utility is run by a single class, the code for which is below.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
class GhostType {
constructor({ anchorEl, nodes, options = {}, cursor: cursorSettings }) {
this.nodes = nodes;
this.index = 0;
this.options = options;
this.content = this.setUpContentSpan(anchorEl);
this.cursor = this.setUpCursorSpan(anchorEl, cursorSettings);
}
// creates the span where the text/html will be typed
setUpContentSpan = (anchorEl) => {
const content = document.createElement('span');
document.getElementById(anchorEl).appendChild(content);
return content;
}
// creates the span responsible for rendering the cursor
setUpCursorSpan = (anchorEl, cursorSettings = {}) => {
if (cursorSettings.hide)
return null;
// Add keyframes to document for cursor flicker animation
const cursorAnimation = document.createElement("style");
const FLICKER = `flicker {
0% { opacity: 1; }
50% { opacity: 1; }
75% { opacity: 0; }
100% { opacity: 1; }
}`;
cursorAnimation.textContent = `
@-webkit-keyframes ${FLICKER}
@-moz-keyframes ${FLICKER}
@-o-keyframes ${FLICKER}
@keyframes ${FLICKER}
`;
document.body.appendChild(cursorAnimation);
const cursor = document.createElement('span');
cursor.innerText = '.';
cursor.id = 'cursor';
cursor.style = `
display: inline-block;
background-color: ${cursorSettings.color || 'black'};
width: 2px;
line-height: 1em;
color: #ffffff00;
animation: 1s linear flicker infinite;
`;
document.getElementById(anchorEl).appendChild(cursor);
return cursor;
}
// used to add a span for each new node so that individual nodes
// can have custom styling
appendNewSpanToContent = (style) => {
const span = document.createElement('span');
span.style = style || '';
this.content.appendChild(span);
return span;
}
// Calculates a random delay duration between the provided [min, max]
calculateRandomDelay = ([ min, max ]) => Math.floor(Math.random() * (max - min)) + min;
// Finds delay based on node and global delay settings
determineDelayDuration = (delay) => {
// node has a delay set
if (delay)
return Array.isArray(delay) ? this.calculateRandomDelay(delay) : delay;
const { globalDelay } = this.options;
return Array.isArray(globalDelay) ? this.calculateRandomDelay(globalDelay) : globalDelay;
}
// types out the prompt for a given node with delays between
// each character based on specified values
typeTextNode = async (span, text, typingDelay) => {
if (typingDelay === 0)
return span.innerHTML += text;
return new Promise((resolve, reject) => {
let charPosition = 0;
const delay = () => {
setTimeout(() => {
span.innerHTML += text.charAt(charPosition++);
if (charPosition < text.length)
delay();
else
resolve();
}, this.determineDelayDuration(typingDelay));
}
delay();
});
}
// called before a nodes typing is started
pauseTyping = async (duration) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, duration);
});
}
// called after a nodes text has been typed out. Deletes specified number of characters
// with a specified delay between each deletion
backspaceTextNode = async (span, { count, delay }) => {
return new Promise((resolve, reject) => {
let charPosition = count || span.innerText.length;
const delayTyping = () => {
setTimeout(() => {
span.innerHTML = span.innerHTML.slice(0, -1);
if (--charPosition)
delayTyping();
else
resolve();
}, delay ? (Array.isArray(delay) ? this.determineDelayDuration([delay[0], delay[0]]) : delay) : 100);
}
delayTyping();
});
}
start = () => this.nextNode()
// runs the operation of the next specified node
nextNode = async () => {
const { text, html, clear, pause, backspace, typingDelay, jumpBack, style } = this.nodes[this.index];
if (pause)
await this.pauseTyping(pause);
if (clear)
this.content.innerHTML = '';
const span = this.appendNewSpanToContent(style);
if (style)
span.style = style
if (text)
await this.typeTextNode(span, text, typingDelay);
if (html)
span.innerHTML += html;
if (backspace) {
if (backspace.pause)
await this.pauseTyping(backspace.pause);
await this.backspaceTextNode(span, backspace);
}
if (jumpBack) {
this.index = this.index - jumpBack;
return this.nextNode();
}
if (this.nodes.length - 1 > this.index) {
this.index = this.index + 1;
return this.nextNode();
}
if (this.options.repeat && this.options.repeat.count !== 0) {
this.options.repeat.count--;
this.index = 0;
if (this.options.repeat.clear)
this.content.innerHTML = '';
this.nextNode();
}
if (this.options.onComplete) {
this.options.onComplete();
}
}
}