inline-block 前世今生

轉自:http://ued.taobao.com/blog/2012/08/15/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 的證據:

Theinline-blockvalue 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

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.1The ‘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)!

W3C9.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 constitutewhite space.

For more information about SGML’s specification of line breaks, please consult thenotes on line breaksin 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-spacingword-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-17

一絲
2012-8-10 於杭州

發佈了0 篇原創文章 · 獲贊 2 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章