Good list — one addition that made a surprisingly large difference for us: moving the game loop itself into a Web Worker with OffscreenCanvas. Decoupling the simulation and render tick from the main thread means GC pauses and DOM activity (ads, analytics, whatever) stop spiking your frame time.
The setup is more involved than it sounds — you need to transferControlToOffscreen() and message-pass input events from the main thread — but once it's wired up, frame pacing becomes dramatically more consistent. We went from occasional 40ms spikes to a much flatter profile on mid-range hardware.
Also worth profiling: canvas.getContext('2d') vs WebGL even for 2D games. If you're doing a lot of image blitting with transforms, WebGL sprite batching can be a significant win over the 2D context API, especially on mobile where the GPU is underutilized and the CPU is the bottleneck.
