给Joe主题添加代码行号

550 阅读4分钟

因为已习惯用 Markdown 写文章,最近我将博客从 Wordpress 转到了Typecho。转换过程还是比较顺利的。我选的是 Joe 主题,最新版本 7.3.7。Joe 内置了代码高亮,无需借助任何插件。实际上是借助 Prism 来实现的,支持200种语言,但就是没有我要的显示代码行号功能。

纯 CSS 方案

记得以前做用的是纯 CSS 实现过加行号的功能,尝试能不能在这里管用。迅速找到以前的 demo 文件,代码很简单。

  • HTML 片段:
<pre class="language-css"><code class="language-css">
  <span class="token">code {</span>
    <span class="token">counter-reset: step;</span>
    <span class="token">counter-increment: step 0;</span>
  <span class="token">}
  <span class="token"></span>
  <span class="token">code .token:before {</span>
    <span class="token">position: absolute;</span>
    <span class="token">left: 10px;</span>
    <span class="token">content: counter(step);</span>
    <span class="token">counter-increment: step;</span>
  <span class="token">}</span>
</code>
</pre>
  • CSS 片段:
code {
  counter-reset: step;
  counter-increment: step 0;
}

code .token:before {
  position: absolute;
  left: 10px;
  content: counter(step);
  counter-increment: step;
}

效果如下图:

line-numbers.jpg

这应该是我见过的最简单的显示代码行号实现了。正如我们所见,上述 CSS 样式我们声明了计数器 step,使用 counter-reset 属性重置计数器值为 0,使用 counter-increment 属性每行递增计数器值,然后在 content 属性上应用 counter() 函数将返回值显示在页面上。

注意:counter() 函数以字符串形式返回当前计数器的值。不支持 IE 浏览器。

我满怀希望的把这段 “魔法” CSS 代码片段应用到 Jeo 主题上。结果行号是可以显示出来,但行号重叠了😓。原来 demo 文件HTML 片段中每行只有一个 token,但是 Prism 渲染出来 token 一行可能有几个。难怪行号会重叠,所以这种方案是不可行的。如果我们还用 Prism 来高亮显示代码,就得借助它的 Line Numbers 插件了。

使用 Line Numbers 插件

让我们看另外一种方案 —— 使用Line Numbers 插件。插件安装起来简单。

  • 导入依赖:

header 中引入 CSS 文件,然后在 body 最后一行的前面引入 JavaScript 文件。

<head>
  <link href="https://prismjs.com/themes/prism.css">
  <link href="https://prismjs.com/plugins/line-numbers/prism-line-numbers.css">
</head>
<body>
  ...
  <script src="https://prismjs.com/prism.js"></script>
  <script src="https://prismjs.com/plugins/line-numbers/prism-line-numbers.js"></script>
</body>
  • 添加 line-numbers 样式类

<pre> 或其父容器上添加 line-numbers 样式类,如下所示:

<pre class="language-css line-numbers"><code class="language-css">
    pre {
      padding: 0 20px 0 40px;
    }
  
    code {
      counter-reset: step;
      counter-increment: step 0;
    }
  
    code .token:before {
      position: absolute;
      left: 0;
      width: 40px;
      text-align: right;
      content: counter(step);
      counter-increment: step;
    }
  </code>
</pre>

现在,在代码块的父标签或者代码块容器上面加入 line-numbers 类,就可以显示行号了。访问 codesandbox 查看实例效果

不过不知你注意到没有,最终代码块的头部尾部多出两个行号。仔细看一下这个插件的使用文档是不能通过设置来解决这个问题的。时了一下,最简单的办法时将 <code> 开始标签放第一行,<code> 结束标签放最后一行。

如果代码块较多,调整起来未免麻烦,可以试试 Prism的 Normalize Whitespace 插件。

解决整合问题

将 Line Numbers 插件整合到我的Joe主题后,我发现任没有行号,虽然代码块左边距比以前大。这就叫人尴尬了。是不是这个插件的bug 呢?得看看这个插件的源码。

Prism 需要 DOM 才能让大多数插件工作。查看 plugins/line-numbers/prism-line-numbers.js#L220 中的代码(还好源码就200 行左右),我们可以看到行号都被包裹在 <span aria-hidden="true" class="line-numbers-rows"> 中,每个行号对应一组空白内容的 <span></span> 元素标签。然后查看 plugins/line-numbers/prism-line-numbers.css 中的代码,我们发现这里得样式和先前demo 文件中得相似,每行 span 都包含一个伪选择器 ::before, 也是在 content 上应用 counter() 函数来显示返回的计数器值。不同的是,插件用 JavaScript 遍历创建出用来显示行号的 span 元素,每行一个元素,所以不会重叠。

回到不显示行号的问题,我用开发者工具仔细地研究了一下渲染出来的源码,发现插件其实已经生成行号 span 元素,伪选择器 ::before 下确实有相应的 CSS 样式属性。所以这个问题的根本原因在于 prism-line-numbers.css 样式不兼容 Jeo主题。略微调整一下样式,终于大功告成,完美解决了这个问题。可以看一下具体的代码改动:

pre[class*="language-"].line-numbers {
  ...
  padding-left: 48px;
}

.line-numbers .line-numbers-rows {
  ...
  left: 10px;
  width: 30px;
  ...
}

30 + 10 + 8 = 48。好了,1000 行以内的代码显示行号时没有问题了。

提示:如果不得不兼容 IE,我们可以用 JavaScript 遍历 line-numbers-rows 下所有的 span 元素, 用数组 index + 1 代替 count() 返回的函数值即可。

参考资源

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情