Keep 主题 | 实现首页“打字机”效果的自定义
He Junze

在基于 Hexo 框架的 Keep 主题 下,暂时无法调整 first-screen 首页正中央的description(或hitokoto)的打字机效果,然而原本的打字机效果并不理想:字符输出速度慢,输出的速度没有起伏变化,光标距离文字较远。这些问题使得字符输出效果不够真实、自然。

在长时间翻阅 Keep 主题的源代码后,得到下文中所提供的解决办法。该办法可自定义打字机的如下效果:

  1. 光标样式

  2. 输出速度

  3. 是否始终显示光标闪烁

  4. 启用 / 关闭打字机效果

自定义光标样式

node_modules\hexo-theme-keep\layout\_partial\first-screen.ejs 路径下,找到如下代码行:

1
2
3
4
5
6
7
8
9
<% if (fs_hitokoto === true) {%>
<div class="desc-item border-box"><span class="desc hitokoto"></span><span class="cursor"></span></div>
<% } else {%>
<% for (const idx in final_description) {%>
<% if (final_description[idx]) {%>
<div class="desc-item border-box"><span class="desc"><%= final_description[idx] %></span><span class="cursor"></span></div>
<% } %>
<% } %>
<% } %>

此处实现了光标的显示,<span class="cursor"> 丨 </span>中的 便是要显示的光标。直接将 改为你想要的符号,以实现光标的自定义。

注意!这段代码中,共有两处 <span class="cursor"> 丨 </span> 可以修改。第一处是 hitokoto 的光标,第二处是自定义 description 的光标。

笔者在此处将“丨”改为英文符号“|”,使得光标紧贴上一个字母,呈现 test| 的效果(原本为test 丨),与真实光标更加相似(缺点是光标比原来粗,观感稍有逊色)。

自定义输出速度

自定义线性速度

node_modules\hexo-theme-keep\source\js\page\home-page.js 路径下,找到 initTypewriter 函数:

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
const initTypewriter = () => {
const isHitokoto = fsc?.hitokoto === true

if (fsc?.enable !== true) {
return
}

if (fsc?.enable === true && !isHitokoto && !fsc?.description) {
return
}


const descBox = document.querySelector('.first-screen-content .description')
if (descBox) {
descBox.style.opacity = '0'

setTimeout(
() => {
descBox.style.opacity = '1'
const descItemList = descBox.querySelectorAll('.desc-item')
descItemList.forEach((descItem) => {
const desc = descItem.querySelector('.desc')
const cursor = descItem.querySelector('.cursor')
const text = desc.innerHTML
desc.innerHTML = ''
let charIndex = 0

if (text) {
const typewriter = () => {
if (charIndex < text.length) {
desc.textContent += text.charAt(charIndex)
charIndex++
setTimeout(typewriter, 100)
} else {
cursor.style.display = 'none'
}
}
typewriter()
}
})
},
isHitokoto ? 400 : 300
)
}

}

这段代码实现了打字机效果,调整其中

1
setTimeout(typewriter, 100)

这行代码中的数字大小即可调整输出速度。

自定义随机输出速度

为了使输出更像“真人手打”的,我们可以设置 随机的 输出速度,并在每输出一个词后的 空格处 多停顿一段时间。在 node_modules\hexo-theme-keep\source\js\page\home-page.js 路径下,找到 initTypewriter 函数, 进而找到其中的 typewriter 函数:

1
2
3
4
5
6
7
8
9
const typewriter = () => {
if (charIndex < text.length) {
desc.textContent += text.charAt(charIndex)
charIndex++
setTimeout(typewriter, 100)
} else {
cursor.style.display = 'none'
}
}

进行如下改动:

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
const typewriter = () => {
if (charIndex < text.length) {
desc.textContent += text.charAt(charIndex)
// 自定义速度
if(text.charAt(charIndex) === ''){
charIndex++
const randomNumber = Math.floor(Math.random() * 3);
if(randomNumber === 0){
setTimeout(typewriter, 100)
}else if(randomNumber === 1){
setTimeout(typewriter, 130)
}else{
setTimeout(typewriter, 180)
}

}else{
charIndex++
const randomNumber = Math.floor(Math.random() * 3);
if(randomNumber === 0){
setTimeout(typewriter, 20)
}else if(randomNumber === 1){
setTimeout(typewriter, 40)
}else{
setTimeout(typewriter, 60)
}
}
} else {
cursor.style.display = 'none'
}
}

使得填入 setTimeout 的数据不断变化,便可更好地模仿“真人手打”的效果。

始终显示光标闪烁

默认设置下,打字机在打字结束后会自动隐藏光标。若想在打字结束后仍然保留闪烁的光标,可在 node_modules\hexo-theme-keep\source\js\page\home-page.js 路径下,找到 initTypewriter 函数, 进而找到其中的 typewriter 函数:

1
2
3
4
5
6
7
8
9
const typewriter = () => {
if (charIndex < text.length) {
desc.textContent += text.charAt(charIndex)
charIndex++
setTimeout(typewriter, 100)
} else {
cursor.style.display = 'none'
}
}

else 语句下的一行代码注释掉:

1
2
3
} else {
// cursor.style.display = 'none'
}

即可实现效果。

启用 / 关闭打字机效果

在主题配置文件 keep.yml_config.keep.yml中,找到 first_screen 词条,在其下添加 enableTypewriter 词条。enableTypewriter的值可以自由配置,true为启用打字机,false为禁用打字机。

1
2
first_screen:
enableTypewriter: false # true 为启用打字机,false 为禁用打字机

node_modules\hexo-theme-keep\source\js\page\home-page.js 路径下,找到 initTypewriter 函数,添加一个 if-else 判断语句, 具体更改内容如下,请对照使用。

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
const initTypewriter = () => {
const isHitokoto = fsc?.hitokoto === true

if (fsc?.enable !== true) {
return
}

if (fsc?.enable === true && !isHitokoto && !fsc?.description) {
return
}

if (fsc?.enableTypewriter === true){
const descBox = document.querySelector('.first-screen-content .description')
if (descBox) {
descBox.style.opacity = '0'

setTimeout(
() => {
descBox.style.opacity = '1'
const descItemList = descBox.querySelectorAll('.desc-item')
descItemList.forEach((descItem) => {
const desc = descItem.querySelector('.desc')
const cursor = descItem.querySelector('.cursor')
const text = desc.innerHTML
desc.innerHTML = ''
let charIndex = 0

if (text) {
const typewriter = () => {
if (charIndex < text.length) {
desc.textContent += text.charAt(charIndex)
charIndex++
setTimeout(typewriter, 100)
} else {
cursor.style.display = 'none'
}
}
typewriter()
}
})
},
isHitokoto ? 400 : 300
)
}
}else{
const descBox = document.querySelector('.first-screen-content .description');
if (descBox) {
descBox.style.opacity = '1'; // 保证描述内容可见
const descItemList = descBox.querySelectorAll('.desc-item');
descItemList.forEach((descItem) => {
const desc = descItem.querySelector('.desc');
const cursor = descItem.querySelector('.cursor')
const text = desc.innerHTML;
cursor.style.display = 'none'
desc.innerHTML = text; // 直接显示完整内容

});
}
}

}

这段代码添加了一个 if-else 判断,当识别 enableTypewriter 值为 true 时,便会执行原来的打字机代码,当识别为 false 时,便会执行新的代码,这段新代码会使得description(或hitokoto)的内容被直接显示。此外,同样可以选择将新代码中的

1
cursor.style.display = 'none'

一行注释掉,从而在description(或hitokoto)句末保留闪烁的光标。

常见问题

为方便读者确认操作是否有误,笔者将修改后的 initTypewriter 最终代码贴在下方。该代码实现了:

  • 随机而自然的输出速度

  • 打字结束后保留闪烁光标

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
const initTypewriter = () => {
const isHitokoto = fsc?.hitokoto === true

if (fsc?.enable !== true) {
return
}

if (fsc?.enable === true && !isHitokoto && !fsc?.description) {
return
}

if (fsc?.enableTypewriter === true){
const descBox = document.querySelector('.first-screen-content .description')
if (descBox) {
descBox.style.opacity = '0'

setTimeout(
() => {
descBox.style.opacity = '1'
const descItemList = descBox.querySelectorAll('.desc-item')
descItemList.forEach((descItem) => {
const desc = descItem.querySelector('.desc')
const cursor = descItem.querySelector('.cursor')
const text = desc.innerHTML
desc.innerHTML = ''
let charIndex = 0

if (text) {
const typewriter = () => {
if (charIndex < text.length) {
desc.textContent += text.charAt(charIndex)
// 自定义速度
if(text.charAt(charIndex) === ' '){
charIndex++
const randomNumber = Math.floor(Math.random() * 3);
if(randomNumber === 0){
setTimeout(typewriter, 100)
}else if(randomNumber === 1){
setTimeout(typewriter, 130)
}else{
setTimeout(typewriter, 180)
}

}else{
charIndex++
const randomNumber = Math.floor(Math.random() * 3);
if(randomNumber === 0){
setTimeout(typewriter, 20)
}else if(randomNumber === 1){
setTimeout(typewriter, 40)
}else{
setTimeout(typewriter, 60)
}
}
} else {
// 打字结束后隐藏光标
// cursor.style.display ='none'
}
}

typewriter()
}
})
},
isHitokoto ? 400 : 300
)
}
}else{
const descBox = document.querySelector('.first-screen-content .description');
if (descBox) {
descBox.style.opacity = '1'; // 保证描述内容可见
const descItemList = descBox.querySelectorAll('.desc-item');
descItemList.forEach((descItem) => {
const desc = descItem.querySelector('.desc');
const cursor = descItem.querySelector('.cursor')
const text = desc.innerHTML;
cursor.style.display = 'none'
desc.innerHTML = text; // 直接显示完整内容

});
}
}

}
 REWARD AUTHOR
 Comments
Comment plugin failed to load
Loading comment plugin