靈魂拷問——關於DOM元素中的空白符

你可能知道怎麼解決這個問題,可是你真的瞭解背後的緣由嗎?javascript

石碑

請查看下面的示例代碼,在ul元素下有三個使用display: inline-block;水平排列的li元素。css

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>CSS Demo</title>
    <style> * { margin: 0; padding: 0; } #root { width: 400px; border: 3px solid #7abdb6; margin: 40px; padding: 20px; } ul > li { display: inline-block; padding: 5px; background: #27a3ff; color: aliceblue; } </style>
</head>
<body>
<div id="root">
    <ul>
        <li>A</li>
        <li>B</li>
        <li>C</li>
    </ul>
</div>
</body>
</html>
複製代碼

線路

請問,爲何li元素之間有空隙?html

image

經過使用document.querySelector('ul').childNodes獲取ul元素的子節點,能夠發現,在li元素之間,存在節點類型爲3的文本節點text,空隙即文本節點佔據的空間。vue

image

那如何去除li元素之間的空隙呢?java

刪除文本節點便可,好比咱們能夠換成這種寫法:node

<div id="root">
    <ul><li>A</li><li>B</li><li>C</li></ul>
</div>
複製代碼

很好,可是這樣代碼可讀性會下降,有更優雅的方式嗎?git

可使用HTML解析器,自動刪除空白字符文本節點。例如,在vue項目中,咱們能夠這麼寫:github

<div id="root">
    <div id="app"></div>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script> (function () { const res = Vue.compile(` <ul> <li>A</li> <li>B</li> <li>C</li> </ul> `, { whitespace: 'condense' }); new Vue({ el: '#app', render: res.render, staticRenderFns: res.staticRenderFns, }); }()); </script>
複製代碼

刪除空白字符可能會形成誤傷,有不刪除空白字符的方法嗎?面試

能夠給ul設置font-size: 0;,讓空白字符不佔據空間,例如:npm

ul {
    font-size: 0;
}

li {
    font-size: initial;
}
複製代碼

雖然默認狀況下,Chrome只容許設置最小12px字號,可是設置0px也是容許的。

若是從佈局的角度來考慮,還有其它解決方案嗎?

有的,可使用浮動佈局,例如:

ul:after {
    content: ' ';
    display: block;
    clear: both;
    height: 0;
    visibility: hidden;
}

li {
    float: left;
}
複製代碼

或者使用彈性盒子佈局也能夠:

ul {
    display: flex;
}
複製代碼

那爲何使用float或者flex以後,li之間的空白字符就不佔據空間了呢?

咱們換一個更簡單的示例:

<p>
    <span>A</span>
    <span>B</span>
</p>
<p>
    <span>C</span><span>D</span>
</p>
複製代碼

能夠發現,在瀏覽器渲染後,AB之間存在空隙,而CD是牢牢挨着的。

讓咱們來回顧一下人類語言。

對於中文(也包括一些其它的語言)而言,詞與詞之間是直接相連的。

JavaScript是世界上最好的語言。

可是對於英文(也包括一些其它的語言)而言,詞與詞之間是使用空格分隔的。

JavaScript is the best language in the world.

因此,瀏覽器在渲染包括文本在內的行內元素時,爲了保證語義正確性,必需保留渲染空白字符。

也就是說,li之間有空隙,本質上是由於display: inline-block;將它們轉換爲了行內元素。因此瀏覽器在渲染它們時,選擇的是和文本渲染相同的策略,把每一個li當成了一個單詞來對待,這樣它們之間的空格天然也就保留渲染了。而使用float就意味着使用塊佈局,它會在某些狀況下修改display 屬性的計算值。因此,此時lidisplay屬性雖然表面上仍是inline-block,可是實際上已經被修改成了block。對於flex也是一樣的道理。

image

回到最開始,ul下有7個子節點,爲何第1個和最後1個文本節點沒有佔據空間呢?

console.log(document.querySelector('ul > li:first-child').getBoundingClientRect());

// DOMRect {x: 63, y: 63, width: 30.515625, height: 32, top: 63, right: 93.515625, bottom: 95, left: 63}
複製代碼

能夠看到,第1個li元素的x爲63,正好等於margin 40px + border 3px + padding 20px

一樣用一個更簡單的示例:

<div>
    Here is an English paragraph
    that is broken into multiple lines
    in the source code so that it can
    more easily read in a text editor.
</div>
複製代碼

能夠發現,這一大段文本都渲染在了同一行。

回到互聯網剛誕生的時候,網頁都是純靜態的,因此網頁裏面的內容都是直接硬編碼出來的,而不像今天咱們大多都是動態渲染或靜態編譯出來的。這樣,爲了HTML源碼的可讀性和易維護性,空格、縮進和換行,以及一些其它空白字符就會被大量使用了。可是瀏覽器在渲染的時候,不能把這些都渲染出來啊,因此就有了一個摺疊連續空白字符的概念。

簡言之,就是把連續的空白字符所有摺疊爲單個的空白字符,並儘可能不渲染空白字符。

這裏直接引用CSS規範裏面的一段原文。

image

重點在第一、第3和第4點,行首和行尾的連續可摺疊的空白字符在渲染時將被刪除,若是行尾仍有可摺疊的空白字符,則懸掛到行首。

因此,能夠看到,第1個文本節點是沒法選中的,可是第7個文本節點能夠被選中,且出如今了第1個li的前面!

注意:這裏的第7個文本節點能夠被選中且寬度爲0,是受到了第5個文本節點的影響。

image

若是換成下面的寫法:

<ul>
    <li>A</li><li>B</li><li>C</li>
</ul>
複製代碼

則最後一個文本節點一樣沒法被選中。

實戰

某宇宙公司面試官發了個 codepen 上的源碼連接。

請問,爲何div元素之間有空隙?

不記得了。

問題結束。

地圖

相關文章
相關標籤/搜索