Update: Although, having a closer look at the scene, I see its pixel art, so I bet the author is snapping floating point positions to a pixel point to prevent sub pixel blurring.
Another small update: I was sure requestAnimationFrame was locked to 60fps, but I noticed on Chrome the other day it was 144hz, the full speed of my monitor.
But I think it's more meant to demonstrate drawing parallax layers rather than subpixel scrolling.
TL;DR: if you base your animation- or scroll-speed on the 'raw' measured time between two frames, you'll get micro-stutter because it's pretty much impossible to obtain a non-jittery frame duration on modern operating systems or web browsers, all you can do is try to remove the noise via filtering, or 'align' your measured frame duration with the display refresh interval, which on some platforms cannot be queried.
In web browsers the most important problem is that you can't measure the exact frame duration (which is fallout from Spectre/Meltdown), or obtain a precise 'presentation timestamp', or even query the display refresh frequency.
Even in the native OS APIs that provide a presentation timestamp (like DXGI on Windows or CVDisplayLink on macOS) that timestamp has considerable jitter and has not much to do with the actual presentation time when the frame becomes visible to the user.
And as soon as you base your animation timings on such a jittery timestamp you'll get micro-stutter (the easiest way to get smooth animation is actually to assume a fixed frame duration, but then your animation speed will be tied to the display refresh rate).
It's often possible to eliminate the timing jitter with 'noise removal' filters or just tracking an average over the last couple dozen frames, but those then may behave funny in situations where the frame duration changes drastically (such as moving a window between displays with different refresh rate, or when rendering stops and then resumes because the window or browser tab is fully obscured and then becomes visible again).
PS: Raylib's frame pacing code on the web is also a bit on the crude side [2].
...e.g. it just sleeps for 16 milliseconds, and relies on ASYNCIFY to enable a traditional render loop in browsers. It would actually be better to use a frame callback via requestAnimationFrame (or the Emscripten wrapper function emscripten_request_animation_frame), but this means giving up the cross-platform 'own the game loop' application model. Not that requestAnimationFrame alone solves any of the above mentioned time jitter problems though.
[1] https://medium.com/@alen.ladavac/the-elusive-frame-timing-16...
[2[ https://github.com/raysan5/raylib/blob/f1007554a0a8145060797...
Do you have any comment whether frame blending (actually using a mix of two frame states to produce the rendering) would be a workable solution?
One thing that's always bothered me about Wasm and browser 3d/2d graphics is that I often find minor issues such as scrolling. Look at the example called "Background scrolling & parallax" here: https://www.raylib.com/examples.html
I've tested on several devices and it's definitely not smooth scrolling, unless there's something wrong with my eyes.
How can 2D smooth scrolling not be a solved problem in 2024?