「主题改造」Typecho使用highlight.js代码高亮实现代码行号插图

前言

自开始写博客主题以来,代码高亮的行号问题一直让我颇为头疼。记得初学前端时,我曾尝试过一些方法,比如利用代码行的固定高度来创建一个伪元素,并通过脚本生成大量行号内容,然后利用内容溢出剪切来实现行号。不过最近,我注意到一些博客主题开始增加了代码行号,这让我又燃起了研究这个功能的兴趣。

「主题改造」Typecho使用highlight.js代码高亮实现代码行号插图1

教程

首先,我们需要确保代码已经被highlight.js进行了高亮处理,这样可以避免在添加行号时产生解析冲突。

highlight.js的基本使用如下:

import hljs from 'highlight.js';
import javascript from 'highlight.js/lib/languages/javascript';

// 注册代码语言解析
hljs.registerLanguage('javascript', javascript);

// 对DOM元素进行代码高亮
document.addEventListener('DOMContentLoaded', (event) => {
  document.querySelectorAll('pre code').forEach((el) => {
    hljs.highlightElement(el);
  });
});

接下来,我们观察了一些已经实现代码行号的网页的HTML结构,发现它们给每行代码都添加了一个带有自定义data属性的span标签,该属性的值即为行号。

基于这个发现,我们可以编写一个函数来添加行号:

function addLineNumber(codeDom) {
  codeDom.classList.add("code-block-extension-code-show-num");
  const codeHtml = codeDom.innerHTML;
  const lines = codeHtml
    .split("\n")
    .map((line, index) => {
      return `<span class="code-block-extension-code-line" data-line-num="${index + 1}">${line}</span>`;
    })
    .join("\n");
  codeDom.innerHTML = lines;
}

这个函数首先获取到代码的HTML文本,然后通过split(“\n”)将其转换成数组。这样,数组中的每个元素就代表一行代码。接着,我们使用map函数遍历数组,并为每一行代码添加一个带有行号data属性的span标签。最后,我们使用join将数组重新转换成HTML文本,并更新DOM。

接下来,我们添加一些CSS样式来美化行号:

.code-block-extension-code-line {
  background-color: inherit;
}

.code-block-extension-code-line::before {
  content: attr(data-line-num);
  color: gray;
  user-select: none;
  background-color: inherit;
  text-align: center;
  display: inline-block;
  width: 18px;
  padding: 0 10px;
  box-sizing: content-box;
  position: sticky;
  left: 0;
}

这种方法基本上可以满足大部分代码展示的需求。不过需要注意的是,行号的宽度是固定的18px,如果你的代码行数非常多,可能需要调整这个宽度。