Анимация

Для того чтобы что то двигалось по канвасу, нам нужен анимационный цикл.
Анимационный цикл (animation loop) это функция вызванная в интервале. Она используется для создания и перерисовки холста с обновлениями.

Вот порядок создания анимационного цикла:
— Сначала мы очищаем холст
— Далее мы сохраняем состояние (при необходимости)
— Далее отрисовываем обновление
— Далее восстанавливаем состояние (при необходимости)

Делаем цикл из этих шагов.

Если рассмотреть подробнее. Сначала мы обновляем всё, включая позицию.
Затем мы очищаем холст и что-нибудь рисуем на нем. Затем возвращаем всё на холст.
Это три главных шага анимации. И вконце мы циклируем эти шаги снова и снова.

Лучше всего понять их на примере. Нарисуем прямоугольник и затем обновим его цвет рандомно.

Мы можем запустить анимацию с помощью setInterval или setTimeout — но это НЕПРАВИЛЬНЫЙ способ анимации в canvas.

setTimeout(drawRandomColoredRectangle, 500);

    drawRandomColoredRectangle(230, 107, 400, 250);

    function drawRandomColoredRectangle(x,y,w,h) {
        let color, fillOpacity, fillColor, borderColor;

        color = createRandomRGBColor();
        fillOpacity = Math.random();
        fillColor = `rgba(${color.r},${color.g},${color.b},${fillOpacity})`;
        borderColor = `rgb(${color.r},${color.g},${color.b})`;

        // Random data
        x = getRandomInt(0, canvas.width);
        y = getRandomInt(0, canvas.height);
        w = getRandomInt(0, canvas.width - x);
        h = getRandomInt(0, canvas.height - y);

        //Draw rectangle
        context.beginPath();
        context.fillStyle=fillColor;
        context.strokeStyle=borderColor;
        context.rect(x, y, w, h);
        context.stroke();
        context.fill();

        setTimeout(drawRandomColoredRectangle, 1000);
    }

    function createRandomRGBColor() {
        return {
            r:getRandomInt(0,257),
            g:getRandomInt(0,257),
            b:getRandomInt(0,257)
        }
    }

    function getRandomInt(min, max) {
        min = Math.ceil(min);
        max = Math.floor(max);
        return Math.floor(Math.random() * (max - min)) + min;
    }

А вот правильный.
Для анимации на холсте используется специальная функция — requestAnimationFrame

window.requestAnimationFrame = (function() {
    return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFram ||
    window.msRequestAnimationFram ||
    function(callback) {
        window.setTimeout(callback, 1000 / 60);
    };
})();

Этот метод специально разработан, чтобы анимировать на канвасе наиболее плавно и эффективно насколько это возможно. Это наиболее оптимизированный способ создать анимацию на холсте.
Для того чтобы начать использовать эту функцию, нужно прописать её вызов как снаружи функции, так и внутри.

Но в отличие от setTimeout и setInterval, мы не можем установить частоту кадров.
Для отрисовки будем использовать функцию requestAnimationFrame — она принимает единственный аргумент — callback — функцию, которая будет вызвана, когда холст будет готов отрисовать изображение. Теперь браузер устанавливает оптимальную частоту кадров.
Для управления скоростью анимации, потребуется добавить еще одно условие в нашу функцию.

window.requestAnimationFrame(drawRandomColoredRectangle);

    function drawRandomColoredRectangle(x, y, w, h) {
        let color, fillOpacity, fillColor, borderColor, now;

        color = createRandomRGBColor();
        fillOpacity = Math.random();
        fillColor = `rgba(${color.r},${color.g},${color.b},${fillOpacity})`;
        borderColor = `rgb(${color.r},${color.g},${color.b})`;

        // Random data
        x = getRandomInt(0, canvas.width);
        y = getRandomInt(0, canvas.height);
        w = getRandomInt(0, canvas.width - x);
        h = getRandomInt(0, canvas.height - y);

        now = new Date();
        if (now - start >= 1000) {
            start = now;

            //Clear canvas
            context.clearRect(0, 0 ,canvas.width, canvas.height)

            //Draw rectangle
            context.beginPath();
            context.fillStyle = fillColor;
            context.strokeStyle = borderColor;
            context.rect(x, y, w, h);
            context.stroke();
            context.fill();

            console.log(start);

        }

        window.requestAnimationFrame(drawRandomColoredRectangle);

    }

    console.log(start);

    function createRandomRGBColor() {
        return {
            r: getRandomInt(0, 257),
            g: getRandomInt(0, 257),
            b: getRandomInt(0, 257)
        }
    }

    function getRandomInt(min, max) {
        min = Math.ceil(min);
        max = Math.floor(max);
        return Math.floor(Math.random() * (max - min)) + min;
    }

    window.requestAnimationFrame = (function (callback) {
        return window.requestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFram ||
            window.msRequestAnimationFram ||
            function (callback) {
                console.log('callback');
                window.setTimeout(callback, 1000 / 60);
            };
    })();

А сейчас рассмотрим пример создании анимации на основе анимации спрайта.

const tile = new Image();
    tile.src = './tile.png';

    tile.onload = () => {
        context.drawImage(tile, 0, 0, 768, 200, 60, 0, 768, 200);

        let cellWidth = 192;
        let cellHeight = 200;

        drawTileCell(2);

        function drawTileCell(index) {
            context.drawImage(tile, index * cellWidth, 0, cellWidth, cellHeight, 360, 300, cellWidth, cellHeight);
        }

        requestAnimationFrame(animationLoop);

        let counter = 0;
        let start = new Date();

        function animationLoop() {
            let now = new Date();
            if (now - start >= 100) {
                start = now;
                //Clear
                context.clearRect(0, 200, canvas.width, canvas.height);

                //Update counter
                counter++;
                counter %= 4;

                // Draw
                drawTileCell(counter);

            }
            //Animate
            requestAnimationFrame(animationLoop);
        }