vertical-align刨根问底

写在前面

本文第一部分翻译自Vertical-Align: All You Need To Know,就是之前在CSS上下左右居中参考资料部分提到的待翻译的那一篇

其余部分是对原文的技巧总结

一.译文

经常需要让一些并排显示的元素竖直对齐

CSS提供了一些可选方案,有时通过float来解决,有时用position: absolute,有时甚至用手动添加marginpadding这样的脏方法,我不很喜欢这些方案。浮动只是让它们顶部对齐,而且要手动清除(浮动的影响)。绝对定位让一些元素脱离标准文档流,以至于它们无法再影响周围元素。而即使是最微小的变动也会破坏固定marginpadding

但还有另外一个角色:vertical-align。我觉得它更值得信任。虽然在技术上,用vertical-align实现布局是一种hack,因为它不是为布局设计的,而是用来对齐文本与文本旁边元素的。但是,也能用vertical-align在不同环境中灵活且细粒度(fine-grained)地对齐元素。不需要知道元素的大小,元素仍然处于标准文档流中,其它元素能响应其尺寸变化。这些优势让它成了一个有价值的选项

vertical-align的怪脾气

vertical-align有时候真的很讨厌,用起来会有些挫败感,似乎有一些神秘的规则。例如,可能会遇到,改变元素的vertical-align根本没有改变它自己的对齐方式,但同一行的其它元素(的对齐方式)却变了!现在还时不时地钻进这些阴暗的角落,让我抓狂(tearing my hair)

不幸的是,大多数相关资源都太浅显了,尤其是在我们想用vertical-align实现布局时。他们专注于试图让一个元素里面的所有东西都竖直对齐的错误想法,给出属性的基本介绍,并解释非常简单的场景下元素的对齐方式,而不解释技巧性的部分

所以,我给自己定下了一劳永逸地澄清vertical-align行为的目标,以深入W3C的CSS规范,并尝试一些例子告终,最终成果就是本文

那么,下面我们从游戏规则入手

vertical-align的依赖项

vertical-align用来对齐内联级(inline-level)元素,也就是那些display属性的计算值为:

  • inline
  • inline-block
  • inline-table(本文不考虑)

内联元素(inline elements)是基本标签包裹着的文本

内联-块元素(inline-block elements)就像它名字所说的那样:内嵌的块元素(block elements living inline)。它们可以具有widthheight(也有可能是通过其内容确定的)和paddingbordermargin

内联级元素(inline-level elements)在一行中一个挨一个地排列,一旦当前行放不下了,就在它下方创建一个新行,所有这些行都具有所谓的行盒(line box),包住这一行的所有内容。不同大小的的内容意味着不等高的行盒。下图中行盒的上下边界用红线标出来了:

A tall in a line of text.

A short in a line of text.

Thiscanhappen.

行盒就是我们的上下文(the line boxes trace out the field we are playing on),这些行盒中的vertical-align属性负责对齐各个元素。那么,元素对齐到底是怎么回事?

baseline和outer edge

竖直对齐最重要的参照点是相关元素的baseline,某些情况下,元素包裹盒的顶边和底边也很重要。我们一起看看各种类型元素的baseline和outer edge在哪里:

内联元素

aAÄ qQ

aAÄ qQ

aAÄ qQ

可以看到3行并列的文本,行高的顶边和底边用红线表示出来,字体的高度用绿线,baseline用蓝线。左边文本的行高设置为与font-size相同,绿线和红线重合了。中间文本行高是font-size的2倍。右边行高是font-size的一半

内联元素的outer edge与其行高的顶边和底边对齐,如果行高小于字体高度的话,就无所谓。所以,outer edge是上图中的红线

内联元素的baseline是字符坐在上面的那条线(baseline is the line, the characters are sitting on),即图中的蓝线。很难理解的是,baseline有时会在字体高度的下方,见W3C规范的详细定义

内联-块元素

c

c

从左到右依次是:含有流内(in-flow)内容(那个“c”)的内联-块元素,含有流内内容和overflow: hidden的内联-块元素和不含流内内容(但内容区具有高度)的内联-块元素。margin的边界用红线表示出来,border为黄色,padding为绿色,内容区为蓝色,每个内联-块元素的baseline用蓝线表示

内联-块元素的outer edge是其margin-box的顶边和底边,也就是图中的红线

内联-块元素的baseline取决于元素是否含有流内内容:

  • 含有流内内容时,内联-块元素的baseline是常规流中最后一个内容元素的baseline(左边的例子),最后一个元素的baseline是根据它自身的规则来确定的
  • 含有流内内容但具有计算值为非visibleoverflow属性时,baseline是margin-box的底边(中间的例子),所以,它与内联-块元素的底边相同
  • 不含流内内容时,baseline也是margin-box的底边(右边的例子)

行盒

xThiscanhappen.

上图中,把行盒的文本盒(更多信息见下文)的顶边和底边用绿色画出来,而baseline还用蓝线,还给文本元素设置了灰色背景高亮标记出来

行盒的顶边与该行最高元素的顶边对齐,并且底边与该行最低元素的底边对齐,就是上图中用红线表示的部分

行盒的baseline是可变的:

CSS 2.1 does not define the position of the line box’s baseline. — the W3C Specs

这可能是用vertical-align时最让人迷惑的部分了。也就是说,baseline具体放在哪里要满足所有其它条件,比如vertical-align和让行盒高度最小,它是方程中的一个自由参数

因为行盒的baseline是不可见的,无法直观地看出来它在哪里。但很容易就能让他变得可见,只需要在有疑问的行首添一个字符,就像图中添的“x”。如果这个字符没有以任何方式对齐,它默认将坐在baseline上

在baseline周围,行盒含有我们称之为文本盒(text box)的东西。文本盒可以简单地看做一个没有任何对齐方式的行盒中的内联元素。其高度等于其父元素的font-size。因此,文本盒只会包裹行盒中没被格式化过的文本,上图中用绿线表示出来了。因为这个文本盒与baseline绑在一起,baseline动的时候它也跟着动(注:这个文本盒在W3C规范中被称为strut)

这就是最难的部分了。现在,我们已经知根知底了。快速总结一下最重要的几点:

  • 有个区域叫行盒,是竖直对齐发生的地方。它具有baseline,文本盒及顶边底边
  • 内联级元素,是哪些被对齐的东西,它们具有baseline和顶边底边

vertical-align的值

通过使用vertical-align来对上面提到的参照点和内联级元素设定某些关联

元素的baseline相对行盒baseline对齐

xbaselinesubsuper-50%+10px

  • baseline:元素的baseline恰好与行盒的baseline重合
  • sub:元素的baseline移到行盒baseline下方
  • super:元素的baseline移到行盒的baseline上方
  • <percentage>:元素的baseline相对行盒的baseline移动关于line-height的百分比
  • <length>: 元素的baseline相对行盒的baseline移动一个绝对长度

元素的outer edge相对行盒baseline对齐

xmiddle

  • middle:元素顶边底边之间的中点与行盒的baseline加上半个x-height对齐

元素的outer edge相对行盒的文本盒对齐

x text-top text-bottom

  • text-top:元素的顶边与行盒的文本盒的顶边对齐
  • text-bottom:元素的底边与行盒的文本盒的底边对齐

元素的outer edge相对行盒的outer edge对齐

x top bottom

  • top:元素的顶边与行盒的顶边对齐
  • bottom:元素的底边与行盒的底边对齐

当然,正式的定义在W3C规范里都能找到

为什么vertical-align的行为是这样

我们可以更近一步看看某些场景下的竖直对齐,尤其是我们将那些可能出错的场景

居中小图标

有个烦扰着我的问题:我有一个小图标,想要与旁边的一行文本居中对齐。只给小图标来个vertical-align: middle看起来居中效果不那么让人满意。看看这个例子:

Centered?

Centered!

<!-- left mark-up -->
<span class="icon middle"></span>
Centered?<!-- right mark-up -->
<span class="icon middle"></span>
<span class="middle">Centered!</span><style type="text/css">
 .icon   { display: inline-block;
           /* size, color, etc. */ } .middle { vertical-align: middle; }
</style>

这儿还有个相同的例子,但我画出了一些你已经从上面了解到的辅助线:

x Centered?

x Centered!

这样能揭示一些线索,因为左边的文本没有任何对齐方式,它坐在baseline上。实际上,设置vertical-align: middle来对齐小方块,我们把它对齐到了不具上伸部(ascender)的小写字母的中心位置(半个x-height)。所以,具有上伸部的字符显得比较靠上

右边的话,我们让整个字体区的中点也竖直对齐,把文本的baseline相对行盒baseline稍微下移来实现效果。结果是文本和紧挨着的小图标漂亮地居中了

行盒baseline的移动

这是个用vertical-align的常见陷阱:行盒的baseline受该行所有元素的影响。我们假设有个元素以这种方式对齐(相对自身baseline对齐),行盒的baseline就不得不移动。因为大多数竖直对齐(除了topbottom)都是相对其baseline的,导致该行所有其它元素也都跟着调整位置

一些示例:

  • 如果一行有个高元素横跨整个高度,vertical-align对它就不起作用了,它顶部之上和底部之下已经没有能供它移动的空间了。为了满足其相对行盒baseline的对齐关系,行盒baseline就不得不移动了。矮方块具有vertical-align: baseline,左边,高方块是text-bottom对齐,右边是text-top对齐,可以发现baseline带着矮盒子一起跳上去了
  <!-- left mark-up -->
 <span class="tall-box text-bottom"></span>
 <span class="short-box"></span> <!-- right mark-up -->
 <span class="tall-box text-top"></span>
 <span class="short-box"></span> <style type="text/css">
   .tall-box,
   .short-box   { display: inline-block;
                 /* size, color, etc. */ }   .text-bottom { vertical-align: text-bottom; }
   .text-top    { vertical-align: text-top; }
 </style>

在用其它vertical-align值对齐一个高元素时会出现同样的行为

  • 甚至设置vertical-alignbottom(左图)和top(右图)也会移动baseline,这就怪了,因为根本不牵扯baseline啊
  <!-- left mark-up -->
 <span class="tall-box bottom"></span>
 <span class="short-box"></span> <!-- right mark-up -->
 <span class="tall-box top"></span>
 <span class="short-box"></span> <style type="text/css">
   .tall-box,
   .short-box { display: inline-block;
                /* size, color, etc. */ }   .bottom    { vertical-align: bottom; }
   .top       { vertical-align: top; }
 </style>
  • 一行里放两个大元素,竖直对齐它们会移动baseline到满足它们对齐方式的位置,然后行盒的高度也会调整(左图)。添上第三个元素,其对齐方式不会让它超出行盒的边界的话,既不影响行盒的高度也不影响baseline的位置(中图)。如果它超出了行盒的边界,行盒的高度和baseline就会再次调整,这种情况下,我们最初的两个方块被推下去了(右图)
  <!-- left mark-up -->
 <span class="tall-box text-bottom"></span>
 <span class="tall-box text-top"></span> <!-- mark-up in the middle -->
 <span class="tall-box text-bottom"></span>
 <span class="tall-box text-top"></span>
 <span class="tall-box middle"></span> <!-- right mark-up -->
 <span class="tall-box text-bottom"></span>
 <span class="tall-box text-top"></span>
 <span class="tall-box text-100up"></span> <style type="text/css">
   .tall-box    { display: inline-block;
                  /* size, color, etc. */ }   .middle      { vertical-align: middle; }
   .text-top    { vertical-align: text-top; }
   .text-bottom { vertical-align: text-bottom; }
   .text-100up  { vertical-align: 100%; }
 </style>

内联级元素下方可能会有小间隙

看看这种情况,试图vertical-align列表里的li时,很容易遇到:

<ul>
 <li class="box"></li>
 <li class="box"></li>
 <li class="box"></li>
</ul><style type="text/css">
 .box { display: inline-block;
        /* size, color, etc. */ }
</style>

如图所示,列表项坐在baseline上,baseline下方是一些用于来容纳文本下延部(descender)的空间,造成了间隙。解决方案呢?只需要把baseline移远一点,例如,用vertical-align: middle对齐列表项:

<ul>
 <li class="box middle"></li>
 <li class="box middle"></li>
 <li class="box middle"></li>
</ul><style type="text/css">
 .box    { display: inline-block;
           /* size, color, etc. */ } .middle { vertical-align: middle; }
</style>

这种场景不会出现在含有文本内容的内联-块元素中,因为内容已经移到baseline上了

内联级元素之间的间隙破坏布局

这主要是内联级元素自身的问题,但因为它们是vertical-align的依赖项之一,所以最好了解清楚

在前一个例子中也能看到列表项之间的间隙,间隙来自出现在标记代码(HTML/XML等)里的内联元素之间的空白字符。内联元素之间的所有空白字符都被合并成一个空格,就是这个空格碍事,例如想让两个内联元素仅挨在一起并都设置width: 50%的话,就没有足够的空间容纳两个50%的元素和一个空格。所以会拆分成2行破坏布局(左图)。为了去掉间隙,我们需要去掉空白字符,例如用HTML注释(右图)

50% wide

50% wide… and in next line

50% wide

50% wide

<!-- left mark-up -->
<div class="half">50% wide</div>
<div class="half">50% wide... and in next line</div><!-- right mark-up -->
  <div class="half">50% wide</div><!--
--><div class="half">50% wide</div><style type="text/css">
 .half { display: inline-block;
         width: 50%; }
</style>

vertical-align揭秘

嗯,就是这样,一旦知道规则后就不很复杂。如果vertical-align不生效,只用考虑这些问题:

  • 行盒的baseline和顶边底边在哪里?
  • 内联级元素的baseline和顶边底边在哪里?

这将揭示问题的解决方案

二.技巧

1.怎样确定行盒的baseline?

给这一行加个没有下延部的字符,一般习惯加x,字符的底部边缘就是行盒baseline的位置

例如:

.baseline:before {
   content: 'x';
}

2.怎么确定行盒的边界?

利用上面提到的“元素的outer edge相对行盒的outer edge对齐”:

.line-box-top {
   border-top: 1px dotted red;
   /* 让 border-top 与行盒的顶边重合 */
   vertical-align: top;   /* 宽度沾满整行 */
   display: inline-block;
   width: 100%;
   /* 让开空间,避免影响内容布局 */
   margin-right: -100%;
   /* 提升z,避免被内容遮住 */
   position: relative;
   z-index: 10;
}
/* .line-box-bottom 与之类似 */

在想要明确行盒边界的那一行的行首(因为用margin-right: -100%,所以放最左边)添上

<span class="line-box-top"></span><span class="line-box-top"></span>

即可

3.怎么确定文本盒的边界?

与确定行盒边界的方法类似,利用vertical-align: text-top;vertical-align: text-bottom;

相对谁对齐,那么就能把这个“谁”画出来

4.用HTML注释去掉空白字符技巧

例如:

<figure>
   <span class="large font">
       <span class="green dotted line text-top"> </span><!--
    --><span class="green dotted line text-bottom"> </span><!--
    --><span class="red dotted line top"> </span><!--
    --><span class="red dotted line bottom"> </span><!--
    --><span class="blue dotted line baseline"> </span><!--
    --><span class="font color-grey inline-overlay">x</span><!--
    --><span class="center">
           <span class="middle bg-grey">This</span>
           <span class="tall box bg-grey text-top"> </span>
           <span class="top bg-grey">can</span>
           <span class="tall box bg-grey text-bottom"> </span>
           <span class="bottom bg-grey">happen.</span>
       </span>
   </span>
</figure>

去掉空白字符的同时,保留标签缩进格式,很有意思

原文发布于微信公众号 - ayqy(gh_690b43d4ba22)

原文发表时间:2017-08-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券