Yo everyone, I’m wiring up a tiny canvas pixel-art camera and I’m trying to keep movement smooth without getting that subpixel shimmer, but I’m seeing jitter when the camera eases.
Your snapping math is fine-ish, but the jitter is coming from when you round: Math. round(cam. x) will sometimes flip a pixel a frame earlier/later during easing, so you get that “1px back-and-forth” feel when you hover around . 5 boundaries. I’d keep cam. x/y as full floats for the sim/easing, and only quantize the screen-facing camera offset right before you apply it. Use Math. floor (or a consistent bias) instead of round so it doesn’t toggle. Think “camera is analog, screen is a pixel grid” — only the value you feed into the transform should be snapped.
If you want “round to nearest” without the flip-flop, you can do a tiny biased round like Math. floor(cam. x + 0. 00001), or keep a separate renderX/renderY that only updates when you cross an integer, but the simple floor version usually kills the shimmer fast.
Snapping the camera won’t save you if your sprites are still landing on fractional pixels at draw time.
I’ve had this exact “why is it still shimmering??” moment because of one innocent +0.5 for centering, or a sprite origin that isn’t an integer, or entities moving on floats while only the camera gets quantized. Keep the sim in floats, but make the final coords you feed into drawImage integers in world pixels (then scale), otherwise you’ve just moved the jitter downstream.
// sim in floats
entity.x += entity.vx;
entity.y += entity.vy;
// camera snapped for render
const camX = Math.floor(cam.x);
const camY = Math.floor(cam.y);
// snap final draw coords too (world pixels)
const drawX = Math.floor(entity.x - camX);
const drawY = Math.floor(entity.y - camY);
ctx.drawImage(sprite, drawX, drawY);
The half-pixel gremlin is real. One stray helper offset and you’re back in subpixel land even though the camera math looks “fine”.