本站 - 代码块隐藏模块原创
笔记
一个代码块的代码太多,会占据大量的篇幅,如果能选择性隐藏,页面也许更加好看。
2021-01-11 @Young Kbt
# 前言
目前适用版本是 Vdoing v1.x。
代码块可以隐藏,也可以展开,这和 ::: details
类似,下面是简单的代码块 Demo:
public class Hello {
public static void main(String[] args) {
System.out.println("Hello,World");
}
}
2
3
4
5
看到代码块右边的箭头了吗,点击即可隐藏代码块,再次点击则会展开代码块。
本内容实现并不难,只需三步:
- 添加箭头图标
- 编写代码块模块的 Vue 组件
- 全局注册 Vue 组件
实现内容:
代码块的隐藏和显示
美化代码块的 UI,趋向于 Mac
优化代码块语言的显示,因为默认主题的一些语言如 stylus 是不会显示出来。本内容的优化无论代码块语言是什么(如
abc
),都会显示出来,如下我的语言不是 Java、PHP、JS、SH,而是 abdedfg
1
# 前提 1
本内容重新实现的一键复制功能是基于 vuepress-plugin-one-click-copy
插件(箭头左边),该插件已经内置 vuepress-theme-vdoing
主题,所以无需担心,如果你曾经卸载了该插件,则需要安装回来;如果已经安装,则无需看这一步:
yarn add vuepress-plugin-one-click-copy -D
当然,如果你懂得看下面的源码,则将适配 vuepress-plugin-one-click-copy
插件的代码进行修改,只需要提供其他插件的 class 名进行判断(Vue 组件的 108 - 119 行代码),并自行在 F12 调试,移动到满意的位置。
如果不知道自己是否曾卸载或存在该插件,则前往根目录下的 package.json
文件查看 devDependencies
是否有 vuepress-plugin-one-click-copy
插件。
# 前提 2
本功能需要代码块需要开启 行号 功能,该功能已经内置 VuePress,所以只需要开启该配置即可。
在 docs/.vuepress/config.ts
里开启行号:
export default defineConfig4CustomTheme({
theme: "vdoing", // 使用 npm 包主题
// ...
markdown: {
lineNumbers: true, // 显示代码块的行号
extractHeaders: ["h2", "h3", "h4"], // 支持 h2、h3、h4 标题
},
// ...
});
2
3
4
5
6
7
8
9
# 添加箭头图标
图标库来自阿里云:https://www.iconfont.cn/
。
如果你没有账号,或者觉得添加比较麻烦,就使用我的图标库地址,当你发现图标失效了,就请来这里获取新的地址,如果还没有更新,请在评论区留言。
当然,建议你使用自己的图标库,比较稳定。就像注册一个购物账户,然后添加到购物车即可。
在 docs/.vuepress/config.js(新版是 config.ts)的 head 模块里添加如下内容:
['link', { rel: 'stylesheet', href: '//at.alicdn.com/t/font_3114978_qe0b39no76.css' }]
# 添加Vue组件
在 docs/.vuepress/components 目录下创建 Vue 组件:BlockToggle.vue
。如果不存在 components 目录,则请创建。
添加如下内容:
<template></template>
<script>
export default {
mounted() {
setTimeout(() => {
this.addExpand(40);
}, 1000);
},
watch: {
$route(to, from) {
if (to.path != from.path || this.$route.hash == "") {
setTimeout(() => {
this.addExpand(40);
}, 1000);
}
},
},
methods: {
// 隐藏代码块后,保留 40 的代码块高度
addExpand(hiddenHeight = 40) {
let modes = document.getElementsByClassName("line-numbers-mode");
// 遍历出每一个代码块
Array.from(modes).forEach((item) => {
// 首先获取 expand 元素
let expand = item.getElementsByClassName("expand")[0];
// expand 元素不存在,则进入 if 创建
if (!expand) {
// 获取代码块原来的高度,进行备份
let modeHeight = item.offsetHeight;
// display:none 的代码块需要额外处理,图文卡片列表本质是代码块,所以排除掉
if (
modeHeight == 0 &&
item.parentNode.className != "cardImgListContainer"
) {
modeHeight = this.getHiddenElementHight(item);
}
// modeHeight 比主题多 12,所以减掉,并显示赋值,触发动画过渡效果
modeHeight -= 12;
item.style.height = modeHeight + "px";
// 获取代码块的各个元素
let pre = item.getElementsByTagName("pre")[0];
let wrapper = item.getElementsByClassName("line-numbers-wrapper")[0];
// 创建箭头元素
const div = document.createElement("div");
div.className = "expand icon-xiangxiajiantou iconfont";
// 箭头点击事件
div.onclick = () => {
// 代码块已经被隐藏,则进入 if 循环,如果没有被隐藏,则进入 else 循环
if (parseInt(item.style.height) == hiddenHeight) {
div.className = "expand icon-xiangxiajiantou iconfont";
item.style.height = modeHeight + "px";
setTimeout(() => {
pre.style.display = "block";
wrapper.style.display = "block";
}, 80);
} else {
div.className = "expand icon-xiangxiajiantou iconfont closed";
item.style.height = hiddenHeight + "px";
setTimeout(() => {
pre.style.display = "none";
wrapper.style.display = "none";
}, 300);
}
};
item.append(div);
item.append(this.addCircle());
}
// 解决某些代码块的语言不显示在页面上
this.getLanguage(item);
// 移动一键复制图标到正确的位置
let flag = false;
let interval = setInterval(() => {
flag = this.moveCopyBlock(item);
if (flag) {
clearInterval(interval);
}
}, 1000);
});
},
getHiddenElementHight(hiddenElement) {
let modeHeight;
if (
hiddenElement.parentNode.style.display == "none" ||
hiddenElement.parentNode.className !=
"theme-code-block theme-code-block__active"
) {
hiddenElement.parentNode.style.display = "block";
modeHeight = hiddenElement.offsetHeight;
hiddenElement.parentNode.style.display = "none";
// 清除 vuepress 自带的 deetails 多选代码块
if (
hiddenElement.parentNode.className == "theme-code-block" ||
hiddenElement.parentNode.className == "cardListContainer"
) {
hiddenElement.parentNode.style.display = "";
}
}
return modeHeight;
},
// 添加三个圆圈
addCircle() {
let div = document.createElement("div");
div.className = "circle";
return div;
},
// 移动一键复制图标
moveCopyBlock(element) {
let copyElement = element.getElementsByClassName("code-copy")[0];
if (copyElement && copyElement.parentNode != element) {
copyElement.parentNode.parentNode.insertBefore(
copyElement,
copyElement.parentNode
);
return true;
} else {
return false;
}
},
// 解决某些代码块的语言不显示在页面上
getLanguage(element) {
// 动态获取 before 的 content 属性
let content = getComputedStyle(element, ":before").getPropertyValue(
"content"
);
// "" 的长度是 2,不是 0,"x" 的长度是 3
if (content.length == 2 || content == "" || content == "none") {
let language = element.className.substring(
"language".length + 1,
element.className.indexOf(" ")
);
element.setAttribute("data-language", language);
}
},
},
};
</script>
<style>
/* 代码块元素 */
.line-numbers-mode {
overflow: hidden;
transition: height 0.3s;
margin-top: 0.85rem;
}
.line-numbers-mode::before {
content: attr(data-language);
}
/* 箭头元素 */
.expand {
width: 16px;
height: 16px;
cursor: pointer;
position: absolute;
z-index: 3;
top: 0.8em;
right: 0.5em;
color: rgba(238, 255, 255, 0.8);
font-weight: 900;
transition: transform 0.3s;
}
/* 代码块内容 */
div[class*="language-"].line-numbers-mode pre {
margin: 30px 0 0.85rem 0;
}
/* 代码块的行数 */
div[class*="language-"].line-numbers-mode .line-numbers-wrapper,
.highlight-lines {
margin-top: 30px;
}
/* 箭头关闭后旋转 -90 度 */
.closed {
transform: rotate(90deg) translateY(-3px);
transition: all 0.3s;
}
li .closed {
transform: rotate(90deg) translate(5px, -8px);
}
/* 代码块的语言 */
div[class*="language-"]::before {
position: absolute;
z-index: 3;
top: 0.3em;
left: 4.7rem;
font-size: 1.15em;
color: rgba(238, 255, 255, 0.8);
text-transform: uppercase;
font-weight: bold;
width: fit-content;
}
/* li 下的代码块的语言和 li 下的箭头 */
li div[class*="language-"]::before,
li .expand {
margin-top: -4px;
}
/* 代码块行数的线条 */
div[class*="language-"].line-numbers-mode::after {
margin-top: 35px;
}
/* 代码块的三个圆圈颜色 */
.circle {
position: absolute;
top: 0.8em;
left: 0.9rem;
width: 12px;
height: 12px;
border-radius: 50%;
background: #fc625d;
-webkit-box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b;
box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b;
}
/* 代码块一键复制图标 */
.code-copy {
position: absolute;
top: 0.8rem;
right: 2rem;
fill: rgba(238, 255, 255, 0.8);
opacity: 1;
}
.code-copy svg {
margin: 0;
}
/* 如果你浅色模式的代码块背景色是浅灰色,则取消下面的注释使代码生效,如果是黑色,则注释下面的三段代码(我注释了,因为是黑色背景) */
/* .theme-mode-light .expand {
color: #666;
}
.theme-mode-light div[class*="language-"]::before {
color: #666;
}
.theme-mode-light .code-copy {
fill: #666;
} */
</style>
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
第 7 行和第 14 行的参数 40 是隐藏代码块后,保留的代码块高度,40 是默认值。
注意
- 如果浅色模式的代码块背景色是浅灰色,则取消 226 - 234 的注释使代码生效(模板已经取消注释)
- 如果是黑色,则注释 226 - 234 的代码(我自己的注释了,因为我的代码块是黑色背景)
- 如果不喜欢代码块的语言变成大写,则注释 188 行的
text-transform: uppercase;
如果你想要你的代码块和我一样是 黑色,则打开 docs/.vuepress/styles/palette.styl
文件,替换掉原来的浅色模式:
.theme-mode-light
--bodyBg: #f4f4f4
--mainBg: rgba(255,255,255,1)
--sidebarBg: rgba(255,255,255,.8)
--blurBg: rgba(255,255,255,.9)
--customBlockBg: rgba(255,255,255,.9)
--textColor: #00323c
--textLightenColor: #0085AD
--borderColor: rgba(0,0,0,.15)
// 代码块浅色主题
//--codeBg: #f6f8fa
//--codeColor: #24292e
//codeThemeLight()
// 行高亮颜色,和代码块浅色主题一起使用,一起注释
//div[class*="language-"]
// .highlight-lines
// .highlighted
// background-color rgba(200,200,200,.4)
// &.line-numbers-mode
// .highlight-lines .highlighted
// &:before
// background-color rgba(200,200,200,.4)
// 代码块深色主题
--codeBg: #282C34
--codeColor: #D4D4D4
codeThemeDark()
// 行高亮颜色,和代码块深色主题一起使用,一起注释
div[class*="language-"]
.highlight-lines
.highlighted
background-color rgba(0,0,0,.66)
&.line-numbers-mode
.highlight-lines .highlighted
&:before
background-color rgba(0,0,0,.66)
div[class*="language-"].line-numbers-mode::after // 代码块的行数和内容分割线颜色
border-right 1px solid rgba(0, 0, 0, 0.66)
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
如果你喜欢加粗的 绿色、`` 包裹的 英文高亮 abcd
、<mark></mark> 包裹的 文字高亮、深色模式的颜色(点击右下角的衣服图标,切换深色模式)等等,那么可以参考我的自定义样式模块,左侧的关于本站目录下就能找到。
# 注意
vuepress-plugin-one-click-copy
插件在移动端(手机端)失效,因为其自带的隐藏效果原因,这并不是本模块引起,而是本身插件的设计问题,所以如果觉得移动端也想要支持一键复制,请更换其他插件,并自行修改源码进行适配- 低分辨率的电脑,会导致代码的行数与代码不对应(代码行数溢出),这并非本模块原因,而是 VuePress 代码块本身的原因,可能新版本会修复
# 注册Vue组件
在 docs/.vuepress/config.js(新版是 config.ts)的 plugins 中添加插件配置。
添加如下内容:
# 结束语
如果你正在热编译 markdown 的代码块,它不会立马生效,你只需要刷新下就能看到效果,而打包后,效果是会生效,无需担心。
如果你还有疑惑,可以去我的 GitHub 仓库或者 Gitee 仓库查看源码。
如果你有更好的方式,评论区留言告诉我,或者加入 Vdoing 主题的 QQ 群:694387113。谢谢!