jsPerf.app is an online JavaScript performance benchmark test runner & jsperf.com mirror. It is a complete rewrite in homage to the once excellent jsperf.com now with hopefully a more modern & maintainable codebase.
jsperf.com URLs are mirrored at the same path, e.g:
https://jsperf.com/negative-modulo/2
Can be accessed at:
https://jsperf.app/negative-modulo/2
Measures total ms needed to:
① capture first state ② apply new grid layout ③ prepare/invert transforms, not the play phase (animating is compositor work).
<!-- libs -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.13.0/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.13.0/Flip.min.js"></script>
<style>
/* ——— base ——— */
body{
margin:0;
font-family:sans-serif;
background:#111;
color:#ccc;
}
.stage{
display:grid;
grid-template-columns:repeat(auto-fill,70px);
gap:8px; /* modern alias for grid-gap */
padding:20px;
/* container-query setup */
container-type:inline-size;
width:400px; /* will be toggled to 600 px in the test */
}
.card{
width:70px;
height:70px;
background:#3a79ff;
border-radius:6px;
}
/* ——— container query overrides ——— */
@container (min-width:500px){
.card{
width:120px;
height:120px;
background:#ff5d3a; /* visual proof CQ fired */
}
}
</style>
<div id="stage" class="stage"></div>// ------------ constants --------------
const CARD_COUNT = 400;
const stage = document.getElementById('stage');
// ------------ helper: create grid ----
function makeGrid(reverse = false) {
stage.innerHTML = '';
const frag = document.createDocumentFragment();
for (let i = 0; i < CARD_COUNT; i++) {
const d = document.createElement('div');
d.className = 'card';
// reverse order so each run rearranges everything
d.dataset.id = reverse ? CARD_COUNT - i : i;
frag.appendChild(d);
}
stage.appendChild(frag);
}
// ------------ helpers shared by tests ----
const rectCache = new WeakMap(); // element -> DOMRect
const matCache = new WeakMap(); // element -> {dx,dy,sx,sy}
function readAllRects() {
const els = stage.children;
const out = new Array(els.length);
for (let i = 0; i < els.length; i++) {
out[i] = els[i].getBoundingClientRect();
}
return out;
}
function writeAllTransforms(frames) {
// apply inverse transform
const els = stage.children;
for (let i = 0; i < els.length; i++) {
const t = frames[i];
els[i].style.transform =
`translate(${t.dx}px,${t.dy}px) scale(${t.sx},${t.sy})`;
}
}
function computeFrames(first, last) {
const arr = new Array(first.length);
for (let i = 0; i < first.length; i++) {
const a = first[i],
b = last[i];
arr[i] = {
dx: a.left - b.left,
dy: a.top - b.top,
sx: a.width / b.width,
sy: a.height / b.height,
};
}
return arr;
}
// start every test with the same initial DOM
makeGrid(false);
// Tick-bound memoized rect helper
const rectCacheTB = new WeakMap();
let purge = false;
function rectTB(el) {
let r = rectCacheTB.get(el);
if (!r) {
r = el.getBoundingClientRect();
rectCacheTB.set(el, r);
if (!purge) {
purge = true;
queueMicrotask(() => { rectCacheTB.clear(); purge = false; });
}
}
return r;
}
Ready to run.
| Test | Ops/sec | |
|---|---|---|
| Naïve FLIP (per-node read) | | ready |
| Batched FLIP (single read, single write) | | ready |
| Cached FLIP (skip unchanged) | | ready |
| GSAP Flip (v7) | | ready |
| GSAP Flip (v7) + Container-Query | | ready |
| Batched FLIP with tick-bound memo | | ready |
You can edit these tests or add more tests to this page by appending /edit to the URL.