博客里的代码框一直有个问题,每行的内容太长,代码框的宽度没法完整容纳一行。网上有过加横向滚动条、鼠标移上去就代码框自动加长这两种解决方案,但都不太美观。自动换行的方案符合大部分人的习惯,于是我研究了代码框的自动换行。本文设置了一个“行号和内容分开复制、代码自动换行并不影响自动高亮”的代码框,文中有可以用的代码以及用到属性的归纳。

基础的HTML代码

博客生成的代码框,格式经过简化就是如下的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<figure class="highlight javascript">
<table> <tbody> <tr>
<td class="gutter">
<pre>
<span class="line">0</span><br>
<span class="line">1</span><br>
<span class="line">2</span><br>
</pre>
</td>
<td class="code">
<pre>
<span class="line">"a "<span class="keyword">very</span>" very long line"</span><br>
<span class="line">a very very long line</span><br>
<span class="line">a very very long line</span><br>
</pre>
</td>
</tr> </tbody> </table>
</figure>

这些代码均为自动生成,也没有什么可以讨论的。代码高亮通过span实现;由于代码与行号生成在两个td中,所以复制代码的时候不会复制到行号。

换行的CSS设置

换行涉及到三个内容,具体实现的代码为:

1
2
3
4
figure .code span {
white-space: pre-wrap;
word-break: break-all;
}

其中分别的作用为:

  • white-space:设置元素中的空白符如何处理
  • overflow-wrap:设置完整单词的断开方式(未用到)
  • word-break:设置完整单词(包含汉语等的句子)的断开方式

white-space

空白符的处理简而言之是这样一张表:

属性 多个换行 空格和换行 自动换行 行末空格
normal 保留一个 保留一个 移除
nowrap 保留一个 保留一个 移除
pre 保留 保留 保留
pre-wrap 保留 保留 保留不换行
pre-line 保留 保留一个 移除
break-spaces 保留 保留一个 保留换行

我们的代码,对于多个换行需要全部保留,多个空格和换行需要全部保留,需要自动换行,行末空格保留但不需要因为空白字符换行。所以就white-space的属性可以选择pre-wrap

overflow-wrap

虽然空白符处理中我们设置了需要换行,但是过长的单词还是会导致行宽超出预设的宽度。

属性 如何处理过长单词
normal 保留
break-word 断开

我们不希望因为有过长的单词导致出现横向滚动栏,所以这里可以选择break-word。但考虑到与word-break属性的作用重合,而且word-break属性更适合汉语的特别处理,所以就不再使用overflow-wrap

word-break

换行的设置也可以使用这个,这个属性可以将汉语整句视为一个单词不换行(直到有标点符号为止)。

属性 英文处理 中文处理
normal 不断开单词 断开句子
break-all 断开单词 断开句子
keep-all 不断开单词 不断开句子
break-word 可能的话不断开单词 断开句子

其中break-allbreak-word的区别为:若一行可以容纳一个单词但是无法容纳两个,break-all将断开第二个单词,break-word将把第二个单词放在第二行。

根据要求,我们就选用了break-all。这里其实和使用overflow-wrap: break-word是一样的效果。

换行的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
+function($) {
'use strict';

var CodeBlockResizer = function(elem) {
this.$codeBlocks = $(elem);
};

CodeBlockResizer.prototype = {
run: function() {
var self = this;
self.resize();
$(window).smartresize(function() {
self.resize();
});
},

resize: function() {
var self = this;
self.$codeBlocks.each(function() {
var $gutter = $(this).find('.gutter');
var $code = $(this).find('.code');

var codePaddings = $code.width() - $code.innerWidth();
var width = $(this).outerWidth() - $gutter.outerWidth() + codePaddings;

$code.css('width', width);
$code.children('pre').css('width', width);

for (var i = 0, m = $gutter.find('span').length; i < m; i++) {
$gutter.find('span').eq(i).height(
$code.find('span.line')[i].offsetHeight);
}
});
}
};

$(document).ready(function() {
$.fn.hasHorizontalScrollBar = function() {
return this.get(0).scrollWidth > this.innerWidth();
};
var resizer = new CodeBlockResizer('figure.highlight');
resizer.run();
});
}(jQuery);

这里与行号位置相关的部分就是29-32行的内容,根据offsetHeight调整height

仅是这样还是无法更改行号的位置,原因在于目前显示代码使用的都是span元素。我们若需要通过height调整行号所占的高度,应将其显示方式改为inline-block,所以增加如下CSS:

1
2
3
4
figure.highlight pre > .line {
display: inline-block;
line-height: 18px;
}

其中如果不设置display: inline-blockline-heightheight也将不相同,可能出现的小数将导致不必要的麻烦。

到这里就完成了全部关于代码换行的设置。