跳到主要內容
黯羽輕揚每天積累一點點

vertical-align 刨根問底

免費2017-08-13#CSS#line-box#text-box#vertical-align无效#vertical-align原理#vertical-align规范#vertical-aligin计算方式

結合 CSS 規範深入了解 vertical-align,解釋各種奇怪的間隙

寫在前面

本文第一部分翻譯自 Vertical-Align: All You Need To Know,就是之前在 [CSS 上下左右居中](/articles/css 上下左右居中/) 參考資料部分提到的待翻譯的那一篇

其餘部分是對原文的技巧總結

.top{display:inline-block;vertical-align:top}.bottom{display:inline-block;vertical-align:bottom}.middle{display:inline-block;vertical-align:middle}.baseline{display:inline-block;vertical-align:baseline}.text-top{display:inline-block;vertical-align:text-top}.text-bottom{display:inline-block;vertical-align:text-bottom}.sub{display:inline-block;vertical-align:sub}.super{display:inline-block;vertical-align:super}strong{font-weight:400}figure{line-height:1;overflow:visible}figure ul,figure ol{margin:0 !important;;padding:0 !important;}figure.center{text-align:center}figure.bg-grey{background:#f0f0f0;padding:1em}figure .center{text-align:center;display:inline-block;width:100%}figure+pre{margin-top:1em}figure *+*{margin-top:0}figure figcaption{width:100%;margin:0;margin-top:1em;font-style:italic;font-size:.8em}figure .bg-grey{background:#d3d3d3}figure .bg-green{background:#c1cd89}figure .bg-yellow{background:#fcdb9a}figure .bg-blue{background:#8ab3bf}figure .border-grey{border:1px solid #d3d3d3}figure .font{white-space:nowrap;line-height:1}figure .font.small{font-size:.6666em}figure .font.smaller{font-size:.3333em}figure .font.large{font-size:1.5em}figure .font.larger{font-size:3em}figure .font.tall-line-height{line-height:2}figure .font.short-line-height{line-height:.5}figure .font.color-grey{color:#d3d3d3}figure .box{min-width:1em;min-height:1em}figure .box.shorter{min-height:.25em}figure .box.shorter.quad{min-width:.25em}figure .box.short{min-height:.5em}figure .box.short.quad{min-width:.5em}figure .box.tall{height:2em}figure .box.tall.quad{width:2em}figure .box.taller{height:4em}figure .box.taller.quad{width:4em}figure .box.quad.max-third-width{max-width:30%}figure .inline-overlay{display:inline-block;width:100%;margin-right:-100%;position:relative;z-index:10}figure .line{display:inline-block;width:100%;margin-right:-100%;position:relative;z-index:10;border-top:1px solid #000}figure .line.dashed{border-style:dashed}figure .line.dotted{border-style:dotted}figure .line.grey{border-color:#d3d3d3}figure .line.red{border-color:red}figure .line.blue{border-color:#00f}figure .line.green{border-color:#32cd32}figure .line.orange{border-color:#daa520}figure .show-box-model{background:#c1cd89;border-color:#fcdb9a}figure .show-box-model>.show-box-model-content{background:#8ab3bf;display:block;min-width:100%;min-height:100%} .columns.no-break>* { margin-right: 2.5%; width: 47.5% !important; display: inline-block; margin-top: 0; } .columns.no-break.thirds>* { margin-right: 2.5%; width: 30% !important; display: inline-block; margin-left: 0; }

經常需要讓一些並排顯示的元素豎直對齊

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.
This can happen.

行盒就是我們的上下文(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 的底邊(右邊的例子)

行盒

x This can happen.

上圖中,把行盒的文本盒(更多信息見下文)的頂邊和底邊用綠色畫出來,而 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 對齊

x baseline sub super -50% +10px
  • baseline:元素的 baseline 恰好與行盒的 baseline 重合

  • sub:元素的 baseline 移到行盒 baseline 下方

  • super:元素的 baseline 移到行盒的 baseline 上方

  • <percentage>:元素的 baseline 相對行盒的 baseline 移動關於 line-height 的百分比

  • <length>: 元素的 baseline 相對行盒的 baseline 移動一個絕對長度

元素的 outer edge 相對行盒 baseline 對齊

x middle
  • 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>

去掉空白字符的同時,保留標籤縮進格式,很有意思

評論

暫無評論,快來發表你的看法吧

提交評論