Justinus Hermawan
Justinus Hermawan

Reputation: 1214

JavaScript's offsetWidth and offsetHeight give wrong value when element is resizing quickly

I'm creating my own scrollbar component using vanilla javascript. The element is resizable, so when it's resized, the scrollbar's maximum scroll position and size will follow.

Everything is fine if I resize with normal speed, but if I test quickly, the calculation result (size and max. pos) produce wrong result, I guess because of the offsetWidth or offsetHeight mistake.

My scrollbar's render function triggered when on resize, sbv means scrollbar vertical and sbh means scrollbar horizontal:

this.sbv.size = this.content.offsetHeight / this.content.scrollHeight;
this.sbv.style.height = (this.sbv.size * 100) + "%";
this.sbv.max = (100 - this.sbv.size * 100) / 100 * this.content.offsetHeight;
this.sbv.pos = this.content.scrollTop / this.content.scrollHeight * this.content.offsetHeight;
this.sbv.style.top = (this.sbv.pos / this.content.offsetHeight * 100) + "%";

this.sbh.size = this.content.offsetWidth / this.content.scrollWidth;
this.sbh.style.width = (this.sbh.size * 100) + "%";
this.sbh.max = (100 - this.sbh.size * 100) / 100 * this.content.offsetWidth;
this.sbh.pos = this.content.scrollLeft / this.content.scrollWidth * this.content.offsetWidth;
this.sbh.style.left = (this.sbh.pos / this.content.offsetWidth * 100) + "%";

Does anyone know why this happen? TiA

Upvotes: 1

Views: 3563

Answers (1)

Kaiido
Kaiido

Reputation: 136746

That will be a theoretical answer only, because I didn't took the time to setup a repro case, and because "produce wrong results" is a bit broad of a description.


I suspect the problem is actually the inverse of your claim: offsetWidth, offsetHeight, scrollHeight and scrollWidth actually give you the correct values, in a mean you may not have expected.

These properties, in order to return the correct values do trigger a recalculation of all the bounding boxes of the elements in the document, this is known as a "reflow".
This reflow implies that all the CSS rules you did set previously are now applied and active.

So in your code, when you do

elem.size = content.offsetHeight / content.scrollHeight;
elem.style.height = (elem.size * 100) + "%";
elem.max = (100 - elem.size * 100) / 100 * content.offsetHeight;

The value of content.offsetHeight at the first line, and the one at the third line will probably not be the same anymore.

var elem = document.getElementById('elem');
var content = document.getElementById('content');

console.log('before', content.offsetHeight);
elem.size = content.offsetHeight / content.scrollHeight;
elem.style.height = (elem.size * 100) + "%";
elem.max = (100 - elem.size * 100) / 100 * content.offsetHeight;
console.log('after', content.offsetHeight);
body{
  height: 100vh;
}
#content,#elem {
  border: 1px solid;
}
#elem {
  height: 800px;
}
<div id="content">
  <div id="elem"></div>
</div>

So, not only triggering multiple reflows like that is terrible for the performances, but you will probably have your calculations wrong indeed.

The best is probably to cache all the current values before hand in variables, make all your calculations and only at the end set all the new styles.

Also note that all this would be better ran in a requestAnimationFrame debouncer, so it happens only once per painting frame.

Upvotes: 2

Related Questions