深入剖析 CSS:字体度量、行高和垂直对齐


Line-height 并且是简单的 CSS 属性。如此简单,以至于我们大多数人都相信要完全理解它们的工作原理以及如何使用它们。但事实并非如此。它们真的很复杂,也许是最难的,因为它们在创建CSS鲜为人知的功能之一(内联格式设置上下文)方面发挥着重要作用。 vertical-align

例如,可以设置为长度或无单位值 line-height 1,但默认值为 。好吧,但什么是正常的?我们经常读到它是(或应该是)1,或者可能是1.2,甚至是 normal CSS规范在这一点上尚不清楚.我们知道无单元是相对的,但问题是它的行为在字体系列之间是不同的,所以总是相同还是不同?它真的介于 1 和 1.2 之间吗?以及,它有什么影响? line-height font-size font-size: 100px line-height vertical-align line-height

深入了解一个不那么简单的CSS机制...

我们先来谈谈 font-size 

看看这个简单的HTML代码,一个包含3的标签,每个标签都有不同的: <p> <span> font-family

<p><spanclass="a">Ba</span><spanclass="b">Ba</span><spanclass="c">Ba</span></p>

p{font-size:100px}.a{font-family:Helvetica}.b{font-family:Gruppo}.c{font-family:Catamaran}

对不同的字体系列使用相同的字体可生成具有不同高度的元素: font-size

不同的字体系列,相同的字体大小,给出不同的高度即使我们知道这种行为,为什么不创建高度为100px的元素呢?我测量并发现了这些值:Helvetica:115px,Gruppo:97px和Catamaran:164px font-size: 100px

字体大小的元素:100px 的高度从 97px 到 164px 不等虽然乍一看似乎有点奇怪,但完全可以预料到。原因在于字体本身。以下是它的工作原理:

字体定义其全开-方形(或 UPM,单位单位/em),一种将绘制每个字符的容器。此正方形使用相对单位,通常设置为 1000 个单位。但它也可以是1024,2048或其他任何东西。

根据其相对单位,设置字体的度量(升序,降序,大写高度,x高度等)。请注意,某些值可能会在全角方块之外渗出。

在浏览器中,相对单位会缩放以适合所需的字体大小。

让我们采用双体船字体并将其打开FontForge获取指标:

全角方块为 1000

上升器为 1100,下降器为 540。运行一些测试后,浏览器似乎在Mac OS上使用HHead上升/下降值,在Windows上使用Win上升/下降值(这些值可能会有所不同!我们还注意到,资本高度为 680,X 高度为 485。

使用 FontForge 的字体指标值这意味着双体船字体在1000个单位的em-square中使用1100 + 540个单位,这在设置时提供164px的高度。这个计算的高度定义了元素的内容区域,我将在本文的其余部分参考这个术语。您可以将内容区域视为应用属性的区域 font-size: 100px background 2.

我们还可以预测大写字母高 68px(680 个单位),小写字母(x 高度)高 49px(485 个单位)。因此,= 49px 和 = 100px,而不是 164px(谢天谢地,它基于 ,而不是计算高度) 1ex 1em em font-size

双体船字体:UPM —单位单位-单位-Em—使用字体大小等效像素:100px在深入探讨之前,请简要说明它涉及的内容。当元素在屏幕上呈现时,它可以根据其宽度由多行组成。每行由一个或多个内联元素(HTML 标记或文本内容的匿名内联元素)组成,称为行框行框的高度基于其子级的高度。因此,浏览器会计算每个内联元素的高度,从而计算行框的高度(从子元素的最高点到子元素的最低点)。因此,行框始终足够高,可以包含其所有子级(默认情况下)。 <p>

每个 HTML 元素实际上是一堆行框。如果您知道每个行框的高度,则知道元素的高度。

如果我们像这样更新以前的HTML代码:

<p>Good design will be better.<spanclass="a">Ba</span><spanclass="b">Ba</span><spanclass="c">Ba</span>We get to make a consequence.</p>

它将生成 3个行框

第一个和最后一个都包含一个匿名内联元素(文本内容)

第二个包含两个匿名内联元素,3个 <span>

(黑色边框)由包含内联元素(实线边框)和匿名内联元素(虚线边框)的线框(白色边框)组成 <p> 我们清楚地看到,由于第二个行框的子级计算内容区域,更具体地说,使用双体船字体的行框比其他行框高。

行框创建中的困难之处在于,我们无法真正看到,也无法用CSS控制它。即使将背景应用于 也不会给我们任何关于第一行框高度的视觉线索。 ::first-line

line-height :对问题及其他

到目前为止,我引入了两个概念:内容区域行框。如果你读得很好,我告诉过一个行框的高度是根据它的孩子的高度来计算的,我没有说它的孩子内容区域的高度。这会产生很大的不同。

尽管这听起来很奇怪,但内联元素有两种不同的高度:内容区域高度和虚拟区域高度(我发明了术语“虚拟区域”,因为高度对我们来说是不可见的,但你不会在规范中找到任何内容)。

内容区域高度由字体指标定义(如前所述)

虚拟区域高度是 行高 ,它是用于计算行框高度的高度

内联元素具有两种不同的高度话虽如此,它打破了基线之间距离的普遍看法。在 CSS 中,它不是 line-height 3.

在 CSS 中,行高不是基线之间的距离虚拟区域内容区域之间计算的高度差称为前导。此行距的一半添加在内容区域的顶部,另一半添加在底部。因此,内容区域始终位于虚拟区域的中间

根据其计算值,(虚拟区域)可以等于、高或小于内容区域。对于较小的虚拟区域,行距为负数,行框在视觉上小于其子区域。 line-height

还有其他类型的内联元素:

替换了内联元素(、、等) <img> <input> <svg>

inline-block 和所有元素 inline-*

参与特定格式设置上下文的内联元素(例如,在 flexbox 元素中,所有 flex 项都被块化))

对于这些特定的内联元素,高度是根据其 和属性计算的。如果 是 ,则使用,并且内容区域严格等于 。 height margin border height auto line-height line-height

内联替换元素、内联块/内联 和块化内联元素的内容区域等于其高度或行高无论如何,我们仍然面临的问题是 的价值是多少?至于内容区域*高度的计算,答案可以在字体指标中找到。 line-height normal

因此,让我们回到FontForge。双体船的em-square是1000,但我们看到许多上升/下降值:

将军上升/下降:上升者为770,下降者为230。用于字符绘制。(表“OS/2”)

指标上升/下降:上升者为 1100,下降者为 540。用于内容区域的高度。(表“hhea”和表“OS/2”)

公制线距。用于 ,通过将此值添加到上升/下降指标。(表“嗯” line-height: normal )

在我们的例子中,双体船字体定义了一个0单位的行间距,所以 行高:正线 将等于内容区域,即1640个单位,即1.64

作为比较,Arial字体描述了2048个单位的em-square,1854年的上升,434的下降和67的行间距。这意味着内容区域为112px(1117个单位)和115px(1150个单位或1.15)。所有这些指标都是特定于字体的,并由字体设计人员设置。 font-size: 100px line-height: normal

很明显,设置 行高:1 是一种不好的做法。我提醒你,无单位值是相对的,而不是内容区域相对的,处理小于内容区域虚拟区域是我们许多问题的根源。 font-size

使用行高:1 可以创建小于内容区域的行框但不仅如此。值得一提的是,在我计算机上安装的1117字体上(是的, line-height: 1 我安装了所有来自谷歌网页字体的字体),1059 种字体(约 95%)的计算值大于 1。他们的计算从0.618到3.378。你已经读得很好了,3.378! line-height line-height

关于行框计算的小细节:

对于内联元素,并增加背景区域,但不增加内容区域的高度(也不增加行框的高度)。因此,内容区域并不总是您在屏幕上看到的内容。 并且没有效果。 padding border margin-top margin-bottom

对于替换的内联元素和块化的内联元素:,并增加 ,因此内容区域行框的高度 inline-block padding margin border height

vertical-align :一个属性来统治它们

我还没有提到该属性,尽管它是计算行框高度的重要因素。我们甚至可以说 垂直对齐 可能在内联格式上下文中起主导作用。 vertical-align

缺省值为 。是否提醒字体指标的升序和降序?这些值决定了基线的位置,以及比率。由于上升者和下降者之间的比例很少是50/50,因此可能会产生意想不到的结果,例如兄弟姐妹元素。 baseline

从该代码开始:

<p><span>Ba</span><span>Ba</span></p>

p{font-family:Catamaran;font-size:100px;line-height:200px;}

一个具有 2 个兄弟姐妹继承的标记,并已修复 。基线将匹配,并且行框的高度等于其 。 <p> <span> font-family font-size line-height line-height

相同的字体值,相同的基线,一切似乎都可以如果第二个元素的尺寸较小怎么办? font-size

span:last-child{font-size:50px;}

尽管听起来很奇怪,但默认的基线对齐方式可能会导致更高的 (!)行框,如下图所示。我提醒你,一个线框的高度是从孩子的最高点到孩子的最低点计算的。

较小的子元素可能会导致行框的高度较高这可能是支持使用无单位值的论据line-height,但有时你需要固定的创造完美的垂直节奏.说实话,无论你选择什么,你总是会遇到内联对齐的麻烦

再看这个例子。带有 的标记,包含单个继承 <p> line-height: 200px <span> line-height

<p><span>Ba</span></p>

p{line-height:200px;}span{font-family:Catamaran;font-size:100px;}

线盒有多高?我们应该期待200px,但这不是我们得到的。这里的问题是有其自身的不同之处(默认为 )。标记和之间的基线可能不同,因此行框的高度高于预期。发生这种情况是因为浏览器进行计算,就好像每个行框都以零宽度字符开头一样,该规范称为支柱。 <p> font-family serif <p> <span>

一个看不见的角色,但一个看得见的影响。

要继续,我们面临着与兄弟姐妹元素相同的先前问题。

每个子项都对齐,就好像其行框以不可见的零宽度字符开头一样基线对齐被搞砸了,但是为了救援呢?正如您在规范中读到的那样,“将框的垂直中点与父框的基线加上父框的 x 高度的一半对齐”。基线比率以及x高度比率不同,因此 中间 对齐也不可靠。在大多数情况下,最糟糕的情况从来都不是真正的“中间”。涉及的因素太多,无法通过CSS设置(x高度,上升/下降比率等) vertical-align: middle middle middle

顺便说一句,还有其他4个值,在某些情况下可能很有用:

vertical-align: top / bottom 与线框的顶部或底部对齐

vertical-align: text-top / text-bottom 与内容区的顶部或底部对齐

垂直对齐:顶部、底部、文本顶部和文本底部但要小心,在所有情况下,它都会对齐虚拟区域,因此不可见的高度。看看这个使用 的简单示例。不可见 的行高 可能会产生奇怪但不令人惊讶的结果。 vertical-align: top

垂直对齐最初可能会产生奇怪的结果,但在可视化行高时是预期的最后,还接受相对于基线提高或降低框的数值。最后一种选择可能会派上用场。 vertical-align

CSS很棒

我们已经讨论了如何一起工作,但现在的问题是:字体指标是否可以用CSS控制?简短的回答:不。即使我真的希望如此。无论如何,我认为我们必须玩一点。字体指标是恒定的,所以我们应该能够做一些事情。 line-height vertical-align

例如,如果我们想要一个使用双体船字体的文本,其中大写高度正好是100px高,该怎么办?似乎可行:让我们做一些数学运算。

首先,我们将所有字体指标设置为 CSS 自定义属性4,然后进行计算以获得 100px 的大小写高度。 font-size

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);}

大写高度现在为100px高很简单,不是吗?但是,如果我们希望文本在视觉上位于中间,以便剩余空间均匀地分布在“B”字母的顶部和底部,该怎么办?为了实现这一点,我们必须根据上升/下降比率进行计算。 vertical-align

首先,计算和内容区域的高度: line-height: normal

p{…--lineheightNormal:(var(--fm-ascender)+var(--fm-descender)+var(--fm-linegap));--contentArea:(var(--lineheightNormal)*var(--computedFontSize));}

然后,我们需要:

从大写字母底部到底部边缘的距离

从大写字母顶部到顶部边缘的距离

这样:

p{…--distanceBottom:(var(--fm-descender));--distanceTop:(var(--fm-ascender)-var(--fm-capitalHeight));}

我们现在可以计算,即距离乘以计算的之间的差值。(我们必须将此值应用于内联子元素) vertical-align font-size

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”匹配的图标变得容易:

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;}

图标和 B 字母的高度相同在 JSBin 中查看结果

请注意,此测试仅用于演示目的。你不能依赖这个。原因有很多:

除非字体度量是恒定的,浏览器中的计算不是̄*(ツ)*/ ̄

如果未加载字体,则回退字体可能具有不同的字体指标,并且处理多个值将很快变得非常难以管理

要点

我们学到了什么:

内联格式上下文真的很难理解

所有内联元素都有 2 个高度:

内容区域(基于字体指标)

虚拟区域( line-height )

毫无疑问,这两个高度都无法想象。(如果你是一个devtools开发人员,并且想从事这项工作,那可能会很棒)

line-height: normal 基于字体指标

line-height: n 可以创建一个小于*内容区域的*虚拟区域

vertical-align 不是很可靠

行框的高度是根据其子级和属性计算的 line-height vertical-align

我们无法使用CSS轻松获取/设置字体指标

但我仍然喜欢CSS:)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,585评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,283评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,760评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,461评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,280评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,268评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,656评论 3 385
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,322评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,629评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,691评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,445评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,299评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,694评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,982评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,244评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,642评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,829评论 2 335