中文排版二三事

前段时间一直在折腾中文排版相关的事情,自认为结果还算不错😄。故开源之,即是Entry.css。这是一个可配置的、更适合阅读的中文文章样式库,可以用来快速搭建中文博客主题或是用于项目文档的样式。在这篇博文中会介绍下在做这个库过程中学到的一些中文排版知识,以及它的特色。


垂直的韵律

Typography大师Robert Bringhurst (The Elements of Typographic Style一书的作者)曾经说:

Space in typography is like time in music. It is infinitely divisible, but a few proportional intervals can be much more useful than a limitless choice of arbitrary quantities.

排版中的空间就想音乐中的时间一样。他是无限整除的,但是按比例的间隔比起毫无限制的使用任意大小要有用很多。

在做web开发和设计中经常会用到网格。它即解决了统一性,也避免了我们在排版时纠结那一两个像素的位置摆放。可惜网格只能解决水平方向的排版布局,在垂直方向上一直没有这样的技术,全仰仗设计师大大的美感了。不过最近出现了baselinecss这样的css库,它类似于960 grid,提供了一个css库以及一些psd磨版。它是基于vertical rhythm原则设计的,解决了垂直方向上的排版布局。

Vertical Rhythm可译成垂直的旋律。它的排版思路是垂直方向上各行文字的行高是一个基础数值的n倍,n是正整数。一般情况下,我们会把基础数值设置成基本行高。这样的限制可以让文字的布局变得更美观,且易于阅读。特别是对于有很多文字的页面,减少视觉疲劳很重要。

上面那个样例采用了14px/28px和21px/28px这两种字体大小/行高样式。对于中文来说看起来挺合适。基线之道这篇文章中也提到了另外一种比例:16px/22px ,28px/33px,40px/44px。它的基础数值是11px,所以也并不需要拘泥于“基础数值是基本文字的行高”这条。实际上我们需要遵守的规则是:

按照一定比例的间隔,让阅读更加舒适。

这看起来这是一件挺简单的事情,但是实际操作起来还是会有很多问题的。于是涌现了很多相关工具,辅助你开发这样拥有美妙旋律的网站。例如:

  1. baseliner.js是一个js库帮助你在页面上绘制固定间隔的横线
  2. basehold.it 是一个类似hold.it的服务,可以提供用于绘制横线的css或背景图片
  3. baseline.js是一个js库用于确保外部资源,例如图片的高度是基础数值的n倍。不过这个库还有不少bug

开发时你还会用到些复杂情况。

边距与边框(em与px)

当我们需要设置上下各一像素边框时,就会导致有两像素多余。旋律就会被打破。同样上下padding与margin都会有这个问题。我们可以设置上下padding/margin/border之和为基础数值的正整数倍。

对于基于px单位的情况,这样处理还算比较容易,只是加减法而已。如果你是用em这样的相对单位呢?你的border如果实际上只需要1px,字体大小为14px。那么你需要设置border为1/14em。如果你不想支持旧浏览器,那么你还可以用伪元素after/before来伪装上下border。

.fake-border-top-bottom (@color: #CCC, @width: 1px) {
    position: relative;
    &:before,
    &:after {
        position: absolute;
        z-index: 1;
        content: '';
        width: 100%;
        height: @width;
        background-color: @color;
    }
    &:before {
        top: 0;
        left: 0;
    }
    &:after {
        bottom: 0;
        left: 0;
    }
}

可惜这种方法对img这样没有after/before伪元素的标签不起作用,对于pre、table这样的标签也有些小bug。当然对于固定高度的元素还可以用box-sizing限制死高度。

最后你还会遇到浏览器对于em单位计算不精确导致1像素的偏差。我本来也打算基于em来写entry.css,结果总是遇到chrome浏览器在处理计算时的bug。后来干脆换成了px,当然也损失了对于缩放情况的优化。

外部资源

更多会遭遇的麻烦是外部资源的高度问题。比如你的文章中可以引入图片,恰巧你又不知道高度的确定值,那么很可能图片会打破旋律。对此没有什么特别好的办法,使用js是我能想到的唯一方法。于是基于baseline.js库来设置外部资源的高度,如下:

// 这是standalone版本的baseline.js
baseline.init('.entry img, .entry iframe', 28);

其实在我写这篇文章的时候这个库有不少bug,用之前先看下github上别人的pull request。

缩放因子

最后还有个问题,真的仅仅凭借p {line-height: 28px;}一句就可以确保p的行高是28px吗?看这个例子:

p标签中有一个small标签,这时p标签的整体高度是57px,不是行高56px(28px*2)的。这是因为small继承得到行高为28px,然后small与匿名文本一起按照baseline摆放。行高最终是通过一行之上的最高边界与最低边界确定的。而small的文字比匿名文本小,于是计算就可以知道行高就会有可能超过28px。理论上可以计算:(28-14)/2 + 14 + (28-10)/2 = 30,但在safari上实际得到的结果确是29px。对此还没搞明白为什么。后来找到的解决方案是使用“缩放因子”而不是绝对数值,即line-height:2

当然如果有行内元素的行内块高度超过基础数值也会打破旋律。对于这种情况我还没有比较好的解决方案。

样式的优化

Entry.css也考虑到了针对中文阅读做些特殊优化,比如下划线样式。众所周知,下划线有个很严重的问题是:使用某些字体时,下划线会和文字粘在一起。例如中文的“十”字和下划线粘连的时候就会造成“十”和“士”两字难以区分。Entry.css使用了border-bottom来模拟下划线样式。除此之外,对于相邻的两个下划线样式还会设置一些间隔,避免下划线粘连。

如果文字和下划线的颜色一样,人的视觉误差会造成错觉:让人感觉下划线的颜色更深一些。于是Entry.css使用了less css中的lighten方法,降低了下划线的颜色。

对于中文缩近,并没有采用text-indent来实现,因为其默认继承的特性并不是我所期望的。所以采用了如下方法:

.start-with2word () {
    // text-indent: 2em; 避免继承,故不用
    &:before {
        content: '  ';
        display: inline;
    }
}

使用了伪元素before实现了两个中文字的缩近。

Entry.css还提供了“书名号、缩写、着重符、旁注、上下标等等”这样的特殊样式。可以参考下Entry.css的文档

大小与适应性

以前我在写样式的时候觉得一行之上显示的文字应该尽量多,后来发现一行之上的文字太多反而会影响到自己阅读的耐心,让自己的眼睛变的很累。于是我开始思考一行放多少字才算合适。后来我通过纸质书籍找到了一个合适的数值:40。大概统计了身边十本书籍之后,发现一行之上的中文大概都在40字左右。

在写Entry.css之前就已经设计好了它要支持小屏幕,于是在限制宽度的时候都使用了max-width,而不是width。最大宽度设置成了@font-size * 42,在左右各留一个字大小的空间。

对于基础文字大小,我设置成了14px。主要是综合了各种默认字体在各个系统中的样子,觉得14px还算比较均衡的一个数值,再大的话可能会导致在使用特殊字体时变得特别难看。当然你也可以使用typekit、justfont、Typesquare这样的服务,使用在线字体。

自定义

对于这点不做过多说明了。因为Entry.css是基于less写的,所以使用了less提供的变量功能实现了自定义配置功能。Entry.css提供了基础的左、中、右三种布局方式。又因为对于配色这块不太了解,所以移除了最初默认提供的标题背景配色。期待大家可以提供很棒的配色~

最后如果你有什么喜欢的样式,或是觉得需要的功能,更或是发现了bug,请提交issue给我。Feel free to use it~