打字机实现
小祁同学 2022-10-10
技术文章
vue
介绍
关于首页打字机效果是如何实现的
# 1、使用技术
- CSS3=>animation (核心)
- 关于 animation 可以看看大佬的这篇文章CSDN 超详细 CSS 动画-animation-CSDN (opens new window)
- MDN animation的使用 (opens new window)
- Vue(本网站使用vuepress搭建,即主页打字机功能)
# 2、使用技术
- CSS3=>animation (核心)
你如果不想看文字,那么你可以去该链接观看教程
bilibili:【CSS】打字特效 (opens new window)
# 3、在vuepress系统中应用
你需要知道的是在 VuePress 中,所有在 .vuepress/components 中找到的 *.vue 文件将会自动地被注册为全局的异步组件,如:
.
└─ .vuepress
└─ components
├─ demo-1.vue
├─ OtherComponent.vue
└─ Foo
└─ Bar.vue
1
2
3
4
5
6
7
2
3
4
5
6
7
你可以直接使用这些组件在任意的 Markdown 文件中(组件名是通过文件名提取到的):
<demo-1/>
<OtherComponent/>
<Foo-Bar/>
1
2
3
4
2
3
4
于是我们在 components 目录下新建一个 NewFont.vue
文件,并填入以下代码:
<template>
<div></div>
</template>
<script>
export default {
name: '',
data() {
return {
strs: [
{
title: '以梦为马,不负韶华,流年笑掷,未来可期,不负初心,方得始终。',
// 停顿的位置
// stop: 5,
stop: [5,10,15,20,25,30]
// stop: [4, 13] // 可以是数组,多几个位置停顿
},
{
title: '北方有佳人,绝世而独立。一顾倾人城,再顾情人过。宁不知倾城与倾国,佳人在难得。',
stop: [6,12,18,24,33,39]
},
{
title: '远赴人间惊鸿宴,一睹人间盛世颜。',
stop: [8,16]
},
{
title: '古之立大事者,不惟有超世之才,亦必有坚忍不拔之志。',
stop: [7,15,25]
},
{
title: '往日暗沉不可追,来日之路光明灿烂。',
stop: [8,,17]
},
{
title: '少年遥想得山田,请赶在日落下山前 ,日落下山得山田,归来仍是那少年。',
stop: [8,,16,24,32]
}
],
// 当前进行到第几行
currentIndex: 0,
spans: null,
el: null,
titleEl: null,
}
},
mounted() {
if (!this.el) {
const timer = setInterval(() => {
this.el = document.querySelector('.hero .description')
this.titleEl = document.querySelector('.hero h1')
if (this.el) {
this.titleEl.style.fontWeight = 'bold'
timer && clearInterval(timer)
this.init()
}
}, 100)
} else {
this.init()
}
},
methods: {
init() {
if (this.currentIndex == this.strs.length) {
this.currentIndex = 0
}
const current = this.strs[this.currentIndex]
this.el.innerHTML = current instanceof Object ? current.title : current
this.el.innerHTML = this.el.textContent.replace(/\S/g, '<span>$&</span>')
let delay = 0
this.spans = [...document.querySelectorAll('.hero .description span')]
this.spans.forEach((span, i) => {
delay += 0.1
if (current.hasOwnProperty('stop')) {
if (current.stop instanceof Array) {
if (current.stop.includes(i)) {
delay += 0.3
}
} else {
if (current.stop == i) {
delay += 0.3
}
}
}
span.style.setProperty('--delay', `${delay}s`)
})
this.el.addEventListener('animationend', this.lastEnd)
},
lastEnd(e) {
if (e.target == document.querySelector('.hero .description span:last-child')) {
this.el.classList.add('ended')
setTimeout(() => {
this.el.removeEventListener('animationend', this.lastEnd)
let delay = 0
this.spans.reverse().forEach((span, i) => {
this.el.classList.remove('ended')
span.style.width = '2ch'
span.style.animation = '0.1s text-out ease-in-out forwards'
delay += 0.05
// 回去停顿功能
// if (this.strs[this.currentIndex].hasOwnProperty("stop")) {
// if (this.strs[this.currentIndex].stop instanceof Array) {
// if (
// this.strs[this.currentIndex].stop.includes(
// this.spans.length - i
// )
// ) {
// delay += 0.3;
// }
// } else {
// if (
// this.strs[this.currentIndex].stop ==
// this.spans.length - i
// ) {
// delay += 0.3;
// }
// }
// }
span.style.animationDelay = `${delay}s`
})
this.el.addEventListener('animationend', this.firstEnd)
}, 1500)
}
},
firstEnd(e) {
if (e.target == document.querySelector('.hero .description span:first-child')) {
this.spans.forEach(v => {
v.remove()
})
this.el.removeEventListener('animationend', this.firstEnd)
this.currentIndex++
this.init()
}
},
},
}
</script>
<style>
.hero .description {
margin: 0;
padding: 0;
/* 必须是等宽字体 */
/* 由于是等宽字体,1ch 等于 任何数字、英文、符号的宽度 */
font-family: monospace;
position: relative;
width: fit-content;
}
.hero .description::after {
content: '';
display: inline;
position: absolute;
width: 2px;
height: 2ch;
top: 9%;
background-color: #000;
border-radius: 2px;
right: -0.5ch;
}
.hero .description.ended::after {
animation: 1.1s cursor steps(2, jump-none) infinite;
}
.home-blog .hero .description span {
--delay: 10s;
display: inline-block;
overflow: hidden;
width: 0ch;
animation: 0.1s text-in ease-in-out forwards;
animation-delay: var(--delay);
font-weight: 600;
}
@keyframes text-in {
from {
width: 0ch;
}
to {
/* 必须是等宽字体 */
/* 由于是等宽字体,1ch 等于 任何数字、英文、符号的宽度 */
/* 中文2ch,英文1ch */
width: 2ch;
}
}
@keyframes text-out {
from {
/* 中文2ch,英文1ch */
width: 2ch;
}
to {
width: 0ch;
}
}
@keyframes cursor {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
接下来在你的首页文件 README.md 文件中应用即可。
<NewFont />
1
我的目录结构如下:
至此vuepress上的打印机介绍完毕
# 4、普通html效果实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>打字机效果在线演示</title>
<style>
:root {
font-size: 20px;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
h1 {
margin: 0;
padding: 0;
/* 必须是等宽字体 */
font-family: monospace;
/* 由于是等宽字体,1ch 等于 任何数字、英文、符号的宽度 */
/* width: 1ch; */
position: relative;
/* overflow: hidden; */
/* animation: 1s typing forwards steps(13); */
}
h1::after {
content: "";
display: inline;
position: absolute;
width: 5px;
height: 2ch;
background-color: #000;
border-radius: 2px;
right: -0.5ch;
}
h1.ended::after {
animation: 1.1s cursor steps(2, jump-none) infinite;
}
h1 span {
--delay: 10s;
display: inline-block;
overflow: hidden;
width: 0ch;
animation: 0.1s text-in ease-in-out forwards;
/* animation: 0.1s text-out ease-in-out forwards; */
animation-delay: var(--delay);
}
@keyframes text-in {
from {
width: 0ch;
}
to {
width: 2ch;
}
}
@keyframes text-out {
from {
width: 2ch;
}
to {
width: 0ch;
}
}
@keyframes cursor {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>
</head>
<body>
<script>
let strs = [
{
title: '你才二十多岁,没遇到喜欢的人很正常,往后你会发现,大概是遇不到了。',
},
{
title: '两情相悦如今已经不多了,尤其在物欲横流的今天这已是奢望。但我仍然带着我的热情跟向往在等待那个风雨夜归的人。',
},
{
title: '现在的爱情不归月老管了, 他老人家早退休了, 现在归财神管。',
},
{
title: '可惜我啊,相貌平平,一事无成,惊艳不了谁的岁月,温暖不了谁的人生。',
},
{
title: '我本以为早踏入社会是一件多么美好的事,但是现在才知道社会多么的残酷无情。',
},
{
title: '卦不敢算尽,恐天道无常。情不敢至深,恐大梦一场。',
},
{
title: '你羡慕的生活背后都是你熬不起的苦。',
},
{
title: '小时候微笑是一种心情,长大后微笑是一种表情。',
},
{
title: '条条大路通罗马,可有的人出生在罗马。',
},
{
title: '小时候很穷但是我很快乐,可是长大后我虽然总笑,可是我心里却空了。',
},
];
// 当前进行到第几行
let currentIndex = 0;
let h1 = null;
let spans = null;
setTimeout(() => {
h1 = document.createElement("h1");
document.body.appendChild(h1);
init();
}, 2000);
function init() {
if (currentIndex == strs.length) {
currentIndex = 0;
}
h1.innerHTML = strs[currentIndex].title;
h1.innerHTML = h1.textContent.replace(/\S/g, "<span>$&</span>");
let delay = 0;
spans = [...document.querySelectorAll("span")];
spans.forEach((span, i) => {
delay += 0.1;
if (strs[currentIndex].stop instanceof Array) {
if (strs[currentIndex].stop.includes(i)) {
delay += 0.3;
}
} else {
if (strs[currentIndex].stop == i) {
delay += 0.3;
}
}
span.style.setProperty("--delay", `${delay}s`);
});
h1.addEventListener("animationend", lastEnd);
}
function lastEnd(e) {
if (e.target == document.querySelector("h1 span:last-child")) {
h1.classList.add("ended");
setTimeout(() => {
h1.removeEventListener("animationend", lastEnd);
let delay = 0;
spans.reverse().forEach((span, i) => {
h1.classList.remove("ended");
span.style.width = "2ch";
span.style.animation = "0.1s text-out ease-in-out forwards";
delay += 0.05;
// 回去停顿功能
// if (strs[currentIndex].stop instanceof Array) {
// if (strs[currentIndex].stop.includes(spans.length - i)) {
// delay += 0.3
// }
// } else {
// if (strs[currentIndex].stop == spans.length - i) {
// delay += 0.3
// }
// }
span.style.animationDelay = `${delay}s`;
});
h1.addEventListener("animationend", firstEnd);
}, 2000);
}
}
function firstEnd(e) {
if (e.target == document.querySelector("h1 span:first-child")) {
spans.forEach((item) => {
item.remove();
});
h1.removeEventListener("animationend", firstEnd);
currentIndex++;
// h1.classList.add('ended')
// h1.classList.remove('ended')
init();
}
}
</script>
</body>
</html>
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205