一次內聯元素錯位引發對line-height的思考

滴滴WebApp架構組發表於2018-11-08

作者:李一睿

line-height 對於一個前端小可愛來說,應該是一個會經常碰面的老朋友了。可是有一天,我突然發現自己好像對他沒那麼瞭解,他也沒有外表看起來的那麼簡單。

事情的經過是這樣的……

在偶然一次工作中,我寫了這樣的模板:

<div>
    <span class="name">重大疾病險</span>
    <span class="tip">保額每月可累計</span>
</div>
複製程式碼
div{
    font-family: "PingFang SC";
}
.name{
    line-height: 20px;
    font-size: 20px;
}
.tip{
    display: inline-block;
    line-height: 20px;
    font-size: 14px;
}
複製程式碼

兩個相鄰的內聯元素,字型一大一小,行高相同,由於第二段文字需要有字多情況就自動去下一行的效果,所以第二個spaninline-block我暢想的結果是,兩個 span 高度都是 20px, div 高度也是 20px,多麼完美。但結果往往不近人意……

一次內聯元素錯位引發對line-height的思考

div 的高度怎麼是 28??

一次內聯元素錯位引發對line-height的思考 一次內聯元素錯位引發對line-height的思考

再一看子元素,一個 28 一個 20??

  • 疑問1:難道是 line-height 對行內元素不生效??

    然而規範告訴我,對於非替代的 inline 元素,它用於計算行盒(line box)的高度。

  • 疑問2:既然生效了,為什麼審查元素看著是 28,不是 20 呢?

    對於文字來說,存在一個內容區域(content area)。你可以理解為,用游標選中這行文字時帶背景的區域。他同時受 font-family 和 font-size 的影響。就算是相同的字號,如果字型不同,‘你所看到’的高度也是不一樣的,同學們可以自己嘗試一下。這裡強調你所看到的,也就是我們這裡的 28px,它其實是這個文字內容區的高度,而內容區的高度並不是真正的高度,也就是說,它不會影響這個元素真正的尺寸,也不會撐起父元素的高度,所以只是一個你看起來的高度。而對於行內元素來說,真正影響它高度的就是line-height

    在這裡,我們可以通過改變他們的 font-family 來證實一下。

      div{
          font-family: "HelveticaNeue";
      }
      .name{
          line-height: 20px;
          font-size: 20px;
      }
      .tip{
          display: inline-block;
          line-height: 20px;
          font-size: 14px;
      }
    複製程式碼
    一次內聯元素錯位引發對line-height的思考 一次內聯元素錯位引發對line-height的思考 一次內聯元素錯位引發對line-height的思考

    可以看到,更改了字型之後,.name的高度由28變成了24,.tip由於是inline-block所以高度沒變,但是父div的高度依然是28px,說明content area並不會影響父元素的真實高度。

  • 疑問3:那如何看到它真正的高度?

    你可能也發現了,第二個 span 是inline-block,它的高度就是 20px,那我們將第一個span也設定成inline-block來看一下。
    一次內聯元素錯位引發對line-height的思考
    果然,它的高度終於變成了我們期望的 20px。
    那我們再來看一下父元素div的高度:
    一次內聯元素錯位引發對line-height的思考
    !!怎麼還是 28px??

  • 疑問4:兩個子元素都已經是 20px 了,為什麼父元素還是 28px?

    這裡先宣告兩個問題:

    1. 證明前面疑問2中說的沒錯,content area 是不會影響實際尺寸的,現在我們元素都是 20px 了,可父元素依然還是 28px。
    2. 我們將第一個 span 設定成inline-block之後,可以看到它的尺寸已經變成了 20px,但這並不代表這它就沒有 content area 了,或者它的 content area 變成了 20px。你可以理解為我們給他外面包了一層盒子,你可以看到盒子的真正的尺寸,而 content area 在它裡面,你只是看不到罷了。更何況,它是一個影響不了誰的東西。

    接著回到我們的問題,仔細看上圖,可以發現,兩個高度都是 20px 的子元素,上下都有一定的間隙;再仔細看,發現它倆沒對齊,紅色背景的靠上,粉色背景的靠下,它倆之間稍微有些錯位。就是這些,讓我們的父元素被撐成了 28px。那它們又是從哪裡來的呢?

    既然是對不齊導致父元素被撐高了,那 CSS 中相關對齊的屬性,我們很容易想到是vertical-align,難道是它搞得鬼?那我們試著改一下它們的vertical-align

    一次內聯元素錯位引發對line-height的思考

    vertical-align:top,上面對不齊了
    一次內聯元素錯位引發對line-height的思考

    vertical-align:middle,好像跟之前差別不大……

    雖說這些屬性值都沒能讓他們對齊,但可以發現確實是與它有關的,可能是我們哪裡使用不當,影響了這個屬性?那我們就去了解一下vertical-align是怎麼對齊的,起初我們不設定的時候,會取它的預設值:baseline—— 基線對齊。

    內聯元素預設是基線對齊的,而基線就是指行框盒子中字母'x' 的下邊緣:

    一次內聯元素錯位引發對line-height的思考

    誒??再仔細看看我們之前對不齊的那張圖,你會發現,雖然元素沒對齊,但是文字下端是對齊的,這不就是正常的基線對齊嗎!這下就說的通了。

    劃重點!!

    行內元素的對齊方式預設是基線對齊,也就是都與 x 的下邊緣對齊。這兩個 span 的高度是一樣的,都是 20px,然而重點就在於它們的字型大小不一樣。而文字在如果設定了line-height,是會基於line-height居中的,也就是說,它們分別在各自高度相同框框中居中,但由於字號大小不一樣,所以文字底部是對不齊的,但他們又需要基線對齊,所以它倆開就必須要錯位一下。

    這裡引用一下鑫旭大神的圖:

    一次內聯元素錯位引發對line-height的思考
  • 疑問5:那這是不是就說明,我們把兩個元素的行高設定成不一樣,就可以不錯位把父元素撐高了?

    一次內聯元素錯位引發對line-height的思考

    這裡我們分別給兩個span設定與自己字型大小一樣的行高:20px 和 14px,可以看到它倆已經沒有向外錯位了,但是父元素的高度並沒有如期,還是 28px。怎麼回事?難道還有別的東西再影響它?

    你猜對了,但這是個我們看不見的東西。引用一下張鑫旭大神的叫法——幽靈空白節點。

    “幽靈空白節點”是內聯盒模型中非常重要的一個概念,具體指的是:在 HTML5 文件宣告 中,內聯元素的所有解析和渲染表現就如同每個行框盒子的前面有一個“空白節點”一樣。這 個“空白節點”永遠透明,不佔據任何寬度,看不見也無法通過指令碼獲取,就好像幽靈一樣, 但又確確實實地存在,表現如同文字節點一樣,因此,我稱之為“幽靈空白節點”。

    既然看不見,那我們可以拿一個看的見的 x 字元來假裝它。

      <div>
          <em>x</em>
          <span class="name">重大疾病險</span>
          <span class="tip">保額每月可累計</span>
      </div>
    複製程式碼
      div{
          font-family: "PingFang SC";
      }
      em{
          display:inline-block; /*為了審查元素看到的不是content area*/
          font-style:normal;
      }
      .name{
          line-height: 20px;
          font-size: 20px;
      }
      .tip{
          display: inline-block;
          line-height: 20px;
          font-size: 14px;
      }
    複製程式碼
    一次內聯元素錯位引發對line-height的思考

    這個 'x' 字號和行高都沒有設定,字號是繼承下來的 20px,元素行高預設是 1.x(1.x 倍的 font-size),具體數值還有待研究,但絕對是 >1 的,由圖可見,什麼都沒有設定的情況下,這個 'x' 的高度就是 28px,也就代表著,那個幽靈空白節點的高度就是 28px。

    那我們將幽靈空白節點的行高設小一些不就好了?可是這個幽靈節點,看不見摸不著,怎麼設啊。別忘了,line-heigt是可繼承性的,我們給父div設定line-heigt,幽靈節點自然就會繼承。

如何解決

明白了上述的疑問以及原理,接下來就看下怎麼解決這個問題。

解決一

我們可以給父divline-height設定一個很小的值

div{
    line-height: 5px;
    font-family: "PingFang SC";
}
em{
    display:inline-block; /*為了審查元素看到的不是content area*/
    font-style:normal;
}
.name{
    line-height: 20px;
    font-size: 20px;
}
.tip{
    display: inline-block;
    line-height: 20px;
    font-size: 14px;
}
複製程式碼
一次內聯元素錯位引發對line-height的思考 一次內聯元素錯位引發對line-height的思考

這樣,幽靈節點的行高就只有 5px,父元素行高 20px,沒毛病。但這個方法還不夠完美,因為你需要想一下這個很小的值……

解決二

當 line-height 設定為一個無單位的數值時,表示是某倍的font-size。

div{
    line-height: 1;
    font-family: "PingFang SC";
}
em{
    display:inline-block; /*為了審查元素看到的不是content area*/
    font-style:normal;
}
.name{
    line-height: 20px;
    font-size: 20px;
}
.tip{
    display: inline-block;
    line-height: 20px;
    font-size: 14px;
}
複製程式碼

給父元素設定line-height為1,這樣,子元素高度都是自己的font-size,包括幽靈空白節點。這樣就不會再有人無意間撐高父元素了。當然,除非你的幽靈空白節點繼承下來了一個很大的 font-size, 這點你得明白。

一次內聯元素錯位引發對line-height的思考 一次內聯元素錯位引發對line-height的思考

解決三

既然行高可以與 font-size 有關,font-size 也是可繼承的,那我們將父元素的 font-size 設為 0

div{
    font-size: 0px;
    font-family: "PingFang SC";
}
em{
    display:inline-block; /*為了審查元素看到的不是content area*/
    font-style:normal;
}
.name{
    line-height: 20px;
    font-size: 20px;
}
.tip{
    display: inline-block;
    line-height: 20px;
    font-size: 14px;
}
複製程式碼
一次內聯元素錯位引發對line-height的思考 一次內聯元素錯位引發對line-height的思考

可以看到,我們模仿的那個幽靈節點已經沒有了,寬高都是 0 了,父元素的高度也自然就回到了 20px。

總結

以上,可以說是我對行內元素對齊問題的一些思考總結吧。如果你還有疑問,可以再多看幾遍,自己嘗試一下再繼續思考思考。這個問題我也是來來回回想了好多遍,也歡迎大家評論提問,我們可以一起討論~

前端小白,有講解錯誤或不全之處,還望大佬們嘴下留情,歡迎指正~

相關文章