Skip to content

Instantly share code, notes, and snippets.

@chenglou
Created November 5, 2024 08:47
Show Gist options
  • Select an option

  • Save chenglou/8af5467b47dd4cf58745cfe950bec13f to your computer and use it in GitHub Desktop.

Select an option

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

chenglou commented Nov 5, 2024

Copy link
Copy Markdown
Author

Thanks nomsternom

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