What's the deal with vertical-align: baseline?

2019-01-31 12:21发布

问题:

I thought I knew my way around CSS, but I needed to explain something to someone just now and I found I couldn't.

My question basically boils down to: why is vertical-align:baseline ignored when there are other alignments in the same line?

Example: if the second span has vertical-align:bottom, the first span's vertical alignment is ignored if it is baseline; it behaves as if it has bottom too.

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

While if all the spans have a vertical-align other than baseline, or, if they are all baseline, then they behave as expected.

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>

If this is normal behaviour, then why isn't it described anywhere? I haven't found any source that says baseline and top/bottom interfere with each other in such a way.

回答1:

Vertical-Align

vertical-align is used to align inline-level elements. These are elements, whose display property evaluates to:

  • inline
  • inline-block
  • inline-table (not considered in this answer)

Inline-level elements are laid out next to each other in lines. Once there are more elements than fit into the current line, a new line is created beneath it. All these lines have a so-called line box, which encloses all the content of its line. Differently sized content means line boxes of different height.

In the following illustration the top and bottom of line boxes are indicated by red lines.

Inside these line boxes the property vertical-align is responsible for aligning the individual elements.

Baseline

The most important reference point to align vertically is the baseline of the involved elements. In some cases the top and bottom edge of the element’s enclosing box becomes important, too.

Inline elements

The top and bottom edge of the line height is indicated by red lines, the height of the font by green lines and the baseline by a blue line.

On the left, the text has a line height set to the same height as the font-size. The green and red line collapsed to one line on each side.

In the middle, the line height is twice as large as the font-size.

On the right, the line height is half as large as the font-size.

Note that the inline element’s outer edges (the red lines) does not matter, if the line height is smaller than the height of the font.

Inline-Block Element

From left to right you see:

  • an inline-block element with in-flow content

  • an inline-block element with in-flow content and overflow: hidden

  • an inline-block element with no in-flow content (but the content area has a height)

The boundaries of the margin is indicated by red lines, the border is yellow, the padding green and the content area blue. The baseline of each inline-block element is shown as a blue line.

The inline-block element’s baseline depends on whether the element has in-flow content. In case of:

  • in-flow content the baseline of the inline-block element is the baseline of the last content element in normal flow (example on the left)

  • in-flow content but an overflow property evaluating to something other than visible, the baseline is the bottom edge of the margin-box (example in the middle)

  • no in-flow content the baseline is, again, the bottom edge of the margin-box (example on the right)

Line box

This is probably the most confusing part, when working with vertical-align. It means, the baseline is placed where ever it needs to be to fulfil all other conditions like vertical-align and minimizing the line box’s height. It is a free parameter in the equation.

Since the line box’s baseline is invisible, it may not immediately be obvious where it is. But, you can make it visible very easily. Just add a character at the beginning of the line in questions, like the "x" in the figure. If this character is not aligned in any way, it will sit on the baseline by default.

Around its baseline the line box has what we might call its text box (green lines in the figure). The text box can simply be thought of as an inline element inside the line box without any alignment. Its height is equal to the font-size of its parent element. Therefore, the text box only just encloses the unformatted text of the line box. Since this text box is tied to the baseline, it moves when the baseline moves.

Snippet

If you want to do some experiment with various vertical-align and font-size here you have a snippet where you can try it out. Is also available in 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>

This snippet is made by Duannx.


Source: Please note that this is an extract of Vertical-Align: All You Need To Know written by Christopher Aue.



回答2:

The question is why are vertical-align: baseline; ignored. Well I don't think it is. In your first snippet you're using baseline and bottom what clearly gives a difference between the two <span> elements. So what does baseline do? Well baseline Aligns the baseline of the element with the baseline of the parent element. In the example below I've copied and adjusted some parts to give the difference.

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

As you can see the baseline alignment acts normal just as the middle alignment.

Now lets test something else. lets swap the alignment of baseline and middle and edit middle and add a third <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>

Now if you'll edit the second snippet and take a look at the vertical-align of <span class="three"> you can clearly see that when changing the alignment the text does change it's position.

But you're question relates on text on the same line so lets take a look at the snippet down below.

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>

As you can see in this third snippet I've placed the third <span> next to one and two. And it does make a difference. The baseline in this case is different from the middle alignment. This is because the baseline grabs the baseline of the parent. since the parent's height is normal it doesn't affect baseline and top alignment. but when you'll use middle or sub it is clearly a difference.

For information about each alignment, check out this link.