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-size
ref (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.