垂直对齐基线(vertical-align: baseline)有什么问题?

43

我本以为自己对CSS很熟悉,但刚才有人问我一个问题时,我发现我不知道该怎么回答。

我的问题基本上可以归结为:当同一行出现其他垂直对齐方式时,为什么会忽略vertical-align:baseline

例如:如果第二个span标签设置了vertical-align:bottom,那么第一个span标签的竖直对齐将被忽略,即使它设置为baseline;它的行为就像也设置了bottom一样。

span:first-child {vertical-align:baseline}
span:last-child {font-size:3em; vertical-align:bottom;}
<p>
  <span>one</span> <span>two</span>
</p>

如果所有的span标签都有除baseline以外的垂直对齐方式,或者它们都是baseline对齐方式,那么它们的表现就如预期一样。

span:first-child {vertical-align:top}
span:last-child {font-size:3em; vertical-align:bottom;}
<p>
  <span>one</span> <span>two</span>
</p>

span:first-child {vertical-align:baseline}
span:last-child {font-size:3em; vertical-align:baseline;}
<p>
  <span>one</span> <span>two</span>
</p>

如果这是正常行为,那么为什么没有地方进行描述呢?我没有找到任何源代码表明基线和顶部/底部会以这种方式相互干扰。


2
简而言之:不同对齐方式的内联元素不会改变当前行的基线。 - BoltClock
@BoltClock,那么在我的例子中,当前行的基线在哪里?请原谅我愚钝,但我仍然不明白。如果您将第二个span的垂直对齐方式更改为“top”、“middle”或“bottom”,为什么第一个span会上下跳动?我错过了什么吗? - Mr Lister
@MrLister - 当前行基线的位置并不重要。重要的是第一个span的基线和支架的基线在哪里,但它对两个span的相对位置以及它们在行框中的位置没有影响。CSS 2.2第10.8点2生效,"如果这些框足够高,则存在多个解决方案,CSS 2.2不定义行框基线的位置"。浏览器会做一些明智的事情,但它并没有被CSS真正定义。 - Alohci
@Alohci 这很有道理;我需要进行一些基于此的测试。但到目前为止,我还没有发现任何浏览器行为不同的情况,因此如果这确实没有受到任何正式定义的约束,它们在这里表现得非常协调!(我的意思是,在大多数其他正式定义过于模糊的情况下,浏览器会找到一种方式来区别它们的做法。) - Mr Lister
针对浏览器差异,请尝试Paolo Forgia最后一个示例的这个变体:在https://jsfiddle.net/Lotfg2L7/中,我只是反转了第二个和第三个span的高度。比较Firefox中第一个span的垂直位置与它在Chrome和Edge中出现的位置。 - Alohci
3个回答

61

垂直对齐

vertical-align 用于对齐内联元素。这些是具有以下 display 属性的元素:

  • inline
  • inline-block
  • inline-table(此答案不予考虑)

内联元素在一行中相邻排列。当元素多于当前行所能容纳的数量时,将在其下方创建一个新行。所有这些行都有所谓的行框,它围绕其行的所有内容。不同大小的内容意味着不同高度的行框。

在以下插图中,行框的顶部和底部由红线表示。 enter image description here

在这些行框内,属性vertical-align负责对齐各个元素。

基线

垂直对齐最重要的参考点是涉及元素的基线。在某些情况下,元素的包含框的顶部和底部边缘也很重要。

内联元素

enter image description here

行高的顶部和底部边缘由红线表示,字体高度由绿线表示,基线由蓝线表示。

在左侧,文本的行高设置为与字体大小相同的高度。绿线和红线在两侧折叠成一条线。

在中间,行高是字体大小的两倍。

在右侧,行高是字体大小的一半。

请注意,如果行高小于字体高度,则内联元素的外部边缘(红线)无关紧要。

内联块级元素

enter image description here

从左到右,您可以看到:

  • 一个具有流动内容inline-block元素

  • 一个具有流动内容overflow: hiddeninline-block元素

  • 一个没有流动内容(但内容区域具有高度)的inline-block元素

边界的外边距由红线表示,边框为黄色,内边距为绿色,内容区域为蓝色。每个inline-block元素的基线显示为蓝色线。

inline-block元素的基线取决于元素是否具有流动内容。在以下情况下:

  • 流动内容时,inline-block元素的基线是正常流中最后一个内容元素的基线(左侧示例

  • 流动内容,但overflow属性计算出的值不是visible时,基线是外边距框底边缘(中间示例

  • 流动内容时,基线同样是外边距框底边缘(右侧示例

行框

图片描述

这可能是使用vertical-align时最令人困惑的部分。这意味着,基线放置在需要满足所有其他条件(如vertical-align最小化行框高度)的任何位置。 它是方程中的一个自由参数

由于行框的基线是不可见的,因此它可能不会立即明显。但是,您可以非常容易地使其可见。只需在问题行的开头添加一个字符,例如图中的“x”。如果此字符没有以任何方式对齐,则默认情况下它将位于基线上。

在其基线周围,行框具有我们可以称之为文本框的东西(图中的绿色线)。可以简单地将文本框视为行框内没有任何对齐的内联元素。它的高度等于其父元素的font-size。因此,文本框仅仅包含了行框的未格式化文本。由于该文本框与基线相连,所以当基线移动时,它也会移动。

代码片段

如果您想尝试各种vertical-alignfont-size的实验,请使用以下代码片段进行尝试。您还可以在JSFiddle上找到它。

let sl1 = document.getElementById('sl1');
let sl2 = document.getElementById('sl2');
let sl3 = document.getElementById('sl3');
let sl4 = document.getElementById('sl4');
let elm1 = document.getElementById('elm1');
let elm2 = document.getElementById('elm2');
let elm3 = document.getElementById('elm3');
let elm4 = document.getElementById('elm4');
let ip1 = document.getElementById('ip1');
let ip2 = document.getElementById('ip2');
let ip3 = document.getElementById('ip3');
let ip4 = document.getElementById('ip4');
let slArr = [sl1, sl2, sl3, sl4];
let elmArr = [elm1, elm2, elm3, elm4];
let ipArr = [ip1, ip2, ip3, ip4];
let valueArr = ['baseline', 'top', 'middle', 'bottom'];
for (let i = 0; i < slArr.length; i++) {
  slArr[i].addEventListener('change', (event) => {
    elmArr[i].style.verticalAlign = event.target.value;
    elmArr[i].innerHTML = event.target.value;
    addDiv();
  })
}
for (let i = 0; i < ipArr.length; i++) {
  ipArr[i].addEventListener('change', (event) => {
    elmArr[i].style.fontSize = event.target.value + 'em';
    addDiv();
  })
}

document.getElementById('btnRandom').addEventListener('click', () => {
  for (let i = 0; i < elmArr.length; i++) {
    let element = elmArr[i];
    let fontSize = Math.floor(Math.random() * 4 + 1);
    ipArr[i].value = fontSize;
    element.style.fontSize = fontSize + 'em';
    let styleIndex = Math.floor(Math.random() * 4);
    element.style.verticalAlign = valueArr[styleIndex];
    element.innerHTML = valueArr[styleIndex];
    slArr[i].selectedIndex = styleIndex;
  }
}, this);

function addDiv() {
  let view = document.getElementById('viewer');
  view.innerHTML = "";
  elmArr.forEach(function(element) {
    let div = document.createElement('div');
    div.appendChild(element.cloneNode());
    view.appendChild(div);
  }, this);
}
.header span {
  color: #000;
}

select {
  width: 100px;
}

#elms {
  border: solid 1px #000;
  margin-top: 20px;
  position: relative;
}

span {
  color: #FFF;
  font-size: 1em;
}

#elm1 {
  background-color: #300;
}

#elm2 {
  background-color: #6B0;
}

#elm3 {
  background-color: #90A;
}

#elm4 {
  background-color: #B00;
}

div {
  z-index: -1;
}

#div1 {
  width: 100%;
  height: 1px;
  background-color: #000;
  position: absolute;
  left: 0;
  top: 25%;
}

#div2 {
  width: 100%;
  height: 1px;
  background-color: #000;
  position: absolute;
  left: 0;
  top: 50%;
}

#div3 {
  width: 100%;
  height: 1px;
  background-color: #000;
  position: absolute;
  left: 0;
  top: 75%;
}
<div class="header"> <span style="width: 100px;display: block;float: left;margin-right: 20px;">vertical align</span> <span>font-size(em)</span> </div>
<div>

  <select name="sl1" id="sl1">
    <option value="baseline">baseline</option>
    <option value="top">top</option>
    <option value="middle">middle</option>
    <option value="bottom">bottom</option>
  </select>
  <input type="number" value="1" id="ip1" />
  <br>
  <select name="sl2" id="sl2">
    <option value="baseline">baseline</option>
    <option value="top">top</option>
    <option value="middle">middle</option>
    <option value="bottom">bottom</option>
  </select>
  <input type="number" value="1" id="ip2" />
  <br>
  <select name="sl3" id="sl3">
    <option value="baseline">baseline</option>
    <option value="top">top</option>
    <option value="middle">middle</option>
    <option value="bottom">bottom</option>
  </select>
  <input type="number" value="1" id="ip3" />
  <br>
  <select name="sl4" id="sl4">
    <option value="baseline">baseline</option>
    <option value="top">top</option>
    <option value="middle">middle</option>
    <option value="bottom">bottom</option>
  </select>
  <input type="number" value="1" id="ip4" />
  <br>
  <button id="btnRandom" (onclick)="random()">Random</button>
</div>
<div id="elms">
  <span id="elm1">one</span>
  <span id="elm2">two</span>
  <span id="elm3">three</span>
  <span id="elm4">four</span>
  <div id="div1"></div>
  <div id="div2"></div>
  <div id="div3"></div>
</div>
<div id="viewer"></div>

这段代码由Duannx提供。


来源:请注意,这是Christopher Aue撰写的垂直居中:所有你需要知道的一篇文章的摘录。


6
问题是为什么会忽略 "vertical-align: baseline;"。我不认为它被忽略了。在您的第一个片段中,您使用了“baseline”和“bottom”,这显然区分了两个元素。那么,baseline是做什么的呢?嗯,baseline将元素的基线与父元素的基线对齐。在下面的示例中,我复制并调整了一些部分以显示差异。

span.one {vertical-align:baseline}
span.two {vertical-align:middle;}
<p>
  <span class="one">one</span> <span class="two">two</span>
</p>

正如您所看到的,baseline 对齐方式的表现和 middle 对齐方式一样正常。
现在让我们测试另外一些东西。让我们交换 baselinemiddle 的对齐方式,并编辑 middle 并添加第三个 <span>

span.one { vertical-align: top;}
span.two { vertical-align: baseline;}

span.three {vertical-align: middle; height: 20px; }
p {
height: 50px;
background-color: grey;
}
<p>
  <span class="one">one</span> <span class="two">two</span> <span class="two">two</span><br><br> <span class="three">three</span>
</p>

现在,如果您编辑第二个代码片段并查看<span class="three">vertical-align,您可以清楚地看到更改对齐方式时,文本确实会改变其位置。但是,您的问题涉及同一行上的文本,因此让我们看一下下面的代码片段。

span.one { vertical-align: middle;}
span.two { vertical-align: baseline;}

span.three {vertical-align: middle; height: 20px; }
p {
height: 50px;
background-color: grey;
}
<p>
  <span class="one">one</span> <span class="two">two</span> <span class="two">two</span><span class="three">three</span>
</p>

如您在第三个代码片段中所见,我将第三个<span>放在了“one”和“two”的旁边。这确实有所不同。在这种情况下,baselinemiddle对齐是不同的。这是因为baseline抓取父元素的baseline。由于父元素的高度是正常的,它不会影响baselinetop对齐。但是,当您使用middlesub时,显然存在差异。

有关每种对齐方式的信息,请查看link


1
一个简短的解释:起点是知道父元素的基线位置。在 <p> 标签内或设置 vertical-alignmentbaseline<span> 中添加一些文本(下面是 "one"),"one" 的基线是字符底部,例如字母 'n' 的底部。
然后就可以轻松地看到其他 span 相对于它如何变化了。我在 span "one" 周围添加了一个边框,以便清楚地看到其顶部和底部边缘。

span {  }
span:nth-child(1) {vertical-align:baseline; font-size:3em; border: 1px solid gray}
span:nth-child(2) {vertical-align:top; color:red}
span:nth-child(3) {vertical-align:middle; color:green;}
span:nth-child(4) {vertical-align:bottom; color:blue;}
<p>
  <span>one</span> <span>two</span> <span>three</span> <span>four</span>
</p>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接