字体排印(1)

连字符断行

难题

设计师迷恋文本的两端对齐效果。看一眼杂志和书籍中的精美排版, 就会发现这种效果无处不在。不过在网页中,两端对齐却极少使用,而且越是有经验的设计师就越少使用。从 CSS 1 开始就已经有 text-align: justify; 了,为什么还会形成这个局面呢?

只要看一眼下图,其中的原因就会立刻浮出水面。在对文本进行两端对齐处理时,需要调整单词的间距,此时会出现“单词孤岛”现象。这个结果不仅看起来很糟糕,而且损伤了可读性。在打印媒介中,两端对齐总是与连字符断行相辅相成的。因为连字符允许单词在音节分界处断开并折行,所以在处理对齐时所需要调整的间距就少得多了,文本看起来也自然很多。
CSS 两端对齐的默认效果

以前,有一些在网页上实现连字符断行的方法,但这类方法完全是“伤 敌八百,自损一千”。常见的方法包括服务器端预处理、JavaScript 后期处理、用在线生成器单独处理,甚至还有开发者耐着性子在单词中纯手工插入软连字符(­),以便浏览器可以在正确的地方断开单词。一般来说,这种额外成本很不划算,因此设计师往往会改用其他的文本对齐方式。

解决方案

CSS 文本(第三版)引入了一个新的属性 hyphens。它接受三个值:none、manual 和 auto。manual 是它的初始值,其行为正好对应了现有的工作方式:我们可以在任何时候手工插入软连字符,来实现断词折行的效果。很显然 hyphens: none; 会禁用这种行为;而最为神奇的是,只需这短短一行 CSS 就可以产生我们梦寐以求的效果:

 hyphens: auto;

仅此一行足矣。你可以在下图 中看到它的效果。当然,为了确保它奏效,你需要在 HTML 标签的 lang 属性中指定合适的语言。其实不管怎样,这本来就是你早该做好的份内之事。
使用了 hyphens: auto 的结果

如果需要更细粒度地控制连字符的行为(比如在简短的引文中),你仍然可以通过一些软连字符(­)来辅助浏览器进行断词。这个 hyphens属性会优先处理它们,然后再去计算其他可以断词的地方。

CSS 连字符可以非常平稳地退化。如果 hyphens 属性不被支持,得到的文本对齐效果就是图 5-1 的程度。这个效果确实算不上好看,也算不上特别易读,但它的可访问性还是完美可靠的。

插入换行

一个定义列表,每行都是一个名值对
难题

通过 CSS 来插入换行的需求通常与定义列表(参见上图图)有关,但有时也涉及其他场景。在通常情况下,采用定义列表是因为我们立志在互联网上以身作则,坚持使用合适的标签、合理的语义——哪怕在视觉上所要呈现的只是一行行的名值对,我们也会认真对待。举例来说,考虑下面这段结构代码:

<dl>
 	<dt>Name:</dt>
 	<dd>Lea Verou</dd>
 	<dt>Email:</dt>
 	<dd>lea@verou.me</dd>
 	<dt>Location:</dt>
 	<dd>Earth</dd>
</dl>

我们所期望的视觉效果有时就是图 5-3 那样的简单样式。第一步通常是给它添加一些基本的 CSS:

dd {
 	margin: 0;
 	font-weight: bold; 
}

不过,由于这些 <dt><dd> 都是块级元素,我们最终得到的往往是图 5-4 这样的结果,所有的名和值均独占一行。我们接下来可能会给这些 <dt><dd> 元素(或两者)指定其他的 display 属性值——人们走投无路时往往会胡乱尝试。不过这样一来,我们得到的结果通常如下图所示。
这个定义列表的默认样式
在我们把头发揪光、咒骂 CSS 或者干脆放弃结构与样式分离转而修改结构之前,有没有办法可以同时保全我们的神智和(技术上的)操守?

解决方案
基本上,我们需要做的只是在每个 <dd> 后面添加一个换行。如果不在乎使用表现型的结构标记,可以请出老套的 <br> 元素,比如这样:

<dt>Name:</dt>
<dd>Lea Verou<br /></dd>

然后,对所有的 <dt><dd> 元素应用 display:inline; 样式,基本上就大功告成了。当然,这种方法不仅在可维护性方面是一种糟糕的实践,而且污染了结构层的代码。只要能使用生成性内容来添加换行,并以此取代<br> 元素,那么问题就可以解决了!但这好像做不到,对吧?又或者,这其实可行的?

实际上,有一个 Unicode 字符是专门代表换行符的:0x000A 。在 CSS 中,这个字符可以写作 “\000A”,或简化为 “\A”。我们可以用它来作为 ::after伪元素的内容,并将其添加到每个 <dd> 元素的尾部,代码如下所示:

dd::after {
 	content: "\A"; 
}

display:inline 在这里会帮倒忙

这段代码看起来是可以奏效的,但如果我们亲手试一试,就会发现结果令人失望:跟上图相比没有任何变化。不过,这并不表示我们的思路不对,只是表示我们还忽略了什么。这段 CSS 代码所做的其实只相当于在HTML 结构中的所有关闭标签 </dd> 之前添加换行符。还记得在 HTML 代码中输入换行符会发生什么吗?默认情况下,这些换行符会与相邻的其他空白符进行合并。空白符合并通常是一件非常好的事情,否则我们就得把整个HTML 文档的源代码整理进一行里面!不过,有时候我们希望保留源代码中的这些空白符和换行,代码块就是最典型的例子。还记得我们在这种场景下通常会怎么做吗?我们会用到 white-space: pre;。这里也可以这么做,但只对伪元素生成的换行符设置这个样式。

我们只有一个换行符,并不用担心有其他空白符被保留下来(因为这里根本就没有),因此任何 pre 值都可以起作用(pre、pre-line 或 pre-wrap)。我推荐 pre,因为它的浏览器支持程度最好。把这些思路整理成代码:

dt, dd { display: inline; }
dd {
 	margin: 0;
 	font-weight: bold; 
}
dd::after {
 	content: "\A";
	white-space: pre; 
}

如果你亲手测试一下,就会发现这个办法真的效,它的渲染结果与第三个图 一模一样!不过,这种方法足够健壮吗?假设我们要给定义列表中的这位用户添加第二个邮箱:

...
<dt>Email:</dt>
<dd>lea@verou.me</dd>
<dd>leaverou@mit.edu</dd>
...

当遇到多个  时,我们的解决方案就不灵了
结果如上图所示,有些莫名其妙。由于我们在每个 <dd> 的后面都加了一个换行符,每个值都会被分到单独一行中,甚至在不需要换行的时候也是如此。如果多个并列的值以逗号分隔并且排在同一行中(假设容器的宽度足够),就会好得多了。

在理想情况下,我们只想针对 <dt> 之前的最后一个 <dd> 来插入换行,而不是对所有的 <dd> 都这样做。不过,这对于当前 CSS 选择符的功能来说还是不可能的,因为选择符无法做到先在 DOM 树中选中主体元素,再倒回去查询它之前的元素。我们需要换种方式来思考。一个想法就是换行符不用加在 <dd> 的后面,而是加在 <dt> 的前面:

dt::before {
 	content: '\A';
 	white-space: pre; 
}

这会导致第一行变为空行,因为选择符对第一个 <dt> 也是生效的。为了规避这个问题,可以尝试使用以下这些选择符来替代单纯的 dt:

☑ dt:not(:first-child)
☑ dt ~ dt
☑ dd + dt

我们将采用最后一种方案,因为即使是在多个 <dt> 共用同一个值的场景下,它也是可以正常工作的;而另外两者在这种情况下还是会出问题。有些时候,我们可能还是需要把多个 <dd> 显式分隔开,除非我们觉得多个值以空格作为分隔是可以接受的(这种方式在某些时候表现良好,但有时则不一定)。在理想情况下,我们希望能够告诉浏览器“只在后面还跟着一个 <dd><dd> 尾部插入逗号”,但我们又一次遇上了那个限制,眼下的CSS 选择符还表达不出这种需求。因此,我们再次调整思路,在每个前面有<dd><dd> 头部插入逗号。最终 CSS 代码会变成(可以在下图 中看到代码的效果):

dd + dt::before {
 	content: '\A';
 	white-space: pre; 
}
dd + dd::before {
 	content: ', ';
 	font-weight: normal; 
}

最终效果
千万要记住,如果你的结构代码在多个连续的 <dd> 之间包含了(未加注释的)空白符,那么逗号前面会有一个空格。有很多方法可以修复这个问题,但都不够完美。其中一种方法是利用负外边距:

dd + dd::before {
 	content: ', ';
 	margin-left: -0.25em;
 	font-weight: normal; 
}

这个方法可行,但不够可靠。如果你的内容是以不一样的字体和尺寸来显示的,这个空隙的宽度就不一定刚好是 0.25em。在这种情况下,结果看起来就不那么理想了。不过对绝大多数字体来说,这种误差基本上是可以忽略的。

文本行的斑马条纹

抛开以前那种给每一行套元素再加背景的做法,我们换一种思路来重新考虑这个问题。为什么不对整个元素设置统一的背景图像,一次性加上所有的斑马条纹呢?乍听起来这好像是个糟糕的点子,但别忘了,我们可以在CSS 中用渐变直接生成背景图像,而且可以用 em 单位来设定背景尺寸,这样背景就可以自动适应 font-size 的变化了。

让我们用这个方法给下方这段代码加上斑马条纹:

while(true){
	var d = new Date();
	if(d.getDate()==1 && d.getMonth==3){
		alert("TROLOLOL");
	}
}

一小段代码,没有斑马条纹样式,只有一片朴素的实色背景

首先,我们需要运用“条纹背景”一节中所描述的方法,创建出水平条纹背景。它的background-size 需要设置为 line-height 的两倍,因为每个背景贴片需要覆盖两行代码。我们最初尝试写出的代码可能是这样的:

padding: 0.5em;
line-height: 1.5;
background: beige;
background-image: linear-gradient(
 				  rgba(0,0,0,.2) 50%, transparent 0);
background-size: auto 3em;

我们在尝试给代码段加上斑马条纹时走出的第一步

如上图所示,这个结果跟我们的预期已经相当接近了。我们甚至可以试着改变字号,条纹也会跟着放大或缩小!不过,有一个严肃的小问题不可忽视:代码行和条纹是错位的,破坏了整体效果。这是怎么回事?

如果近距离地观察上图,你可能就会发现,第一条条纹是从容器的最顶部开始的,这是背景图像最平常的表现。不过,我们的代码并不是从那里开始的,因为那样排版会显得很局促。如你所见,我们对容器应用了0.5em 的内边距,这个距离正是这些条纹与理想位置之间的偏差。

有一个办法可以解决这个问题,那就是用 background-position 把向条纹向底部移动 .5em。不过,如果我们以后决定调整内边距,还需要相应地修改背景定位值,这显然不够 DRY。可以让背景自动跟着内边距的宽度走吗?

让我们回顾一下“灵活的背景定位”中提到的 background-origin。这个属性正是我们所需要的:它可以告诉浏览器在解析 background-position时以 content box 的外沿作为基准,而不是默认的 padding box 外沿。现在把这一点也加入代码中:

padding: 0.5em;
line-height: 1.5;
background: beige;
background-size: auto 3em;
background-origin: content-box;
background-image: linear-gradient(rgba(0,0,0,.2) 50%,
				  transparent 0);

在下图中可以看到,这段样式正好可以达成我们想要的斑马条纹效果!因为我们是用半透明色来生成条纹的,所以在改变背景色时,斑马条纹仍然可以正常显示。这个方法总体来说是十分灵活的,唯一可能破坏效果的情况可能就是在改变 line-height 时忘了相应地调整 background-size。
最终效果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值