本篇将以 Butterfly 主题为例,讲述如何为你的 Hexo 博客添加文字荧光突出效果。
文章包括了灵感以及探索的历程。如果不感兴趣可以直接在目录中跳转到「操作」。

效果

披绣闼,俯雕甍,山原旷其盈视,川泽纡其骇瞩。

闾阎扑地,钟鸣鼎食之家;

舸舰迷津,青雀黄龙之舳。

云销雨霁,彩彻区明。

落霞与孤鹜齐飞,秋水共长天一色。

渔舟唱晚,响穷彭蠡之滨,

雁阵惊寒,声断衡阳之浦。

以上文字,出自 《滕王阁序》

Markdown 源代码
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
{% highlighter red %}

披绣闼,俯雕甍,山原旷其盈视,川泽纡其骇瞩。

{% endhighlighter %}

{% highlighter orange %}

闾阎扑地,钟鸣鼎食之家;

{% endhighlighter %}

{% highlighter yellow %}

舸舰迷津,青雀黄龙之舳。

{% endhighlighter %}

{% highlighter green %}

云销雨霁,彩彻区明。

{% endhighlighter %}

{% highlighter cyan %}

落霞与孤鹜齐飞,秋水共长天一色。

{% endhighlighter %}

{% highlighter blue %}

渔舟唱晚,响穷彭蠡之滨,

{% endhighlighter %}

{% highlighter purple %}

雁阵惊寒,声断衡阳之浦。

{% endhighlighter %}

以上文字,出自 {% ihighlighter 《滕王阁序》 cyan %}。

灵感

周三那天,我看到了某位同学历史书上画的花里胡哨的荧光笔,又突然想旧技术博客的超链接也有类似效果;今天趁着假期,就翻出我自改的 fly-pro 主题的有关样式文件,发现有以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
a,
.link {
color: var(--c-base-black);
text-decoration: none;
cursor: pointer;
background: transparent 0 0;

&:hover,
&:focus {
// text-decoration: underline;
}

&:hover,
&:focus,
&:active {
outline: 0;
}
}

a.purple-link {
position: relative;
background: linear-gradient(180deg,transparent 70%,rgba(101,125,225,.4) 0);
}

渲染出就是一个荧光超链接的效果。

稍加改动,即可制作出 DEMO:

DEMO
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
<!DOCTYPE html>
<html lang="zh-CN">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>demo</title>
<style>
.highlighter {
font-weight: bold;
}

.highlighter.red-highlighter {
background: linear-gradient(180deg, transparent 70%, rgba(215, 0, 58, .4) 0);
}

.highlighter.orange-highlighter {
background: linear-gradient(180deg, transparent 70%, rgba(255, 165, 0, .4) 0);
}

.highlighter.yellow-highlighter {
background: linear-gradient(180deg, transparent 70%, rgba(255, 255, 0, .4) 0);
}

.highlighter.green-highlighter {
background: linear-gradient(180deg, transparent 70%, rgba(102, 204, 0, .4) 0);
}

.highlighter.cyan-highlighter {
background: linear-gradient(180deg, transparent 70%, rgba(0, 255, 255, .4) 0);
}

.highlighter.blue-highlighter {
background: linear-gradient(180deg, transparent 70%, rgba(0, 128, 255, .4) 0);
}

.highlighter.purple-highlighter {
background: linear-gradient(180deg, transparent 70%, rgba(101, 125, 225, .4) 0);
}
</style>
</head>

<body>
<p><span class="highlighter red-highlighter">RED</span></p>
<p><span class="highlighter orange-highlighter">ORANGE</span></p>
<p><span class="highlighter yellow-highlighter">YELLOW</span></p>
<p><span class="highlighter green-highlighter">GREEN</span></p>
<p><span class="highlighter cyan-highlighter">CYAN</span></p>
<p><span class="highlighter blue-highlighter">BLUE</span></p>
<p><span class="highlighter purple-highlighter">PURPLE</span></p>
</body>

</html>

图:DEMO 效果图

遂欲用之于 Hexo 博客。乃制一标签外挂。

探索

段落

根据 Hexo 博客官方的文档《标签插件(Tag) | Hexo》,我们可以仿造一个 highlighter 标签(有结束标签),用于实现简单的效果。以 Butterfly 主题为例,可以这样添加自定义的标签外挂:

  • 打开 [ThemeRoot]/scripts/tag,新建一个 highlighter.js。不难写出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    /**
    * highlighter
    * by PanDaoxi
    */

    "use strict";

    hexo.extend.tag.register(
    "highlighter",
    (args, content) => {
    var color = args[0] + "-highlighter";
    return `<span class="highlighter ${color}">${content}</span>`;
    },
    { ends: true }
    );
  • 打开 [ThemeRoot]/source/css,新建样式文件 custom.css(或者是你的自定义样式文件),添加:

    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
    .highlighter {
    font-weight: bold;
    }

    .highlighter.red-highlighter {
    background: linear-gradient(
    180deg,
    transparent 70%,
    rgba(215, 0, 58, 0.4) 0
    );
    }

    .highlighter.orange-highlighter {
    background: linear-gradient(
    180deg,
    transparent 70%,
    rgba(255, 165, 0, 0.4) 0
    );
    }

    .highlighter.yellow-highlighter {
    background: linear-gradient(
    180deg,
    transparent 70%,
    rgba(255, 255, 0, 0.4) 0
    );
    }

    .highlighter.green-highlighter {
    background: linear-gradient(
    180deg,
    transparent 70%,
    rgba(102, 204, 0, 0.4) 0
    );
    }

    .highlighter.cyan-highlighter {
    background: linear-gradient(
    180deg,
    transparent 70%,
    rgba(0, 255, 255, 0.4) 0
    );
    }

    .highlighter.blue-highlighter {
    background: linear-gradient(
    180deg,
    transparent 70%,
    rgba(0, 128, 255, 0.4) 0
    );
    }

    .highlighter.purple-highlighter {
    background: linear-gradient(
    180deg,
    transparent 70%,
    rgba(101, 125, 225, 0.4) 0
    );
    }
  • 打开样式配置文件(可以在 [ThemeRoot]/_config.yml),找到 inject 配置项。在 head 添加:

    1
    - <link rel="stylesheet" href="/css/custom.css"/>
  • 重启博客。

    1
    2
    hexo cl
    hexo s

这样即可配置好。

但是,渲染出的只是纯文字,不能使用 markdown 语法,甚至没办法换行。

图:最简单的纯文字渲染

接着,我们查阅文档《渲染 | Hexo》了解到可以通过以下代码渲染字符串为 html

1
hexo.render.renderSync({ text: content, engine: "markdown", });

console.log 一下,七个标签分别得到的字符串是:

1
2
3
4
5
6
7
8
9
10
11
12
13
<p>披绣闼,俯雕甍,山原旷其盈视,川泽纡其骇瞩。</p>

<p>闾阎扑地,钟鸣鼎食之家;</p>

<p>舸舰迷津,青雀黄龙之舳。</p>

<p>云销雨霁,彩彻区明。</p>

<p>落霞与孤鹜齐飞,秋水共长天一色。</p>

<p>渔舟唱晚,响穷彭蠡之滨,</p>

<p>雁阵惊寒,声断衡阳之浦。</p>

我们接下来就需要考虑如何给每个元素都添加上对应的类名。

考虑到字符串中可能存在多个 html 元素,就希望转化为 DOM 来处理。但是,在 Node.js 环境下使用 DOM 不可行,使用正则表达式匹配 html 元素标签不可靠。这时想到使用库 cheerio

安装 cheerio

1
2
3
npm in cheerio
// 如果有 cnpm 的话,也可以是
cnpm in cheerio

highlighter.js 就需要写成:

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
/**
* highlighter
* by PanDaoxi
*/

"use strict";

const cheerio = require("cheerio");

hexo.extend.tag.register(
"highlighter",
(args, content) => {
var color = args[0] + "-highlighter";
var html = hexo.render.renderSync({
text: content,
engine: "markdown",
});
const $ = cheerio.load(html);
$("body")
.children()
.each(function () {
$(this).addClass(`highlighter ${color}`);
});
return $("body").html();
},
{ ends: true }
);

可惜还是不行。程序确实是为每个元素正确添加了类,但是却是操作了整个段落,即给 <p> 标签添加了 highlighter 类。这可不太美观。

图:p 元素被添加了 highlighter 类

于是又想到可以给有文字的部分包裹 <span> 标签,然后只给 <span> 标签添加类。

对于每一个元素,首先检查是否有非空白的文本内容;如果有,则遍历该元素的子节点,找到所有文本节点(node.nodeType === 3),并对这些文本节点用一个 <span> 标签包裹,再添加相应类名。

现在 highlighter.js 这样写:

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
/**
* highlighter
* by PanDaoxi
*/

"use strict";

const cheerio = require("cheerio");

hexo.extend.tag.register(
"highlighter",
(args, content) => {
var color = args[0] + "-highlighter";
var html = hexo.render.renderSync({
text: content,
engine: "markdown",
});
const $ = cheerio.load(html);

$("body")
.find("*")
.addBack()
.each(function () {
if (
$(this)
.contents()
.filter(function () {
return this.nodeType === 3 && this.nodeValue.trim();
}).length
) {
$(this)
.contents()
.each(function () {
if (this.nodeType === 3 && this.nodeValue.trim()) {
$(this).wrap(
`<span class="highlighter ${color}"></span>`
);
}
});
}
});

return $("body").html();
},
{ ends: true }
);

也可以正确渲染了。

1
2
3
4
5
6
<p>
<span class="highlighter red-highlighter">
披绣闼,俯雕甍,山原旷其盈视,川泽纡其骇瞩。
</span>
</p>
...

行内

众所周知,Butterfly 主题有丰富的标签外挂。在其文档《Butterfly 文檔(四) 標簽外挂 | Butterfly》中提到有一个 label 标签。效果如下:

臣亮言:先帝创业未半,而中道崩殂。今天下三分,益州疲敝,此诚危急存亡之秋也!然侍卫之臣,不懈于内;忠志之士,忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气;不宜妄自菲薄,引喻失义,以塞忠谏之路也。
宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理;不宜偏私,使内外异法也。

它的原理很简单。在 [ThemeRoot]/scripts/tag/label.js 中体现了该标签的实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Butterfly
* label
* {% label text color %}
*/

'use strict'

const addLabel = args => {
const [text, className = 'default'] = args
return `<mark class="hl-label ${className}">${text}</mark>`
}

hexo.extend.tag.register('label', addLabel, { ends: false })

根据刚才的经验,结合示例,我们可以写出:

1
2
3
4
5
6
7
8
9
10
hexo.extend.tag.register(
"ihighlighter",
(args) => {
var color = args[args.length - 1];
var text = args.slice(0, -1).join(" ");
// console.log(color, text);
return `<span class="highlighter ${color}-highlighter">${text}</span>`;
},
{ ends: false }
);

此时 {% ihighlighter 行内的 highlighter red %} 效果就是行内的 highlighter。只是你不能在行内标签使用 markdown 语句。

操作

  1. [BlogRoot] 打开命令行(终端),输入以下命令:

    1
    2
    3
    npm in cheerio
    // 如果有 cnpm 的话,也可以是
    cnpm in cheerio
  2. 打开 [ThemeRoot]/scripts/tag,新建一个 highlighter.js。输入以下代码:

    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
    /**
    * highlighter
    * by PanDaoxi
    */

    "use strict";

    const cheerio = require("cheerio");

    hexo.extend.tag.register(
    "highlighter",
    (args, content) => {
    var color = (args[0] ? args[0] : "purple") + "-highlighter";
    var html = hexo.render.renderSync({
    text: content,
    engine: "markdown",
    });
    const $ = cheerio.load(html);

    $("body")
    .find("*")
    .addBack()
    .each(function () {
    if (
    $(this)
    .contents()
    .filter(function () {
    return this.nodeType === 3 && this.nodeValue.trim();
    }).length
    ) {
    $(this)
    .contents()
    .each(function () {
    if (this.nodeType === 3 && this.nodeValue.trim()) {
    $(this).wrap(
    `<span class="highlighter ${color}"></span>`
    );
    }
    });
    }
    });

    return $("body").html();
    },
    { ends: true }
    );

    hexo.extend.tag.register(
    "ihighlighter",
    (args) => {
    var color = args[args.length - 1];
    var text = args.slice(0, -1).join(" ");
    // console.log(color, text);
    return `<span class="highlighter ${color}-highlighter">${text}</span>`;
    },
    { ends: false }
    );
  3. 打开 [ThemeRoot]/source/css,新建样式文件 custom.css(或者是你的自定义样式文件),添加:

    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
    .highlighter {
    font-weight: bold;
    }

    .highlighter.red-highlighter {
    background: linear-gradient(
    180deg,
    transparent 70%,
    rgba(215, 0, 58, 0.4) 0
    );
    }

    .highlighter.orange-highlighter {
    background: linear-gradient(
    180deg,
    transparent 70%,
    rgba(255, 165, 0, 0.4) 0
    );
    }

    .highlighter.yellow-highlighter {
    background: linear-gradient(
    180deg,
    transparent 70%,
    rgba(255, 255, 0, 0.4) 0
    );
    }

    .highlighter.green-highlighter {
    background: linear-gradient(
    180deg,
    transparent 70%,
    rgba(102, 204, 0, 0.4) 0
    );
    }

    .highlighter.cyan-highlighter {
    background: linear-gradient(
    180deg,
    transparent 70%,
    rgba(0, 255, 255, 0.4) 0
    );
    }

    .highlighter.blue-highlighter {
    background: linear-gradient(
    180deg,
    transparent 70%,
    rgba(0, 128, 255, 0.4) 0
    );
    }

    .highlighter.purple-highlighter {
    background: linear-gradient(
    180deg,
    transparent 70%,
    rgba(101, 125, 225, 0.4) 0
    );
    }

    以上样式中共提供七种颜色 redorangeyellowgreencyanbluepurple。可自行添加。

  4. 打开样式配置文件(可以在 [ThemeRoot]/_config.yml),找到 inject 配置项。在 head 添加:

    1
    - <link rel="stylesheet" href="/css/custom.css"/>
  5. 重启博客。

    1
    2
    hexo cl
    hexo s
  6. 完成。