本文是我在众成翻译上认领并翻译的:用CSS实现基线韵律
用CSS实现基线韵律
垂直韵律是一个经常被前端工程师们误解的排版概念
设计师们通过将内容排列在垂直网格中使作品看上去整洁和谐。如果前端工程师们能够准确地实现相同的垂直韵律,就能更加轻松快速地开发出协调一致的美观页面。而这一过程无需设计师的参与。
本文将帮助你建立在CSS中实现合适的垂直韵律的基础。首先我得澄清一下哪些问题不打算在此讨论。
这个概念已经存在很多年了。它引入一个应用于所有元素的通用line height值(或其倍数),包括内外边距,偶尔也将border的宽度纳入计算范围。
此时,根据CSS标准,页面内容会被排列在两条水平网格线的正中间。但两个不同格式的元素相邻排列时会显得很不和谐。
有一种方法能让不同字体格式的内容产生具有协调性的排版结果。那就是设定文本的基线。使用这种方法,能让所有的内容——不论其大小如何——都以同一条网格线为基准对齐。
CSS没有为此提供任何便捷的工具,因此我们必须手动做一些微调。这里有两点需要想清楚:
- 内容需要被移动多少;
- 如何高效率地移动所需的距离;
确定偏移量
Razvan Onofrei写了一篇很棒的文章来解释这一部分。
简而言之,一个大写字母位于基线以上的高度叫做cap height(也就是浏览器会自动在网格线之间居中的高度)。我们要做的就是将它移动line height与cap height之差的距离。
cap height是当前使用字体的一个属性。我们可以通过尝试和微调不同的值来确定它,直到文字内容与网格线对齐。
本站的stylesheets就是基于这种思想,摘录一段如下所示:
$line-height: 24px;
$font-stacks: (
s: $font-stack-text,
m: $font-stack-text,
l: $font-stack-display,
xl: $font-stack-display
);
$font-sizes: (s: 13px, m: 15px, l: 19px, xl: 27px);
$cap-heights: (s: 0.8, m: 0.8, l: 0.68, xl: 0.68);
// Accepts `s`, `m`, `l`, or `xl`
@function rhythm-shift($size-name) {
$font-size: map-get($font-sizes, $size-name);
$cap-height: map-get($cap-heights, $size-name);
$offset: ($line-height - $cap-height * $font-size) / 2;
@return round($offset);
}
应用偏移量
知道文字应该偏移多少之后,我们就需要决定如何可靠地应用它了。回顾一下,我们有几种方案可以实现。
方案1. 利用相对定位
使用top
属性来移动内容而不影响上下文。
$offset: rhythm-shift(m);
.rhythm-m {
position: relative;
top: $offset;
}
查看 示例。
这也许是最简单的一个选项,但用这种方法你至少会遇到两个问题:
position:
relative
会影响元素的堆叠顺序。如果两个元素重叠,设置了相对定位的元素会显示在顶层。有时候这会引起预期之外的z-index
手动微调;position
可能被其他需求使用,例如绝对定位的内容;
在代码库相对小而简单的时候这种方案当然够用。但当我们的APP架构变得更加复杂,需要更强的可扩展性时,我们还是抛弃了这种方案。
方案 2. 使用正的 top padding 和负的 bottom margin
此方案是之前提到的那篇文章中推荐的:
$offset: rhythm-shift(m);
.rhythm-m {
padding-top: $offset;
margin-bottom: -1 * $offset;
}
查看 示例.
top padding将内容按需要向下偏移,负的bottom margin则用来补偿这种偏移。要注意只使用一个方向的margin,比如bottom margin。否则margin折叠将破坏整个布局系统。
此方案最大的缺点就是会迅速增加代码复杂性。以我们的APP为例,公共样式类pt-1
添加了24px的top padding,pt-2
则是48px(两倍行高),诸如此类。如果把这些类一起使用,要么得增加额外的HTML容器,要么就会为了生成所有可能的案例而过度使用Sass特性。无论哪种情况都会使代码变得笨重,将来也无法轻松地迭代升级。
方案 3. 使用正的 top margin 和负的 bottom margin
这是我们最终采取的方案:
$offset: rhythm-shift(m);
.rhythm-m {
margin-top: $offset;
margin-bottom: -1 * $offset;
}
查看 示例.
跟前一个方案一样,顶部偏移——这次是margin——由负的bottom margin补偿。
margin折叠怎么办?
好在我们有一个十分整洁的窍门可以解决这个问题。但首先,我们来回顾一下在有正负margin的情况下折叠的规则:
- 对于两个正的margin,大值获胜。例如
margin-bottom:
30px
和margin-top:
20px
,最后元素之间的空白是30px; - 对于两个负的margin,同样,小值(意味着负的更多)获胜;
- 对于一正一负两个margin,它们会相加。例如
margin-bottom:
30px
和margin-top:
-20px
,结果是10px的空白;
根据最后一条规则,如果我们总是让正负margin交替出现,它们的值就会相互抵消,文本内容则会保持与网格线对齐。
为了保证这种效果,我们决定在APP中除韵律系统以外的地方一律不使用margin。尽管这么做损失并不大,但意外出现的margin并不总是可预测的。
解决 margin 泄露
CSS中的margin还有一个恶心的特性:如果一个元素没有设置border或padding,而它的第一个子元素又有margin,那么这个margin就会泄露到父元素外面。当父元素设置了background的时候这会造成问题。这个background会覆盖子元素所在区域,而不是父元素内部。
有两种方法可以解决此问题。
- 要么使用
overflow:
hidden
将margin强制限定在父元素内部; - 要么添加
padding-top:
0.1px
, 这是一个微小值hack。这个值太小了,以至于浏览器无法实际渲染,但它足够使子元素保持在父元素的容器边界之内;
查看 最终示例.
到这里一切就大功告成了。我们已经成功地在自己的APP上使用这个系统好几月了。
尽管CSS没有提供一个能够立即使用的此类系统,但在我们正确地搭建了基础框架之后基线韵律是完全可以实现的。