предисловие
Вот так, однажды я увидел пост, когда ходил по магазинамИспользуйте 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)
Щенок появился!
Спрайт Художник
Спрайт-объект не должен сам завершать отрисовку, наоборот, он делегирует операцию отрисовки другому объекту для реализации. то есть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
метод, параметрами являются спрайт и продолжительность.
Анимация длится две секунды (используя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]);
добавить небольшую деталь
Наконец, добавление фоновой анимации идеально, эта часть не сложна. Ставить по разным тарифамoffset
,сотрудничатьtranslate
Функция умеет создавать эффект отодвигающегося фона.
использоватьmouseenter
Метод, эффект анимации выполняется только при перемещении мыши в
использоватьmousedown
метод, выполнение поведения при щелчке мыши
Сотрудничатьscale(-1, 1)
进行镜像处理,来实现向左向右移动。 Примечательно,Canvas
Есть большая проблема с позиционированием элементов посередине. Это потому чтоCanvas
Система преобразования координат иCSS
Это не то же самое, поэтому, если вы хотите добиться эффекта отражения по центру, вам нужно перед отражением переместить центральную точку целевого элемента на ось преобразования.
ctx.translate(dog.left, 0)
ctx.scale(-1, 1)
ctx.translate(- dog.left - dog.width, 0)
Конкретный код относительно прост, поэтому он не будет опубликован.Студенты могут попробовать его самостоятельно.
конец
Больше статей, пожалуйста, переместитеГитхаб арендодателя, Если вам это нравится, пожалуйста, нажмите звездочку, это также поощрение автора.