я бы использовал не setTimeout()
, a именно requestAnimationFrame()
Внутри известно прошедшее время и можно понять, какой кадр сейчас должен отображаться.
Надо запоминать, какой кадр сейчас отображён: надо ли его перерисовывать, или requestAnimationFrame()
успел сработать дважды во время одного кадра и перерисовки не требуется.
Так анимации будут жестко привязаны ко времени. Даже если из-за низкой производительности какой-то кадр окажется пропущен, общая скорость анимаций привязана ко времени, и рассинхрона не возникнет.
Вместо «торможения», замедления — могут проглатываться кадры. Но скорость движения персонажа в анимации останется прежней, хоть и менее плавной.