0

I am trying to figure out a way to calculate the baseline of a font at any given font size for typographical animations and noticed something extremely bizarre: For some reason the height value of elements increases in a seemingly arbitrary way when their position is changed to absolute.

Heights without Position Absolute (click to console log heights)

const
  small = document.querySelector('#small'),
  large = document.querySelector('#large')

window.onclick = () => {
  console.log(small.getBoundingClientRect().height,
              large.getBoundingClientRect().height)
}
div {
  border: solid purple 2px;
}

#small {
  border: solid red 2px;
}

#large {
  border: solid black 2px;
  font-size: 10rem;
}
<div>
  <span id='small'>.</span>
  <span id='large'>.</span>
</div>

Heights with Position Absolute (click to console log heights)

const
  small = document.querySelector('#small'),
  large = document.querySelector('#large')

window.onclick = () => {
  console.log(small.getBoundingClientRect().height,
              large.getBoundingClientRect().height)
}
div {
  border: solid purple 2px;
}

#small {
  border: solid red 2px;
}

#large {
  border: solid black 2px;
  font-size: 10rem;
}

span {
  position: absolute;
}
<div>
  <span id='small'>.</span>
  <span id='large'>.</span>
</div>

Can anybody explain this?

oldboy
  • 5,729
  • 6
  • 38
  • 86
  • I don't see any difference between them? Please include OS and browser info. – shreyasm-dev Oct 08 '20 at 00:01
  • 1
    the difference is due to inline/block difference ... the first snippet you have inline element and in the second one they become block due to position:absolute (make the span inline-block in the first example and you have the same height) – Temani Afif Oct 08 '20 at 00:07
  • @TemaniAfif interesting ill test that right now – oldboy Oct 08 '20 at 00:37
  • @TemaniAfif when i set them to `inline-block` from the get go, then they assume the same `height` as if they were positioned `absolutely`. shouldnt it be the other way around? – oldboy Oct 08 '20 at 00:40
  • @TemaniAfif if i set the `span` elements to `inline` from the get go, then the discrepancy remains as you change them to `absolute` – oldboy Oct 08 '20 at 00:43
  • when setting absolute, the display become irrelevant .. inline will get ignored and become block – Temani Afif Oct 08 '20 at 00:48
  • @TemaniAfif ok that makes sense. im about halfway thru ur answer right now. – oldboy Oct 08 '20 at 00:49

1 Answers1

2

The trick is within the display and you won't find any logical relation between the height you are getting because in both cases the calculation is different.

In the first case, you have inline element and this rule apply:

The 'height' property does not apply. The height of the content area should be based on the font, but this specification does not specify how. A UA may, e.g., use the em-box or the maximum ascender and descender of the font. (The latter would ensure that glyphs with parts above or below the em-box still fall within the content area, but leads to differently sized boxes for different fonts; the former would ensure authors can control background styling relative to the 'line-height', but leads to glyphs painting outside their content area.)

The vertical padding, border and margin of an inline, non-replaced box start at the top and bottom of the content area, and has nothing to do with the 'line-height'. But only the 'line-height' is used when calculating the height of the line box.ref

So the height of your span depends only on the font properties (font-family, font-size, etc) + the border top/bottom. You cannot find how the height is calculated exactly unless you dig into the font properties which is a difficult exercise especially if we deal with the default font that differ a from browser to another (and from OS to another).

Related:

Can specific text character change the line height?

What determines the space between the highest and lowest letter and the top and bottom border of the element the letter's in?


In the second case, you span is now a block element because you made it position:absolute so you need to follow the rule detailed here: https://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-height that will lead us to use the following rule: https://www.w3.org/TR/CSS21/visudet.html#root-height

If it only has inline-level children, the height is the distance between the top of the topmost line box and the bottom of the bottommost line box

So the height of our element is now the height of the line box which is defined by line-height (like detailed here: https://www.w3.org/TR/CSS21/visudet.html#line-height). Not only line-height but also vertical alignment of the elements inside but in your case you don't have this complexity since you have only one character

The default line-height is defined by the browser but if you fix it you will get the height you want:

const
  small = document.querySelector('#small'),
  large = document.querySelector('#large')

window.onclick = () => {
  console.log(small.getBoundingClientRect().height,
              large.getBoundingClientRect().height)
}
div {
  border: solid purple 2px;
}

#small {
  border: solid red 2px;
  line-height:1; /* will give a height = 1*16 + 4 */
}

#large {
  border: solid black 2px;
  font-size: 10rem;
  line-height:1; /* will give a height = 1*10*16 + 4 */
}

span {
  position: absolute;
}
<div>
  <span id='small'>.</span>
  <span id='large'>.</span>
</div>

Related: How does height will be calculated, based on font-size?


If you make the span element inline-block you will get the same behavior as in the second case because you follow the rules here: https://www.w3.org/TR/CSS21/visudet.html#block-root-margin that will get us to the same rule for absolute element (https://www.w3.org/TR/CSS21/visudet.html#root-height) which means the line-height will control your height:

const
  small = document.querySelector('#small'),
  large = document.querySelector('#large')

window.onclick = () => {
  console.log(small.getBoundingClientRect().height,
              large.getBoundingClientRect().height)
}
div {
  border: solid purple 2px;
}

#small {
  border: solid red 2px;
  display:inline-block;
  line-height:1;
}

#large {
  border: solid black 2px;
  font-size: 10rem;
  display:inline-block;
  line-height:1;
}
<div>
  <span id='small'>.</span>
  <span id='large'>.</span>
</div>
Temani Afif
  • 245,468
  • 26
  • 309
  • 415
  • @oldboy I also advise you to read the related question to get full details ;) a bit boring but intresting – Temani Afif Oct 08 '20 at 00:56
  • i have read a number of other questions/answers, but didnt believe anything was applicable. if you have any good questions/answers that you think are applicable, could you give me links to them? one other question: does the default `line-height` vary from device to device/"platform" to "platform"? – oldboy Oct 08 '20 at 01:03
  • @oldboy I linked 3 questions in my answer that are applicable ... yes the default `line-height` may vary based on the font and also the platform that's why I used a fixed value to demonstrate my logic otherwise you can have a different result in different browsers. same logic for the font, if you use an explicit font (like a google font for example) you will always have the same height in the first case but if you keep the default font, the result will be different. – Temani Afif Oct 08 '20 at 01:06