Skip to content

Instantly share code, notes, and snippets.

@chenglou
Created November 5, 2024 08:47
Show Gist options
  • Save chenglou/8af5467b47dd4cf58745cfe950bec13f to your computer and use it in GitHub Desktop.
Save chenglou/8af5467b47dd4cf58745cfe950bec13f to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html>
<head>
<style>
* {
font-family: monospace;
}
.box {
position: absolute;
border-radius: 8px;
}
/* Add new styles for strips */
.horizontal-strip {
width: 300px;
height: 40px;
left: 150px;
}
.vertical-strip {
width: 40px;
height: 200px;
}
.horizontal-label {
-webkit-user-select: none;
user-select: none;
position: absolute;
right: 100%;
top: 50%;
transform: translateY(-50%);
margin-right: 10px;
}
.vertical-label {
-webkit-user-select: none;
user-select: none;
position: absolute;
bottom: -25px;
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
}
</style>
</head>
<body>
<div id="strip1" class="box horizontal-strip" style="top: 100px;">
<span class="horizontal-label">Baseline</span>
</div>
<div id="strip2" class="box vertical-strip" style="left: 200px; top: 20px;">
<span class="vertical-label">1x raf</span>
</div>
<div id="strip3" class="box vertical-strip" style="left: 280px; top: 20px;">
<span class="vertical-label">2x raf</span>
</div>
<div id="strip4" class="box vertical-strip" style="left: 360px; top: 20px;">
<span class="vertical-label">7x raf</span>
</div>
<div style="position: relative; top: 260px;
background: rgba(0, 0, 0, 0.8); padding: 16px; border-radius: 8px; color: white;
font-size: 14px; line-height: 1.5; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);">
<div id="display1">1x raf delay vs baseline: -</div>
<div id="display2">2x raf delay vs baseline: -</div>
<div id="display3">7x raf delay vs baseline: -</div>
<p>Here are 4 window click event handlers, 1 per strip. On click:</p>
<li>The horizontal strip sets its color directly.</li>
<li>The 1st vertical strip sets its color inside a requestAnimationFrame (raf) callback.</li>
<li>The 2nd vertical strip sets its color inside a raf, inside another raf.</li>
<li>The 3rd vertical strip sets its color within 7 layers of nested rafs.</li>
<p>Per <a href="https://medium.com/@paul_irish/requestanimationframe-scheduling-for-nerds-9c57f7438ef4"
style="color: white;">Paul Irish's article</a>,</p>
<li>Logic inside a single raf inside an event handler is guaranteed to run in the same frame as the event handler.
Which means the horizontal strip and the 1st vertical strip will <b>always</b> change color at the same time.
</li>
<li>Logic inside a raf wrapped with another raf is guaranteed (?) to run the next frame vs the event handler.
Which means the 2nd vertical strip should <b>always</b> change color slightly after the baseline (and thus 1st
vertical strip).
</li>
<p>However, we can observe that the 2nd vertical strip sometime changes color <b>at the same time</b> as the
horizontal and 1st vertical strip.</p>
<p>So either the callback inside a raf inside another raf (2nd vertical strip) is sometime not actually postponed to
the next frame
(rather, executed on the same frame as the event handler), or there's a missing browser repaint in-between frames.
We do see that 7x raf logic is properly postponed to... some frame.
</p>
<p>The ramifications of this test is either some hard-to-fix animation janks, or bad data race conditions.</p>
<p>Please ping <a href="https://x.com/_chenglou" style="color: white;">@_chenglou</a> if you have any insight!
Thanks</p>
</div>
<script>
const strip1 = document.getElementById('strip1')
const strip2 = document.getElementById('strip2')
const strip3 = document.getElementById('strip3')
const strip4 = document.getElementById('strip4')
const display1 = document.getElementById('display1')
const display2 = document.getElementById('display2')
const display3 = document.getElementById('display3')
strip1.style.background = strip2.style.background = strip3.style.background = strip4.style.background = '#4ECDC4'
let strip1State = 0, strip2State = 0, strip3State = 0, strip4State = 0
function changeColor(element, boxState) {
const colors = ['#FF6B6B', '#4ECDC4']
element.style.background = colors[boxState % colors.length]
}
let time1
window.addEventListener('click', () => {
time1 = performance.now()
changeColor(strip1, strip1State)
strip1State++
})
window.addEventListener('click', () => {
requestAnimationFrame((time2) => {
display1.innerText = `1x raf delay vs baseline: ${(time2 - time1).toFixed(1)}ms`
changeColor(strip2, strip2State)
strip2State++
})
})
window.addEventListener('click', () => {
requestAnimationFrame(() => {
requestAnimationFrame((time3) => {
display2.innerText = `2x raf delay vs baseline: ${(time3 - time1).toFixed(1)}ms`
changeColor(strip3, strip3State)
strip3State++
})
})
})
window.addEventListener('click', () => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
requestAnimationFrame((time4) => {
display3.innerText = `7x raf delay vs baseline: ${(time4 - time1).toFixed(1)}ms`
changeColor(strip4, strip4State)
strip4State++
})
})
})
})
})
})
})
});
</script>
</body>
</html>
@chenglou
Copy link
Author

chenglou commented Nov 5, 2024

Thanks nomsternom

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment