灵魂拷问——关于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元素之间有空隙?

不记得了。

问题结束。

地图

相关文章
相关标签/搜索