generated from github/custom-element-boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathindex.ts
113 lines (97 loc) · 3.01 KB
/
index.ts
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
const loaded: Promise<unknown> = (function () {
if (document.readyState === 'complete') {
return Promise.resolve()
} else {
return new Promise(resolve => {
window.addEventListener('load', resolve)
})
}
})()
class TypingEffectElement extends HTMLElement {
async connectedCallback(): Promise<void> {
await loaded
if (this.content) await typeLines(this.lines, this.content, this.characterDelay, this.lineDelay)
if (this.cursor) this.cursor.hidden = true
this.dispatchEvent(
new CustomEvent('typing:complete', {
bubbles: true,
cancelable: true
})
)
}
get content(): HTMLElement | null {
return this.querySelector('[data-target="typing-effect.content"]')
}
get cursor(): HTMLElement | null {
return this.querySelector('[data-target="typing-effect.cursor"]')
}
get lines(): string[] {
const linesAttr = this.getAttribute('data-lines')
try {
return linesAttr ? (JSON.parse(linesAttr) as string[]) : []
} catch {
return []
}
}
get prefersReducedMotion(): boolean {
return window.matchMedia('(prefers-reduced-motion)').matches
}
get characterDelay(): number {
if (this.prefersReducedMotion) {
return 0
}
return Math.max(0, Math.min(Math.floor(Number(this.getAttribute('data-character-delay'))), 2_147_483_647)) || 40
}
set characterDelay(value: number) {
if (value > 2_147_483_647 || value < 0) {
throw new DOMException('Value is negative or greater than the allowed amount')
}
this.setAttribute('data-character-delay', String(value))
}
get lineDelay(): number {
if (this.prefersReducedMotion) {
return 0
}
return Math.max(0, Math.min(Math.floor(Number(this.getAttribute('data-line-delay'))), 2_147_483_647)) || 40
}
set lineDelay(value: number) {
if (value > 2_147_483_647 || value < 0) {
throw new DOMException('Value is negative or greater than the allowed amount')
}
this.setAttribute('data-line-delay', String(value))
}
}
declare global {
interface Window {
TypingEffectElement: typeof TypingEffectElement
}
}
export default TypingEffectElement
if (!window.customElements.get('typing-effect')) {
window.TypingEffectElement = TypingEffectElement
window.customElements.define('typing-effect', TypingEffectElement)
}
async function typeLines(
lines: string[],
contentElement: HTMLElement,
characterDelay: number,
lineDelay: number
): Promise<void> {
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
if (characterDelay === 0) {
contentElement.append(lines[lineIndex])
} else {
for (const character of lines[lineIndex].split('')) {
await wait(characterDelay)
contentElement.innerHTML += character
}
}
if (lineDelay !== 0) await wait(lineDelay)
if (lineIndex < lines.length - 1) contentElement.append(document.createElement('br'))
}
}
async function wait(ms: number): Promise<void> {
return new Promise<void>(resolve => {
setTimeout(resolve, ms)
})
}