Skip to content

Instantly share code, notes, and snippets.

@aehlke
Forked from nolanlawson/index.html
Last active July 7, 2025 18:58
Show Gist options
  • Save aehlke/bc0e23a21a19cbd0abc13cf55c3d1d91 to your computer and use it in GitHub Desktop.
Save aehlke/bc0e23a21a19cbd0abc13cf55c3d1d91 to your computer and use it in GitHub Desktop.
Test async getBoundingClientRect with IntersectionObserver vs rAF + gBCR
<!doctype html>
<html lang="en">
<head>
<title>IntersectionObserver vs getBoundingClientRect (50,000 elements)</title>
<style>
#theContainer { width: 100vw; max-width: 100%; }
.test-elem { margin:1px; display:inline-block; width:10px; height:10px; background:#ddd; }
</style>
</head>
<body>
<h1>IntersectionObserver vs getBoundingClientRect (50,000 elements)</h1>
<button type=button onclick="testIO()">Test using IntersectionObserver</button>
<button type=button onclick="testGBCR()">Test using rAF + getBoundingClientRect</button>
<button type=button onclick="testGBCRSync()">Test using sync getBoundingClientRect</button>
<div id="theContainer"></div>
<script>
const N = 10000;
// Pre-create all elements on page load
function prefillContainer() {
let bigText = '';
function rubyDigits(str) {
return String(str).split('').map(d =>
`<ruby>${d}<rt>${d}</rt></ruby>`
).join('');
}
for (let i = 0; i < N; i++) {
bigText += `<div class="test-elem" id="elem${i}">${rubyDigits(i)}</div>`;
}
document.getElementById('theContainer').innerHTML = bigText;
}
prefillContainer();
function asyncGetBoundingClientRectUsingIO(element) {
return new Promise(function (resolve) {
var observer = new IntersectionObserver(function (entries) {
observer.disconnect();
resolve(entries[0].boundingClientRect);
});
observer.observe(element);
})
}
function asyncGetBoundingClientRectUsingGBCR(element) {
return new Promise(function (resolve) {
requestAnimationFrame(function () {
var rect = element.getBoundingClientRect();
resolve(rect);
})
})
}
function syncGetBoundingClientRect(element) {
return Promise.resolve(element.getBoundingClientRect());
}
function Marker(str) {
this.str = str;
}
Marker.prototype.mark = function () {
performance.mark('start_' + this.str)
}
Marker.prototype.stop = function () {
performance.mark('end_' + this.str)
performance.measure(this.str, 'start_' + this.str, 'end_' + this.str)
return performance.getEntriesByName(this.str).reverse().slice(-1)[0].duration
}
function stringifyRect(rect) {
return JSON.stringify({ left: rect.left, top: rect.top, width: rect.width, height: rect.height })
}
async function testGBCR() {
var marker = new Marker('gbcr-' + Math.random())
marker.mark()
let promises = [];
for (let i = 0; i < N; i++) {
let randIdx = Math.floor(Math.random() * N);
let el = document.getElementById('elem' + randIdx);
el.style.background = '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0');
promises.push(asyncGetBoundingClientRectUsingGBCR(el));
}
let rects = await Promise.all(promises);
var duration = marker.stop();
console.log('getBoundingClientRect (rAF): duration: ' + duration.toFixed(2) + 'ms, last rect: ' + stringifyRect(rects[rects.length - 1]));
}
async function testGBCRSync() {
var marker = new Marker('gbcrsync-' + Math.random())
marker.mark()
let promises = [];
for (let i = 0; i < N; i++) {
let randIdx = Math.floor(Math.random() * N);
let el = document.getElementById('elem' + randIdx);
el.style.background = '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0');
promises.push(syncGetBoundingClientRect(el));
}
let rects = await Promise.all(promises);
var duration = marker.stop();
console.log('getBoundingClientRect (sync): duration: ' + duration.toFixed(2) + 'ms, last rect: ' + stringifyRect(rects[rects.length - 1]));
}
async function testIO() {
var marker = new Marker('io-' + Math.random())
marker.mark()
let promises = [];
for (let i = 0; i < N; i++) {
let randIdx = Math.floor(Math.random() * N);
let el = document.getElementById('elem' + randIdx);
el.style.background = '#' + Math.floor(Math.random() * 0xFFFFFF).toString(16).padStart(6, '0');
promises.push(asyncGetBoundingClientRectUsingIO(el));
}
let rects = await Promise.all(promises);
var duration = marker.stop();
console.log('IntersectionObserver: duration: ' + duration.toFixed(2) + 'ms, last rect: ' + stringifyRect(rects[rects.length - 1]));
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment