inline-block 前世今生

曾幾何時,display:inline-block 已經深入「大街小巷」,隨處可見 「display:inline-block; *display:inline; *zoom:1; 」這樣的代碼。如今現代瀏覽器已經全面支持這個屬性值了,上面的代碼只是爲了兼容 IE6、7 而已。那麼你真的瞭解 inline-block 了嗎?本文將帶你深入剖析該屬性值的前世今生,讓你更好的理解和運用 inline-block。(本文約定 display:inline-block 簡寫爲 inline-block)

開篇我們來看幾個問題:

  • IE6、7 真的不支持 display:inline-block 嗎?
  • display:inline-block 後的元素爲什麼會產生水平空隙,這真的是 bug 嗎?
  • 如何更好的解決 display:inline-block 元素間產生的水平空隙?

一、inline-block 前世

1.認知

也許有人問你爲何要寫「 display:inline-block; *display:inline; *zoom:1; 」 來兼容 IE6、7 時,你會立馬答道:因爲 IE6、7 不支持 display:inline-block 唄!不知道何時起,慣性思維給開發者帶來了這樣一個可怕的概念。萬物都是辯證的,當你寫下這些的時候,可曾懷疑過大衆觀點真的可靠嗎?也許你認爲這些無關 緊要,實現效果就好。但是如果不能理解每個屬性或屬性值的根本,你將永遠無法全面的瞭解它,人云亦云只會讓你淺嘗輒止,止步不前。那麼這裏就涉及到所謂的 「CSS 學習瓶頸」的問題了,這個問題張鑫旭《說說CSS 學習中的瓶頸》一文有詳細闡述,雖然部分觀點我不是很贊同,但是中心思想還是很值得思考的。文中有幾個不錯的問題這裏也列舉出來供大家觀摩:

  1. line-height:150% 和 line-height:1.5 的區別是?
  2. float 爲何會讓外部容器高度塌陷?這是 bug?(我的答案在《那些年我們一起清除過的浮動》
  3. vertical-align 的表現爲何在IE7, IE8, IE9 下表現不盡相同?其中的渲染機制是?

好了,回到 inline-block 的認知的問題,我的觀點是:

IE 從 5.5 開始就已經支持 display:inline-block 了,只是支持的並不是那麼完善

在 msdn 微軟開發者社區,找到了 IE 從5.5 開始支持 inline-block 的證據:

The inline-block value is supported starting with Internet Explorer 5.5. You can use this value to give an object a layout without specifying the object’s height or width.

這裏明確指出:從 IE5.5 開始支持 inline-block。

鏈接:http://msdn.microsoft.com/zh-cn/library/ie/ms530751(v=vs.85).aspx

那麼既然 IE5.5 開始就已經支持了 inline-block,爲何我們還要寫那麼一坨 CSS 呢?同時我們知道 IE6、7 中 display:inline-block 是可以觸發 hasLayout 的,觸發了 hasLayout 的元素表現出來的特徵就是一個獨立的矩形容器,可以設置寬高而且不受外部元素的影響,類似於現代瀏覽器中的 Block formatting contexts (塊級格式化上下文)的概念。

下面來做一個詳細的測試,分別看看 IE6 中 inline 元素和 block 元素的表現:

1)inline 元素 display:inline-block

IE6 中截圖如下:

.dib-inline, .dib-block {
width:100px;
height:30px;
line-height:30px;
text-align:center;
}
.dib-inline {
display:inline-block;
}

測試表明:IE6 中 inline 元素只要觸發了 hasLayout 其表現就類似於 inline-block,這裏設置 display:inline-block; 或者 zoom:1; 等其他屬性值可以觸發 hasLayout ,表現出來是一樣的。

查看 DEMO(2013-3-22更新)

2)block 元素 display:inline-block

IE6 中截圖如下:

.dib-inline, .dib-block {
width:100px;
height:30px;
line-height:30px;
text-align:center;
}
.dib-block{
display:inline-block;
}

測試表明:IE6 中 block 元素即使觸發了 hasLayout 也不能具有 inline-block 元素不換行的特性。想要 block 元素支持 inline-block 元素的特性,我們可以這樣做:

.dib-block {
display:inline;
zoom:1;
}

首先讓 block 元素轉化爲 inline 元素,強制其不換行;然後通過 zoom:1 觸發 hasLayout,使其可以設置寬高。修復後的 截圖如下:

3)結合現代瀏覽器

綜上,現代瀏覽器都支持 display:inline-block ,IE6、7 inline 元素也可以達到同樣的效果,IE6、7 block 元素需要設置 display:inline; zoom:1; 它們結合在一起便是:

display:inline-block; /* 現代瀏覽器 +IE6、7 inline 元素 */
*display:inline; /* IE6、7 block 元素 */
*zoom:1;

爲了不讓支持 CSS2.1 inline-block 的瀏覽器 重置爲 inline,我們針對 IE6、7 做一個 hack。由於現代瀏覽器也開始支持 zoom 屬性,這裏只是希望 IE6、7 中生效,所以還是 hack 一下比較合適。至此產生了我們熟悉的兼容各個瀏覽器的 inline-block 寫法。

小結:IE6、7 並不是不支持 inline-block,只是 block 元素需要做一些處理來達到 inline-block 的效果。

2. 到底什麼是 inline-block

說了很多,或許很多朋友還不是太明白到底什麼是 inline-block?W3C 在 CSS2.1 The ‘display’ property 中描述如下:

This value causes an element to generate an inline-level block container. The inside of an inline-block is formatted as a block box, and the element itself is formatted as an atomic inline-level box.

大致意思就是:inline-block 後的元素創建了一個行級的塊容器,該元素內部(內容)被格式化成一個塊元素,同時元素本身則被格式化成一個行內元素。

直白一點的意思就是:inline-block 的元素既具有 block 元素可以設置寬高的特性,同時又具有 inline 元素默認不換行的特性。當然不僅僅是這些特性,比如 inline-block 元素也可以設置 vertical-align 屬性。簡而言之:

inline-block 後的元素就是一個格式化爲行內元素的塊容器( Block container )

怎麼樣?聽起來還不錯吧!

3. inline-block 緣從何起?

前面已經證明了 IE 5.5 開始就支持了 inline-block,那麼 IE5.5 是什麼時候發佈的呢?話說當年網景與 IE 大戰,IE5.5 那是何等的風騷……(好吧,此處略去十頁)。從維基百科的資料來看,IE5.5 beta1 的發佈時間是:1999年12月,最終版本是 2000年7月。那麼 W3C 標準中是何時纔出現 inline-block 這個值的呢?

CSS1 規範中,「display」的值僅包括: block | inline | list-item | none 。CSS2.1中才添加了 inline-block 屬性值。一絲繼續舔着手指,用那苦逼的英語水平終於翻到了這份草案:http://www.w3.org/TR/2002/WD-CSS21-20020802/visuren.html#display-prop, 這份草案的日期是 2002年8月2日,納尼!!!原來我們糾結了半天的 inline-block , IE5.5 至少提前兩年就提出來了啊!難道是微軟給 W3C 提議後,CSS 2.1才加入的?(不過我看到 W3C 官網有一個關於是否增加 inline-block 的投票)好吧這個問題也許有一天 IE 某個開發者寫《 IE回憶錄》的時候我們才能瞭解到其中的內幕。如果找到更早關於 inline-block 的 CSS草案,也麻煩告知一絲一聲。好吧,如果你還不相信,打微軟官方電話問問吧 800-820-3800(不是 DHC 哦!)。

原來我們一直討論的 inline-block 在 IE 6、7中和 CSS2.1 中的(現代瀏覽器所支持的) inline-block 上壓根不是一個東東嘛,IE6、7 中的 inline-block 更像是 IE 的私有屬性值,他們本身就不具有可比性。簡單、絕對的認爲 IE6、7 不支持 inline-block 好比一葉障目,看到前面,卻看不到後面,太過於片面。誠然,IE6、7 的 hasLayout 給我們帶來了很多麻煩,但是不得不承認微軟的 IE 在網頁多語言文本混排上的先進性,尤其是 CJK 文字和西文的混排,超越其他瀏覽器至少5年。

總結:

  • IE5.5 後開始支持 inline-block, 但是它所支持的 inline-block 不能等同於 CSS2.1 中的 inline-block,因爲 IE5.5 比 CSS2.1 更早提出 inline-block 的概念並作爲所謂的私有屬性值使用,所以二者表現出來的效果是不完全一致。
  • IE 5.5、6、7 、8(Q)中 block 元素對 inline-block 支持不完整,如果要達到類似的效果,需要先設置爲 display:inline,然後使用 zoom:1 等觸發 hasLayout
  • IE 5.5、6、7 、8(Q)中 inline 元素欲達到 inline-block 的效果只需直接設置此屬性值或使用 zoom:1 等均可。

各瀏覽器對 display 屬性的支持情況請參閱:《各瀏覽器對 ‘display’ 特性值的支持程度不同》

二、inline-block 今生

1. display:inline-block 後的元素爲什麼會產生水平空隙,這真的是 bug 嗎?

這麼一個神奇的屬性,爲何大家一直避而遠之呢?這恐怕還得從 inline-block 元素之間產生的水平空隙(間隙)說起吧。

參照 DEMO

  • 現代瀏覽器中 inline 和 block 元素 display:inline-block 後均會產生水平空隙;
  • IE6、7,IE8(Q)模擬 display:inline-block 後分兩種情況:

    IE6、7,IE8(Q)中:inline 元素會產生空隙,block 元素不會產生空隙。

看看 inline 元素默認的表現情況如何?原來默認就有空隙存在!它們是誰?是空白符(white space)!

W3C 9.1 White space 中規定以下元素屬於空白符(white space):

  • ASCII 空格 ( )
  • ASCII 製表符 ( )
  • ASCII 換頁符 ()
  • 零寬度空格 (​)「這個在閉合浮動中也有運用到」

9.3.2 Controlling line breaks 中進一步闡述:

A line break is defined to be a carriage return ( ), a line feed ( ), or a carriage return/line feed pair. All line breaks constitute white space.

For more information about SGML’s specification of line breaks, please consult the notes on line breaks in the appendix.

折行被定義爲一個回車符( ),一個換行符 line feed ( ),或者一個回車、換行的組合。所有的折行構成了空白符。

有關 SGML 規範中折行的更多信息,請參閱附錄中關於折行的註釋。

通常情況下,對於多個連續的空白符(空格,換行符,回車符等),瀏覽器會將他們合併爲一個空白符。CSS 中由 white-space 這個屬性來控制:

white-space:normal | pre | nowrap | pre-wrap | pre-line

默認值:normal

  • normal:默認處理方式。
  • pre:用等寬字體顯示預先格式化的文本,不合並文字間的空白距離,當文字超出邊界時不換行。可查閱 pre 對象
  • nowrap:強制在同一行內顯示所有文本,直到文本結束或者遭遇 br 對象。
  • pre-wrap:用等寬字體顯示預先格式化的文本,不合並文字間的空白距離,當文字碰到邊界時發生換行。
  • pre-line:保持文本的換行,不保留文字間的空白距離,當文字碰到邊界時發生換行。

注:IE7及更早瀏覽器不支持 CSS2.1 新增的 pre-wrap | pre-line。

所以這並不是 inline-block 後產生的 bug,而是因爲 inline-block 具有 inline 元素固有的特性。那麼爲何 IE6、7 block 元素沒有產生空隙呢?其實前面也提到了 IE 的 hasLayout,具有獨立性,所以產生 hasLayout 的元素之間表現出來互不影響,這也再次表明 IE6、7 中的 inline-block 不能等同於 CSS2.1 中的 inline-block。如果非要說是有 bug, IE6、7 block 元素 inline-block 後不產生空隙纔是 bug。

測試表明刪除換行符後,inline 元素間的空隙就「消失」了:

2.去掉 inline-block 產生的空隙

爲了讓各個瀏覽器表現一致,更好的還原視覺設計搞,很多時候我們需要去掉 inline-block 產生的空隙。

上一節中我們已經知道產生空隙的根本性原因是:

HTML 中的換行符、空格符、製表符等產生了空白符,而這些歸根結底都是字符,那麼它們的大小都是 受 font-size 來控制的,字體大小直接導致 inline 或者 inline-block 後元素之間空隙的大小,把 inline-block 元素間的空隙認爲總是某個固定大小是錯誤的。

用 GIF 動畫的形式來表明對應關係:

很清楚的看到,當 font-size:0 的時候元素間的空隙都爲0了,或許到這裏你會感到很欣喜了,原來掌握的根本性原因這麼簡單就搞定了啊!

然,理想是豐滿的,現實是骨感的。

大部分瀏覽器是支持 font-size:0 的。很明顯,我們要和 IE 6、7 這兩個妖孽進行一番戰鬥。

font-size:0 的支持情況

1)Chrome

低版本的 chrome 瀏覽器爲了不讓文字過小不利於閱讀,默認是不支持 font-size:0 的,還好我們有 -webkit-text-size-adjust 這個私有屬性來控制,當設爲 none 時就支持字體大小爲 0 了。我已經記不清楚 chrome 從哪個版本開始支持 font-size:0 了,反正我用 chrome 19 是支持了(有知道的朋友,煩請告訴一絲一聲,最好有官方更新說明)。但是,-webkit-text-size-adjust:none; 會直接導致頁面文字無法縮放,這對於用戶來說顯然是不友好的。所以-webkit-text-size-adjust:none; 一定要慎用,確保使用的地方沒有大面積的文字。

-webkit-text-size-adjust:none 的使用場景實例參閱:http://vip.etao.com/

2)Safari

Safari 5 依舊不支持 font-size:0 ,不過相信這些瀏覽器廠商都意識到了這個問題,在 Mac 平臺最新的 Safari 6 已經很好的支持 font-size:0 了。

3)Firefox,Opera

經測試,Firefox12,Opera 10 ,這次表現不錯,支持 font-size:0 。

4)IE

  • IE8 以上支持 font-size:0;
  • IE6、7 inline 元素 inline-block 後設置 font-size:0 始終有 1px 的空隙。

是不是一下子又開始頭疼了?沒關係,讓我們請出 letter-spacing word-spacing 二位大神。既然空白符也是字符,那麼二位大神肯定是可以搞定它們的。

  • letter-spacing : normal | length (檢索或設置對象中的文字之間的間隔)
  • word-spacing : normal | length(檢索或設置對象中的單詞之間插入的空隔)

normal: 默認間隔
length: 用長度值指定間隔,允許爲負值。

還等什麼,我們趕緊試試吧:

參照 DEMO

  • 第一步:使用 font-size:0經測試發現,chrome、firefox、IE8+、opera,inline 或 block 元素都沒有空隙了;
    Safari 5.1.7 由於不支持 font-size:0 ,仍然存在空隙;


    IE6、7、8(Q),inline 元素 inline-block 後始終存在 1px 左右的空隙。
  • 第二步:處理 Safari 不支持 font-size:0 的問題上面已經指出 letter-spacing 是支持負值的,那麼這個負值到底取多少合適呢?經過測試得出的結論是:inline-block 產生的空隙與父級元素繼承或者設定的 font-family、font-size 有關,通常情況下,12px 大小的 tahoma 字體,inline-block 後元素間產生的空隙(間隙)大約是 5px;
    各個字體詳細情況請參閱《inline-block空隙–letter-spacing與字體大小/字體關係數據表》Firefox 中 letter-spacing 負值的絕對值大於空隙大小後,會導致元素整體位置向右偏移;Safari 中 letter-spacing 負值的絕對值大於空隙大小後,內部會發生重疊。
  • 第三步:修復 IE6、7 中始終存在的 1px 空隙
    既然 letter-spacing 已經無能爲力了,那就試試 word-spacing 吧,直接設置 word-spacing:-1px。這裏需要注意的是,letter-spacing 和 word-spacing 同時使用可能導致衝突,所以我們需要在 IE6、7 中 hack 掉 letter-spacing。最終代碼如下:

font-size:0;/* 所有瀏覽器 */
letter-spacing:-5px;/* Safari 等不支持字體大小爲 0 的瀏覽器 */
*letter-spacing:normal;
word-spacing:-1px;/* IE6、7 */

  • 第四步:子元素重置回正常值
    上述所有操作都是在父元素設置的,那麼子元素都會繼承這些屬性,字體大小爲0了,子元素就什麼都看不到了,這並不是我們想要的。 同時字符和單詞間距我們也要把它重置爲默認值。「font-size: 12px; letter-spacing: normal; word-spacing: normal;」
  • 最後:inline-block 更好的複用
    或許你會擔心每次我都要去看字體和空隙之間大小的關係嗎?其實不然,通常情況下,全局字體都已經在 body 中指定了,根據全局字體設置合適的 letter-spacing 負值即可。如此一來,我們便可以放心大膽的使用 inline-block 了,結合 OOCSS 的思想,可以抽離出兩個複用的類,在需要設置 inline-block 元素的父級元素上定義一個「.dib-wrap」,該元素自身定義爲「.dib」。這裏還有一個問題需要注意的是:由於 inline-block 具有 inline 元素的特性,在垂直方向上很多時候我們並不希望元素以「vertical-align:baseline」方式來呈現,所以在「.dib-wrap」中統一重置爲「vertical-align:top」即可。

3. 去除 inline-block 空隙終極解決方案(2012年8月17日更新)

.dib-wrap {
font-size:0;/* 所有瀏覽器 */
*word-spacing:-1px;/* IE6、7 */
}
.dib-wrap .dib{
font-size: 12px;
letter-spacing: normal;
word-spacing: normal;
vertical-align:top;
}
@media screen and (-webkit-min-device-pixel-ratio:0){
/* firefox 中 letter-spacing 會導致脫離普通流的元素水平位移 */
.dib-wrap{
letter-spacing:-5px;/* Safari 等不支持字體大小爲 0 的瀏覽器, N 根據父級字體調節*/
}
}
.dib {
display: inline-block;
*display:inline;
*zoom:1;
}

其實在 YUI 3 中也全面運用了 inline-block 作爲基礎佈局,YUI 3 是這樣解決的:

.yui3-g {
letter-spacing: -0.31em; /* webkit: collapse white-space between units */
*letter-spacing: normal; /* reset IE < 8 */
word-spacing: -0.43em; /* IE < 8 && gecko: collapse white-space between units */
}

.yui3-u {
display: inline-block;
zoom: 1; *display: inline; /* IE < 8: fake inline-block */
letter-spacing: normal;
word-spacing: normal;
vertical-align: top;
}

顯然,這裏純粹使用了 letter-spacing 和 word-spacing 來控制元素間的空隙,侷限性極大,-0.31em 和 -0.43em 只是因爲 YUI 3 全局 cssfonts.css 裏設置是:「body { font:13px/1.231 arial,helvetica,clean,sans-serif; }」。

當然,如果你堅持使用把 html 寫在一行的方式來達到去除 inline-block 空隙的目的,我只能說:一切以犧牲結構來兼容表現的行爲都是耍流氓!所以探討此種方式去除空隙也將是無意義的,不在本文和作者考慮範圍之內。

4. 結局——本文產生的一些觀點如下:

  • IE5.5 後開始支持 inline-block, 比 CSS2.1 更早提出 inline-block 的概念並作爲所謂的私有屬性值使用。但是它所支持的 inline-block 不能等同於 CSS2.1 中的 inline-block,IE 5.5、6、7 、8(Q)中 block 元素對 inline-block 支持不完整,因此二者表現出來的效果是不完全一致。
  • 產生 inline-block 空隙的根本性原因是:HTML 中的換行符、空格符、製表符等合併爲空白符,字體大小不爲 0 的情況下,空白符自然佔據一定的寬度,因此產生了元素間的空隙。
  • 慎用 -webkit-text-size-adjust:none,它將會導致頁面無法通過縮放來改變字體大小。

三、inline-block 未來

如今,Mac 平臺下的 Safari 6 已經支持 font-size:0 了,相信很快 Windows 平臺的 Safari 如果發佈 5.X 的更新,也會支持字體爲 font-size:0 了。等到 IE6、7 滅亡之後,世界就真真兒的美妙了!最後說一點:inline-block 與 float 也是無法直接比較的,請不要再討論 inline-block 和 float 哪個更好的話題了。inline-block 從 IE5.5 一路走來,存在即是合理,以後有時間在總結一下 inline-block 與 float 的使用場景的區別。

在不改變 CSS 定位機制的前提下,inline-block 應該是首選,而不是以「奇淫技巧」存在的。有感打油詩一首:

網事如煙
CSS 紅塵裏
inline-block 知多少
你在這頭
inline-block 在那頭
用與不用
它就在那裏
不悲不喜

PS:

  1. 爲了更好的排版,本文使用繁體中文引號 「」代替簡體中文小蝌蚪引號;
  2. 中英文混排的時候英文首尾各加一個空格;
  3. 以後文章有需要的時候也都將使用 gif 動畫配合說明。

測試環境

操作系統版本:  Windows 7 企業版 6.1(內部版本 7600)
瀏覽器版本: IE6
IE9
Firefox 14.0.1
Chrome 19.0.1084.46
Safari 5.1.7(7534.57.2)
Opera 12.50
最後更新時間:  2012-8-22
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章