Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are an element's scrollHeight and clientHeight not the same for some fonts?

For some fonts when the line-height of the element is smaller than a threshold the scrollHeight is bigger than the clientHeight.

So there is something in the font properties that causes this but what is this and how can it be avoided preferably using CSS or even a font editor for custom fonts?

For example, in this snippet the scrollHeight for Tahoma is more than the clientHeight although sans-serif seems OK when line-height is 1. This difference increases when the page is zoomed in Chrome (ctrl+) and happens even for sans-serif. When the line-height is below 1 or the font-size gets bigger the difference increases.

For some other fonts it goes up to 5px at line-height 1 and decreases by increasing the line-height up to 2 which leads to incorrect calculation.

var a = document.getElementById('a');
console.log('tahoma - a.clientHeight: ' + a.clientHeight);
console.log('tahoma - a.scrollHeight: ' + a.scrollHeight);

var b = document.getElementById('b');
console.log('sans - b.clientHeight: ' + b.clientHeight);
console.log('sans - b.scrollHeight: ' + b.scrollHeight);

var c = document.getElementById('c');
console.log('sans - lineHeight:0.5 - c.clientHeight: ' + c.clientHeight);
console.log('sans - lineHeight:0.5 - c.scrollHeight: ' + c.scrollHeight);

var d = document.getElementById('d');
console.log('sans - font-size:200px - d.clientHeight: ' + d.clientHeight);
console.log('sans - font-size:200px - d.scrollHeight: ' + d.scrollHeight);
div[id] {
 overflow:auto;
 max-width:80%;
}
<div id='a' style='font-family:tahoma; line-height:1;'>Hello</div>
<div id='b' style='font-family:sans-serif; line-height:1;'>Hello</div>
<div id='c' style='font-family:sans-serif; line-height:0.5;'>Hello</div>
<div id='d' style='font-family:sans-serif; line-height: 1; font-size:500px;'>Hello</div>

It's clear that this difference is due to an overflow issue but what font metrics are involved here and how can we identify the difference between scrollHeight and clientHeight?

This happens both in Chrome and Firefox; I didn't test other browsers.

like image 542
Ali Avatar asked Oct 15 '18 11:10

Ali


People also ask

What is the difference between scrollHeight and clientHeight?

clientHeight: It returns the height of an HTML element including padding in pixels but does not include margin, border and scrollbar height. scrollHeight: It returns the height of the content enclosed in an html element including padding but not margin, border and scroll bar.

What is the difference between offsetHeight and clientHeight?

clientHeight = the height of an element + the vertical padding. offsetHeight = the height of the element + the vertical padding + the top and bottom borders + the horizontal scrollbar (if it's available).

What is scrollTop and scrollHeight?

In summary, scrollTop is how much it's currently scrolled, and scrollHeight is the total height, including content scrolled out of view.

What is clientHeight in Javascript?

The clientHeight property returns the viewable height of an element in pixels, including padding, but not the border, scrollbar or margin.


1 Answers

To make the issue more visible let's introduce a span inside the div and add some border/background. Let's start by using a big line-height:

body {
 font-family:sans-serif;
}
div {
  border:1px solid;
  margin:10px;
}
span {
  background:red;
}
<div style='line-height:3;'><span>Hello</span></div>

The red part define the content area and the space surrounded by the border is the line box which is the height of our div element (check this more information: Why is there space between line boxes, not due to half leading?).

In this case, we don't have any overflow so both scrollHeight and clientHeight will give the same value:

var a = document.getElementById('a');
console.log('clientHeight: ' + a.clientHeight);
console.log('scrollHeight: ' + a.scrollHeight);
body {
 font-family:sans-serif;
}
div {
  border:1px solid;
  margin:10px;
}
span {
  background:red;
}
<div id="a" style='line-height:3;'><span>Hello</span></div>

We can also conclude that both are exacly equal to 3 * 16px which is line-height * font-sizeref (by default the font-size is 16px).

Now if we start deacring the line-height we will logically decrease the height of the div and the content area will remain the same:

body {
 font-family:sans-serif;
}
div {
  border:1px solid;
  margin:10px;
}
span {
  background:red;
}
<div style='line-height:3;'><span>Hello</span></div>
<div style='line-height:1;'><span>Hello</span></div>
<div style='line-height:0.5;'><span>Hello</span></div>
<div style='line-height:0.2;'><span>Hello</span></div>

Now it's clear that we have overflow and the clientHeight will now be less than the scrollHeight but the clientHeight will remain line-height * font-size while the scrollHeight will be the height of the red part:

var a = document.querySelectorAll('.show');
var b = document.querySelectorAll('.show span');

for(var i=0;i<a.length;i++) {
console.log('cH: ' + a[i].clientHeight + ' sH: ' + a[i].scrollHeight);
}
body {
 font-family:sans-serif;
 font-size:100px;
 padding-bottom:100px;
}
div.show {
  border:1px solid;
  margin:100px;
}
span {
  background:red;
}
<div class="show" style='line-height:3;'><span>Hello</span></div>
<div class="show" style='line-height:1;'><span>Hello</span></div>
<div class="show" style='line-height:0.5;'><span>Hello</span></div>
<div class="show" style='line-height:0.2;'><span>Hello</span></div>
<div class="show" style='line-height:0.1;'><span>Hello</span></div>
<div class="show" style='line-height:0;'><span>Hello</span></div>

But why the value of scrollHeight is decreasing while the content area is kept the same? This is due to the fact that we are having an overflow on the top and the bottom (because the alignment is baseline) and the scrollHeight include only the bottom overflow as the top become inaccessible. To make the scrollHeight equal to the content area we simply need to change the alignment:

var a = document.querySelectorAll('.show');
var b = document.querySelectorAll('.show span');

for(var i=0;i<a.length;i++) {
console.log('cH: ' + a[i].clientHeight + ' sH: ' + a[i].scrollHeight);
}
body {
 font-family:sans-serif;
 font-size:100px;
 padding-bottom:100px;
}
div.show {
  border:1px solid;
  margin:100px;
}
span {
  background:red;
  vertical-align:text-bottom;
}
<div class="show" style='line-height:3;'><span>Hello</span></div>
<div class="show" style='line-height:1;'><span>Hello</span></div>
<div class="show" style='line-height:0.5;'><span>Hello</span></div>
<div class="show" style='line-height:0.2;'><span>Hello</span></div>
<div class="show" style='line-height:0.1;'><span>Hello</span></div>
<div class="show" style='line-height:0;'><span>Hello</span></div>

Now it's clear that if the line-height is big enough both are equal and if the line-height is reduced the scrollHeight has a min value equal to the content area.

If we check the specification we can read this:

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.)

Note: level 3 of CSS will probably include a property to select which measure of the font is used for the content height.

So we cannot know the exact metrics used to define this area, that's why it behave differently for each font. We can only know that it depends on the font-family and the font-size. We may probably find the calculation manually doing some tests. For the above example, the height of the content area seems to be 1.12 * font-size

For tahoma it seems to be 1.206 * font-size (on Chrome) and 1.21 * font-size (on Firefox) (see below):

var a = document.querySelectorAll('.show');
var b = document.querySelectorAll('.show span');

for(var i=0;i<a.length;i++) {
console.log('cH: ' + a[i].clientHeight + ' sH: ' + a[i].scrollHeight);
}
body {
 font-family:tahoma;
 font-size:1000px;
 padding-bottom:100px;
}
div.show {
  border:1px solid;
  margin:100px;
}
span {
  background:red;
  vertical-align:text-bottom;
}
<div class="show" style='line-height:3;'><span>Hello</span></div>
<div class="show" style='line-height:1;'><span>Hello</span></div>
<div class="show" style='line-height:0.5;'><span>Hello</span></div>
<div class="show" style='line-height:0.2;'><span>Hello</span></div>
<div class="show" style='line-height:0.1;'><span>Hello</span></div>
<div class="show" style='line-height:0;'><span>Hello</span></div>

So scrollHeight is equal to p * font-size where p depends on the font and we can find it manually doing some tests and clientHeight is equal to line-height * font-size. Of course, if we keep the alignment baseline, scrollHeight will be different because of the top overflow.

like image 180
Temani Afif Avatar answered Sep 18 '22 00:09

Temani Afif