It’s been quite a while since my last post in this series. I know some of you have been waiting patiently for the next part of our render engine journey. Life’s been keeping me busy, but I’m excited to finally continue where we left off.
In the previous posts, we covered Write a simple 2D render engineⅡ, and today we’re going to dive into smooth interactive events. Let’s get started!
When we talk about smooth animations in the browser, we’re typically aiming for 60 frames per second (60fps), which means each frame has approximately 16.7ms to complete. While traditional film uses 24fps, web animations require higher framerates to feel smooth and responsive. This is especially important for interactive elements where users expect immediate feedback.
A key concept to remember:
- 60fps: The ideal target for smooth web animations
- 16.7ms: The time budget for each frame
- requestAnimationFrame: The browser’s native API for smooth animations
Let’s see how we can implement this in our render engine…
Smmoth Rendering
Here is a simple requestAnimationFrame
usage case
let caller = 0
let animationID = undefined
function animationEvent() {
if (caller >= 100) {
window.cancelAnimationFrame(animationID)
caller = 0
return
}
console.log('smooth animation')
caller++
animationID = window.requestAnimationFrame(animationEvent)
}
animationID = window.requestAnimationFrame(animationEvent)
And now that we understand how to used requestAnimationFrame
, remember that cache system we built in the last part? If it’s a bit fuzzy, no worries - just hop back to the previous chapter to catch up!
let animationFrameId: number | null = null
let move = false
const cache = new RenderCache()
function render() {
if (move) {
if (cache.canvas) {
const frame = doRender(cache) // fake func only draw the cache result into main canvas.
// ... like position translate
animationFrameId = window.requestAnimationFrame(render)
}
} else {
window.cancelAnimationFrame(animationFrameId)
animationFrameId = null
}
move = false
}
function onmousemove() {
move = true
if (!animationFrameId) {
animationFrameId = window.requestAnimationFrame(render)
}
}
Thanks to our cache system, we can avoid repeating heavy node calculations. Since Canvas performs direct drawing, sending a single draw command is more efficient - pretty neat, right?
Okay, The journey is short. In the next chapter, we’ll learn how to use AABB to optimization the treemap component render :D