Анимация холста - бег по траве под голубым небом и белыми облаками🐶

внешний интерфейс Canvas
Анимация холста - бег по траве под голубым небом и белыми облаками🐶

предисловие

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

тогда я используюcanvasСпрайт воспроизвел его, и спрайт можно использовать во многих сценариях.canvasанимация. Давайте представим:

спрайт(sprite)нетCanvasопределенныйAPI, который является графическим объектом, который абстрагирует поведение анимации и методы производства. Ниже вы увидите, как перемещать спрайты, не затрагивая анимированный фон, и придавать им различные поведения, такие как: добавление поведения медленного хождения к объекту 🐶, которое превращается в бег по определенному механизму. Такое поведение может повторяться бесконечно, проявляться в течение определенного периода времени или на расстоянии или изменять свой внешний вид с течением времени.

спрайт

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

Итак, объект спрайта(sprite)содержать два метода

  • paint
  • update

paintэто метод рисования спрайтов, иupdateОн используется для выполнения поведения спрайта.

Как упоминалось выше, это графический объект, который абстрагирует поведение анимации и методы производства, а это означает, что эти два метода спрайта являются общими методами для абстрагирования каждого объекта спрайта.paintМетод выполняет отрисовку спрайта, который передается в рисовальщик(painter)сделать это, потому что некоторые спрайты генерируются из изображений, а другиеcanvasнарисованы, и все они имеют свойства имени, размера и положения. а такжеupdateметод заключается в выполненииbehaviorsМассив объектов, каждый объект в массиве будет начинаться сexecuteспособ сделать что-то со спрайтом. Получается базовая абстракция спрайта:

// 下面代码只是用于说明思路,实现具体看读者个人风格。
class Sprite {
    // 接受名称,绘制器和行为对象数组
    constructor(name, painter, behaviors) {
        this.name = name;
        this.painter = painter;
        this.behaviors = behaviors;
        // 一些默认的属性
        this.left = 0;
        this.top = 0;
        this.width = 10;
        this.height = 10;
    }
    paint (ctx) {
        this.painter.paint(this, ctx)
    },
    update (ctx, ...args) {
        for (var i = this.behaviors.length; i > 0; --i) {
            this.behaviors[i-1].execute(this, ctx, ...args)
        }
    }
}

использоватьSpriteобъект для создания собаки.

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const dog = new Sprite('dog', {
    painter: function (sprite, ctx) {
        dogImg = new Image();
        dogImg.src = require('./assets/dog/dog1.png');
        dogImg.onload = function () {
            ctx.drawImage(dogImg, sprite.left, sprite.top, sprite.width, sprite.height)
        }
    },
    []
});
dog.paint(ctx)

Щенок появился!image.png

Спрайт Художник

Спрайт-объект не должен сам завершать отрисовку, наоборот, он делегирует операцию отрисовки другому объекту для реализации. то естьSpriteОбъект отделяется от объекта рендерера. Таким образом, ящик может быть динамически установлен для объекта спрайта во время работы программы, что значительно повышает гибкость программы. Это также практическое применение шаблона стратегии.

этоdemo, используется визуализатор изображений. Объект изображения содержит ссылку на объект изображения, который рисует это изображение вpaint()метод переданного объекта среды рисования.

export class ImagePainter {
    constructor (img) {
        this.image = new Image();
        this.image.src = img;
    }
    paint (sprite, ctx) {
        if (this.image !== undefined) {
            if (!this.image.complete) {
                this.image.onload = function () {
                    sprite.width = this.width;
                    sprite.height = this.height;
                    ctx.drawImage(this, sprite.left, sprite.top, sprite.width, sprite.height)
                }
            } else {
                ctx.drawImage(this.image, sprite.left, sprite.top, sprite.width, sprite.height)
            }
        }
    }
}

Есть два случая:

  • Когда изображение загружено не полностью, создайте обертку функцииdrawImageметод и назначьте эту функциюimage.onload, который будет выполняться при загрузке изображенияonloadметод. Таким образомdrawImageнарисуй этоcanvasначальство.
  • Когда изображение загружено, выполнить напрямуюdrawImageметод.

При создании ящика изображений вам необходимо передать ссылку на URL-адрес изображения вImagePainterКонструктор. Ящик изображения выполняется только тогда, когда изображение полностью загружено.paint()Нарисуй это.

const dog = new Sprite('dog', new ImagePainter('dog1.png'), []);
function animate () {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    dog.paint(ctx);
    window.requestAnimationFrame(animate);
}
animate();
dog.paint(ctx);

Поскольку спрайт используется в эффекте анимации, он не только рисуется один раз, а затем останавливается, но объект спрайта должен рисоваться неоднократно. Таким образом, если изображение не было загружено при вызове рисовальщика изображений, этот метод не будет выполнять никаких операций, и спрайт не будет отображаться до тех пор, пока он не будет полностью загружен.

Поскольку спрайт для достижения эффекта анимации не может иметь только одно изображение, динамический эффект может отображаться только под действием нескольких изображений. Итак, нужен аниматор спрайтов --SpriteAnimator

SpriteAnimator

SpriteAnimatorОбъект используется для управления анимационным изображением спрайта, который содержит массив, каждый элемент массива является реализациейpaintМетоды объектов, которые можно нарисовать через вас. У каждого объекта спрайта есть рисовальщик спрайтов, предназначенный для его рисования.

SpriteAnimatorВремя от времени объект выбирает объект ящика по порядку из объекта массива и использует его для рисования спрайта. Итак, созданиеSpriteAnimatorобъект, передайте конструктору массив ящиков спрайтов.SpriteAnimator.startМетод используется для воспроизведения анимации, принимая объект спрайта для воспроизведения и количество миллисекунд для поддержания анимации.

class SpriteAnimator {
    constructor (painters, elapsedCallback) {
        this.painters = painters;
        this.elapsedCallback = elapsedCallback;
        this.painter = [];
        this.timerList = [];
        this.duration = 1000;
        this.startTime = 0;
        this.index = 0;
    }
    start (sprite, duration) {
        let endTime = +new Date() + duration;
        let period = duration / this.painters.length;
        let interval = undefined;
        let originalPainter = sprite.painter

        this.index = 0;
        sprite.animating = true;
        sprite.painter = this.painters[this.index];

        interval = setInterval(() => {
            if (+new Date() < endTime) {
                sprite.painter = this.painters[++this.index]
            } else {
                this.end(sprite, originalPainter);
                clearInterval(interval)
            }
        }, period)
    }
    end (sprite, orginalPainter) {
        sprite.animating = false;
        this.elapsedCallback ? this.elapsedCallback(sprite) : sprite.painter = orginalPainter;
    }
}

Чтобы воспроизвести эффект анимации,SpriteAnimatorобъектstartМетод должен добавить продолжительность анимации к текущему времени, чтобы рассчитать время остановки анимации. Затем в зависимости от продолжительности анимации и необходимости отрисовкиpaintersДлина массива для расчета «периода» анимацииperiod, которое представляет собой время отображения, отведенное для каждого анимированного изображения. использоватьsetIntervalкperiodЗамена спрайтов для циклаpainterСотрудничатьrequestAnimationFrameОтображение текущего изображения в течение указанного времени и вызов по достижении указанного времениclearIntervalОстановить воспроизведение анимации.

наконецSpriteAnimator.startПосле того, как метод завершит воспроизведение, вызовитеendметод, чтобы показать финалpainter.

увидеть эффект,

const dog = new Sprite('dog', new ImagePainter('dog1.png'), []);
let dogPainterList = ['dog1.png', 'dog2.png', 'dog3.png', 'dog4.png', 'dog5.png', 'dog6.png', 'dog7.png', 'dog8.png'].map(item => new ImagePainter(item));
let dogAnimator = new SpriteAnimator(dogPainterList)
function animate () {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    dog.paint(ctx);
    window.requestAnimationFrame(animate);
}
animate();
dog.paint(ctx);
dogAnimator.start(dog, 2000);

1. Сначала создайте массив изображений для воспроизведения, состоящий из ряда ящиков изображений.
2. Создайте массив изображений в качестве параметраSpriteAnimatorобъект
3. ЗвонокSpriteAnimatorобъектstartметод, параметрами являются спрайт и продолжительность.
dialog4.gifАнимация длится две секунды (используяmouseenterметод, эффект анимации выполняется только при перемещении мыши)

behavior

Чтобы придать собаке ускоренное поведение, сначала определите это поведение:

  • только тогда, когдаmousedownЭто поведение выполняется только тогда, когдаmousedownположение, расчетcanvasАдрес 222222222222222222.
  • Сравните положение спрайта с позицией щелчка, и если оно меньше, чем положение щелчка, бегите быстрее.
const accelerate = {
    velocityX: 1,
    execute: function(sprite, ctx, pos) {
        this.velocityX = 1; // 重置加速度
        if (sprite.left + sprite.right < pos) {
            sprite.left += this.velocityX;
        } else {
            this.velocityX = 0;
        }
    }
}
const dog = new Sprite('dog', new ImagePainter('dog1.png'), [accelerate]);

dialog5.gif

добавить небольшую деталь

Наконец, добавление фоновой анимации идеально, эта часть не сложна. Ставить по разным тарифамoffset,сотрудничатьtranslateФункция умеет создавать эффект отодвигающегося фона. использоватьmouseenterМетод, эффект анимации выполняется только при перемещении мыши вdialog6.gif

использоватьmousedownметод, выполнение поведения при щелчке мышиdialog7.gif

Сотрудничатьscale(-1, 1)进行镜像处理,来实现向左向右移动。 Примечательно,CanvasЕсть большая проблема с позиционированием элементов посередине. Это потому чтоCanvasСистема преобразования координат иCSSЭто не то же самое, поэтому, если вы хотите добиться эффекта отражения по центру, вам нужно перед отражением переместить центральную точку целевого элемента на ось преобразования.

ctx.translate(dog.left, 0)
ctx.scale(-1, 1)
ctx.translate(- dog.left - dog.width, 0)

dialog8.gif

Конкретный код относительно прост, поэтому он не будет опубликован.Студенты могут попробовать его самостоятельно.

конец

Больше статей, пожалуйста, переместитеГитхаб арендодателя, Если вам это нравится, пожалуйста, нажмите звездочку, это также поощрение автора.