ホームページ > 記事 > ウェブフロントエンド > CSS の line-height とvertical-align の使用法についての深い理解
line-height とvertical-align は両方とも単純な CSS プロパティなので、ほとんどの人がその仕組みを知っていると思っています。しかし、実際には、これら 2 つのプロパティは非常に複雑で、CSS で最も難しい 2 つのプロパティとみなされます。これらの 2 つのプロパティは、CSS のあまり知られていない機能であるインライン フォーマット コンテキスト (IFC) と密接に関連しているためです。この記事では、この 2 つのプロパティについて説明します。 CSS での line-height とvertical-align の使用法に焦点を当てて説明したいと思います。お役に立てれば幸いです。
たとえば、line-height の値は長さ (長さ) または数値にすることができ、そのデフォルト値は Normal です。それで、正常とは何ですか?私たちは通常を 1 または 1.2 として理解することがよくありますが、CSS 仕様ドキュメントでさえこの問題については言及していません。 line-height の値が数値である場合、それは font-size の倍数を表すことはわかっていますが、問題は、font-size:100px に対応するテキストの高さがフォントによって異なることです。では、文字サイズが変わると行の高さも変わるのでしょうか?正常というのは本当に 1 または 1.2 を意味するのでしょうか?垂直方向の配置は行の高さによってどのように影響を受けますか?
それほど単純ではない CSS メカニズムを詳しく見てみましょう。
以下は単純な HTML コードです。p タグには 3 つのスパンタグが含まれています。各スパンにはフォントファミリーがあります:
<p> <span class="a">Ba</span> <span class="b">Ba</span> <span class="c">Ba</span> </p>
p { font-size: 100px } .a { font-family: Helvetica } .b { font-family: Gruppo } .c { font-family: Catamaran }
(翻訳者注: これらのフォントはあなたのコンピュータ上に存在しない可能性があります)。
フォントサイズは同じで、フォントファミリーが異なり、取得されたspan要素の高さも異なります:
なぜfont-size: 100pxが同じ高さの要素を取得できないのか?各スパンの高さを測定しました: Helvetica 115px、Gruppo 97px、Camaran 164px。
一見奇妙に思えますが、よく考えてみると当然のことです。その理由はフォント自体にあり、これがフォントの仕組みです。
フォントは、文字を保持するために使用される金属製の容器である em-square を定義します。この em-square は通常、幅と高さが 1000 相対単位に設定されますが、1024 または 2048 相対単位にすることもできます。
フォントの寸法は、アセンダー、ディセンダー、大文字の高さ、X 高さなどを含む、この相対単位に基づいて設定されます。ここでの値は、必要なフォント サイズに合わせて 1000 相対単位で外側にはみ出すことが許可されていることに注意してください。
私たちは Catamaran フォントを FontForge に入れて、そのフォント メトリクスを分析しました:
em-square は 1000コンテンツ領域については後ほど説明します。コンテンツ領域は、背景が機能する領域と考えることができます。
次に進む前に、関連する事実をいくつか説明します。 p 要素が画面に表示される場合、コンテンツの各行は複数のインライン要素 (テキストを含むインライン タグまたは匿名のインライン要素) で構成されます。 line-box の高さは、そのすべての子要素の高さから計算されます。ブラウザは、この行の各子要素の高さを計算し、次に行ボックスの高さ (具体的には、子要素の最高点から最低点までの高さ) を計算します。したがって、デフォルトでは、行ボックスは常にその子要素を収容するのに十分な高さを持っています。
每个 HTML 元素实际上都是由多个 line-box 的容器,如果你知道每个 line-box 的高度,那么你就知道了整个元素的高度。
如果我们修改一下最初的 HTML 代码:
<p> Good design will be better. <span class="a">Ba</span> <span class="b">Ba</span> <span class="c">Ba</span> We get to make a consequence. </p>
那么就会得到 3 个 line-box(宽度固定):
第一行和最后一行各有一个匿名内联元素(文本内容)
中间一行包含两个匿名内联元素和三个 span
我们清楚地看到第二个 line-box比其他两个要高一些。因为第二行里面的子元素因为有一个用到了 Catamaran 字体的 span。
line-box 的难点在于我们看不见它,而且不能用 CSS 控制它。即使我们用 ::first-line 给第一行加上背景色,我们也看不出第一个 line-box 的高度。
目前我已经提到了两个概念:content-area 和 line-box。如果你仔细看了,会发现我说 line-box 的高度是根据子元素的高度计算出来的,而不是子元素的 content-area 的高度。这个区别大了。
接下来说句听起来很奇怪的话:一个内联元素有两个高度:content-area 高度和 virtual-area (实际区域?)高度(virtual-area 是我自己发明的单词,它表示对人类有效的高度,你在其他地方是看不到这个单词的)。
content-area 的高度是由字体度量定义的(见上文)
vitual-area 的高度就是 line-height,这个高度用于计算 line-box 的高度
这么一来,这就打破了一个长久的谣言:line-height 表示两个 baseline 之间的距离。在 CSS 里,不是这样的。
virtual-area 和 content-area 高度的差异叫做 leading。leading 的一半会被加到 content-area 顶部,另一半会被加到底部。因此 content-area 总是处于 virtual-area 的中间。
计算出来的 line-height(也就是 virtual-area 的高度)可以等于、大于或小于 content-area。如果 virtual-area 小于 content-area,那么 leading 就是负的,因此 line-box 看起来就比内容还矮了。
还有一些其他种类的内联元素:
可替换的内联元素,如 img / input / svg 等
inline-block 元素,以及所有 display 值以 inline- 开头的元素,如 inline-table / inline-flex
处于某种特殊格式化上下文的内联元素,例如 flexbox 元素中的子元素都处于 flex formatting context(弹性格式化上下文)中,这些子元素的 display 值都是「blockified」
这类内联元素,其高度是基于 height、margin 和 border 属性(译者注:好像漏了 padding)。如果你将其 height 设置为 auto 的话,那么其高度的取值就是 line-height,其 content-area 的取值也是 line-height。
我们目前依然没有解释 line-height:normal 是什么意思。要解答这个问题,我们又得回到 content-area 高度的计算了,问题的答案就在字体度量里面。
我们回到 FontForge,Catamaran 的 em-square 高度是 1000,同时我们还看到很多其他的 ascender/descender 值:
常规的 Ascent/Descent:ascender 是 770,descender 是 230,用于渲染字符。
规格 Ascent/Descent:ascender 是 1100,descender 是 540。用于计算 content-area 的高度
规格 Line Gap:用于计算 line-height: normal。
在 Catamaran 这款字体中,Line Gap 的值是 0,那么 line-height: normal 的结果就跟 content-area 的高度一样,是 1640 相对单位。
为了对比,我们再看看 Arial 字体,它的 em-square 是 2048,ascender 是 1854,descender 是 434,line gap 是 67。那么当 font-size: 100px 时,
其 content-area 的高度就是 100/2048*(1854+434) = 111.72,约为 112px;
其 line-height: normal 的结果就是 100/2048*(67+1854+434) 约为 115px。
所有这些值都是由字体设计师设置的。
这么看来,line-height:1 就是一个很糟糕的实践。记得吗,当 line-height 的值是一个数字时,其实就是相对 font-size 的倍数,而不是相对于 content-area。所以 line-height:1 很有可能使得 virtual-area 比 content-area 矮,从而引发很多其他的问题。
不仅仅是 line-height:1 有问题,我电脑上的 1117 款字体中,大概有 1059 款字体的 line-height 比 1 大,最低的是 0.618,最高的是 3.378。你没看错,是 3.378!
line-box 计算的一些细节:
对于内联元素,padding 和 border 会增大 background 区域,但是不会增大 content-area(不是 line-box 的高度)。一般来说你无法再屏幕上看到 content-area。margin-top 和 margin-bottom 对两者都没有影响。
对于可替换内联元素(replaced inline elements)、inline-block 元素和 blockified 内联元素,padding、margin 和 border 会增大 height(译者注:注意 margin),因此会影响 content-area 和 line-box 的高度
我还没提过 vertical-align 属性,它也是计算 line-box 高度的重要因素之一。我们甚至可以说 vertical-align 是内联格式化上下文(IFC)中最重要的属性。
它的默认值是 baseline。还记得字体度量里的 ascender 和 descender 吗?这两个值决定了 baseline 的位置。很少有
字体的 ascender 和 descender 的比例是一比一的,所以我们经常看到一些意想不到的现象,下面是例子。
代码如下:
<p> <span>Ba</span> <span>Ba</span> </p>
p { font-family: Catamaran; font-size: 100px; line-height: 200px; }
一个 p 标签内有两个 span 标签,span 继承了 font-family、font-size 和 200px 的 line-height。这时两个 span 的 baseline 是等高的,line-box 的高度就是 span 的 line-height。
如果第二个 span 的 font-size 变小了呢?
span:last-child { font-size: 50px; }
我们会发现一个非常奇怪的现象,line-box 的高度变高了!如下图所示。提示你一下,line-box 的高度是从子元素的最高点到最低点的举例。
这个例子可以作为「应该将 line-height 的值写成数字」的论据,但是有时候我们为了做出好看的排版,必须把 line-height 写成一个固定值。
不过我实话告诉你吧,不管你把 line-height
写成什么,你都会在对齐内联元素的时候遇到麻烦。
我们来看另一个例子。p 标签有 line-height:200px,内含一个 span,span 继承了 p 的 line-height。
<p> <span>Ba</span> </p>
p { line-height: 200px; } span { font-family: Catamaran; font-size: 100px; }
此时 line-box 的高度是多少?貌似是 200px,但其实不是。这里你没有考虑到的问题是 p 有自己的 font-family,默认值是 serif。p 的 baseline 和 span 的 baseline 位置不一样,因此最终的 line-box 比我们预想的要高一些。出现这种问题是因为浏览器认为每个 line-box 的起始位置都有一个宽度为 0 的字符(CSS 文档将其称为 strut),并将其纳入 line-box 的高度的计算中。
看不见的字符,看得见的影响。
为了说明这个问题,我们画图解释一下这个问题。
用 baseline 来对齐令人费解,如果我们用 vertical-align: middle 会不会好一点呢?读 CSS 文档你会发现,middle 的意思是「用父元素 baseline 高度加上父元素中 x-height 的一半的高度来对齐当前元素的垂直方向的中点」。baseline 所处的高度跟字体有关,x-height 的高度也跟字体有关,所以 middle 对齐也不靠谱。更糟糕的是,一般来说,middle 根本就不是居中对齐!内联元素的对齐受太多因素影响,因此不可能用 CSS 实现。
顺便一说,vertical-align 的其他 4 个值有可能有点用:
vertical-align: top / bottom,表示与 line-box 的顶部或底部对齐
vertical-align: text-top / text-bottom,表示与 content-area 的顶部或底部对齐
不过你依然要小心,大部分情况下,对齐的是 virtual-area,也就是一个不可见的高度。看看下面这个用 vertical-align:top 的例子:
最后,vertical-align 的值也可以是数字,表示根据 baseline 升高或降低,不到万不得已还是别用数字吧。
我们讨论了 line-height 和 vertical-align 如果互相影响,现在问题来了:CSS 可以控制字体度量吗?简单来说答案是:不行。我也很想用 CSS 来控制字体。无论怎样,我还是想试试。字体度量只是一些固定的值而已,我们应该可以围绕它做点什么。
比如说,我们想要一段文字使用 Catamaran 字体,同时大写字母的高度正好是 100px,看起来可以实现,我们只需要一些数学知识。
首先我们把所有字体度量设置为 CSS 自定义属性,然后计算出一个 font-size,让大写字母的高度正好是 100px。
p { /* font metrics */ --font: Catamaran; --fm-capitalHeight: 0.68; --fm-descender: 0.54; --fm-ascender: 1.1; --fm-linegap: 0; /* desired font-size for capital height */ --capital-height: 100; /* apply font-family */ font-family: var(--font); /* compute font-size to get capital height equal desired font-size */ --computedFontSize: (var(--capital-height) / var(--fm-capitalHeight)); font-size: calc(var(--computedFontSize) * 1px); }
看起来也并不复杂不是吗?如果我们想要文字垂直居中怎么办呢?也就是让 B 上面的空间和下面的空间高度一样。为了做到这一点,我们必须要根据 ascender 和 descender 的比例来计算 vertical-align。
首先计算出 line-height:normal 的值和 content-area 的高度:
p { … --lineheightNormal: (var(--fm-ascender) + var(--fm-descender) + var(--fm-linegap)); --contentArea: (var(--lineheightNormal) * var(--computedFontSize)); }
然后我们需要计算:
B 下面空间的高度
B 上面空间的高度
像这样:
p { … --distanceBottom: (var(--fm-descender)); --distanceTop: (var(--fm-ascender) - var(--fm-capitalHeight)); }
然后我们就可以计算 vertical-align 的值。
p { … --valign: ((var(--distanceBottom) - var(--distanceTop)) * var(--computedFontSize)); } span { vertical-align: calc(var(--valign) * -1px); }
最后,设置 line-height:
p { … /* desired line-height */ --line-height: 3; line-height: calc(((var(--line-height) * var(--capital-height)) - var(--valign)) * 1px); }
添加一个和 B 一样高的 icon 就很容易了:
span::before { content: ''; display: inline-block; width: calc(1px * var(--capital-height)); height: calc(1px * var(--capital-height)); margin-right: 10px; background: url('https://cdn.pbrd.co/images/yBAKn5bbv.png'); background-size: cover; }
JSBin 效果演示
注意这只是为了演示,请不要在生产环境中使用此方案。
我们知道了:
IFC 真的很难懂
所有的内联元素都有两个高度
基于字体度量的 content-area
virtual-area(也就是 line-height )
这两个高度你都无法看到
line-height: normal 是基于字体度量计算出来的
line-height: n (n=1,2,3…) 可能得出一个比 virtual-area 还要矮的content-area
vertical-align 不靠谱
line-box 的高度的受其子元素的 line-height 和 vertical-align 的影响
我们无法轻易的用 CSS 来控制字体度量
相关推荐:
以上がCSS の line-height とvertical-align の使用法についての深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。