setInterval() с интервалом в 1 миллисекунду

Я пишу кликер на JS и столкнулся с тем, что если я с помощью setInterval() увеличиваю количество денег на 0,001 раз в миллисекунду (то есть справедливо сказать, что увеличиваю на 1 в секунду), то невооружённым глазом видно, что прирост значительно меньше, чем +1 в секунду

Смысл происходящего в том, чтобы обновлять раз в миллисекунду кол-во денег (чтобы был эффект бегущих циферок), а пользователю показывать сколько он получает денег в секунду с автокликов:

    <body>
        <h1>Money: <span id="money"></span></h1>
        <p><span id="per_sec"></span>/s</p>
        <button id="autoclick" onclick="increaseAutoclick()">+1/s</button>

        <script>
            const stats = {
                money: 0,
                autoclickValue: 0,
            };

            const moneyTag = document.querySelector("#money");
            const perSecondTag = document.querySelector("#per_sec");
            moneyTag.innerText = stats.money;
            perSecondTag.innerText = stats.autoclickValue;
В
            const increaseAutoclick = () => {
                stats.autoclickValue += 1;
                perSecondTag.innerText = stats.autoclickValue;
            };

            // увеличиваем количество денег на 0,001 раз в миллисекунду
            setInterval(() => {
                stats.money += stats.autoclickValue * 0.001;
                moneyTag.innerText = stats.money.toFixed(2);
            }, 1);
        </script>
    </body>

Методом научного тыка я обнаружил, что прирост становится ровно +1 в секунду, если добавлять раз в миллисекунду не 0.001, а 0.004:

setInterval(() => {
    stats.money += stats.autoclickValue * 0.004;
    moneyTag.innerText = stats.money.toFixed(2);
}, 1);

В чём причина? В точности setInterval(), её асинхронности или может аппроксимации десятичных дробей в JS? И возможно есть лучшие способ реализовать такое, не через setInterval()?

Хочется чётко понимать такие базовые отношения с числами, заранее спасибо!


Ответы (1 шт):

Автор решения: Stanislav Volodarskiy

Используйте requestAnimationFrame для анимаций. Он вызывается с той скоростью, с которой браузер способен обновлять экран. Обновлять значения чаще нет смысла. Интервал вызова может оказаться неравномерным, надо написать функцию которая будет менять экран в зависимости от прошедшего времени:

const counter = document.getElementById('counter').firstChild;

const start = performance.now();

const frame = () => {
    const dt = (performance.now() - start) / 1000;
    counter.nodeValue = dt.toFixed(2);
    requestAnimationFrame(frame);
};
requestAnimationFrame(frame);
<span id="counter">0</span>

→ Ссылка