Changhui Xu.
Сегодня мы собираемся создать анимацию холста с довольно цветущими цветами, шаг за шагом . Вы можете следить, играя в Packblitz Projects в этом посте в блоге, и вы можете проверить исходный код в Этот Github Reppo Отказ
В моем недавнем Блог пост Я описал вид высокого уровня сочинения холстовских анимаций с помощью Tymdercript. Здесь я представлю подробный процесс того, как моделировать объекты и как анимировать их на холсте.
Оглавление
- Рисовать цветы
- Оживленные цветы
- Добавить взаимодействия в анимацию
Рисовать цветы
Первые вещи сначала мы должны иметь функцию, чтобы нарисовать цветы на холсте. Мы можем сломать части цветок до лепестки и Центр (Pistil и Stamen). Цветочный центр может быть абстрагирован как круг, наполненный некоторым цветом. Лепестки растут по центру, и их можно нарисовать вращение холста с определенной степенью симметрии.
Обратите внимание, что смелые существительные ( цветок , лепесток , центр ) подразумевает модели в коде. Мы собираемся определить эти модели, определив свои свойства.
Давайте сначала сосредоточимся на рисунке одного лепестка с некоторыми абстракциями. Вдохновлен Это руководство , мы знаем, что лепестка может быть представлена двумя квадратичные кривые и два Безье кривые . И мы можем нарисовать эти кривые, используя Квадратичное () и Безеркурвето () Методы в HTML Canvas API.
Как показано на рисунке 1 (1), квадратичная кривая имеет начальную точку, конечную точку и одну контрольную точку, которая определяет кривую кривой. На рисунке 1 (2) кривая Bézier имеет начальную точку, конечную точку и два контрольных точка.
Для того, чтобы Гладко подключите Две кривые (любые две кривые, квадратичные или бежеирные или другие), нам нужно убедиться, что точка подключения и два близлежащих точка управления находятся на одной линии, так что эти Две кривые имеют одинаковую кривизну на точке соединения Отказ
Рисунок 1 (3) показывает основную форму лепестки, состоящую из двух квадратичных кривых (зеленых) и двух кривых Безии (синий). Существует 4 красных точка, представляющие вершины лепестков и 6 синих точек, представляющих контрольные точки кривых.
Нижняя красная вершина – это центральная точка цветов, а верхняя красная вершина – это цветка лепесток. Средние две красные вершины представляют собой радиус лепестка. И угол между этими двумя вершинами против центральной точки называется угла лета. Вы можете играть с Этот проект Stackblitz о лепестке.
После определения формы лепестки мы можем заполнить форму цветком и получить лепесток, как показано на рисунке 1 (4). С помощью информации выше, мы хорошим, чтобы записать нашу первую модель объекта: Лепесток Отказ
export class Petal {
private readonly vertices: Point[];
private readonly controlPoints: Point[][];
constructor(
public readonly centerPoint: Point,
public readonly radius: number,
public readonly tipSkewRatio: number,
public readonly angleSpan: number,
public readonly color: string
) {
this.vertices = this.getVertices();
this.controlPoints = this.getControlPoints(this.vertices);
}
draw(context: CanvasRenderingContext2D) {
// draw curves using vertices and controlPoints
}
private getVertices() {
// compute vertices' coordinates
}
private getControlPoints(vertices: Point[]): Point[][] {
// compute control points' coordinates
}
}Вспомогательный Точка класс в Лепесток определяется следующим образом. Координаты используют целые числа (через Math.floor () ), чтобы сохранить некоторые вычислительные мощности.
export class Point {
constructor(public readonly x = 0, public readonly y = 0) {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
}
}Представление Цветочный центр Может быть параметризован его центральной точкой, радиусом кругов и цветом. Таким образом, скелет Цветокцентр класс выглядит следующим образом:
export class FlowerCenter {
constructor(
private readonly centerPoint: Point,
private readonly centerRadius: number,
private readonly centerColor: string
) {}
draw(context: CanvasRenderingContext2D) {
// draw the circle
}
}Поскольку у нас есть лепесток и цветочный центр, мы готовы двигаться вперед, чтобы нарисовать цветок, который содержит центральный круг и несколько лепестков с той же формой.
От объектной ориентированной перспективы, Цветок может быть построен как Новый цветок (центр: цветокцентр, лепестки: лепесток []) или как Новый Цветок (Центр: Цветочница, Номераффалы: Номер, лепесток: лепесток) . Я использую второй способ, потому что для этого сценария не требуется массив.
В конструкторе вы можете добавить некоторые проверки, чтобы обеспечить целостность данных. Например, бросить ошибку, если Center.CenterPoint не совпадает Petal.CenterPoint.
export class Flower {
constructor(
private readonly flowerCenter: FlowerCenter,
private readonly numberOfPetals: number,
private petal: Petal
) {}
draw(context: CanvasRenderingContext2D) {
this.drawPetals(context);
this.flowerCenter.draw(context);
}
private drawPetals(context: CanvasRenderingContext2D) {
context.save();
const cx = this.petal.centerPoint.x;
const cy = this.petal.centerPoint.y;
const rotateAngle = (2 * Math.PI) / this.numberOfPetals;
for (let i = 0; i < this.numberOfPetals; i++) {
context.translate(cx, cy);
context.rotate(rotateAngle);
context.translate(-cx, -cy);
this.petal.draw(context);
}
context.restore();
}
}Обратите внимание на лепеталы (контекст) метод. Поскольку вращение вокруг центральной точки цветка, нам нужно сначала перевести холст, чтобы переместить начало начала цветочного центра, затем повернуть холст. После вращения нам нужно перевести обратно на холст, чтобы начало предыдущего (0, 0).
Используя эти модели ( цветок , Цветокцентр , Лепесток ), мы можем получить цветок, похожий на рисунок 1 (5). Чтобы сделать цветок более бетоном, мы добавляем некоторые теневые эффекты, так что цветок выглядит как один на рисунке 1 (6). Вы также можете играть с Проект Stackblitz ниже Отказ
Оживленные цветы
В этом разделе мы собираемся оживить процесс цветущего цветов. Мы смоделируем цветущий процесс как растущий радиус лепестков с течением времени. На рисунке 2 показана окончательная анимация, в которой лепестки цветов расширяются на каждом кадре.
Прежде чем мы выполним фактические анимации, мы можем захотеть добавить несколько сортов для цветов, чтобы они не скучны. Например, мы можем генерировать случайные точки на холсте для разброса цветов, мы можем генерировать случайные формы/размеры цветов, и мы можем нарисовать случайные цвета для них. Такая работа обычно осуществляется в определенном обслуживании с целью централизации логики и повторного использования кода. Затем мы поставьте логику рандомизации в Flowerrandomizationsurvice класс.
export class FlowerRandomizationService {
constructor(){}
getFlowerAt(point: Point): Flower {
... // randomization
}
... // other helper methods
}Тогда мы создаем Bloomingflowers Класс для хранения массива цветов, созданных Flowerrandomizationsurvice Отказ
Чтобы сделать анимацию, мы определяем метод Priverteptalradius () в Цветок Класс для обновления объектов цветов. Затем, позвонив Window.RequestanimationFrame (() => Это.animateflowers ( )); В BloomingFlow Класс, мы планируем перерезаться на холсте на каждом кадре. И цветы обновляются v Ia Flower.increasepetalradius (); во время каждого перерисования. Инструпта из кода ниже показывает голый минимальный анимационный класс.
export class BloomingFlowers {
private readonly context: CanvasRenderingContext2D;
private readonly canvasW: number;
private readonly canvasH: number;
private readonly flowers: Flower[] = [];
constructor(
private readonly canvas: HTMLCanvasElement,
private readonly nFlowers: number = 30
) {
this.context = this.canvas.getContext('2d');
this.canvasWidth = this.canvas.width;
this.canvasHeight = this.canvas.height;
this.getFlowers();
}
bloom() {
window.requestAnimationFrame(() => this.animateFlowers());
}
private animateFlowers() {
this.context.clearRect(0, 0, this.canvasW, this.canvasH);
this.flowers.forEach(flower => {
flower.increasePetalRadius();
flower.draw(this.context);
});
window.requestAnimationFrame(() => this.animateFlowers());
}
private getFlowers() {
for (let i = 0; i < this.nFlowers; i++) {
const flower = ... // get a randomized flower
this.flowers.push(flower);
}
}
}Обратите внимание, что обратно обратно в window.requestanimationFrame (() => Это. Используется синтаксис функции стрелки, который необходим для сохранения Это контекст текущего класса объекта.
Приведенный выше кодовый фрагмент приведет к постоянному увеличению длины лепестки цветка, потому что у него нет механизма, чтобы остановить эту анимацию. В демонстрационном коде я использую Setimeate () Обратный вызов для завершения анимации через 5 секунд. Что, если вы хотите рекурсивно играть в анимацию? Простое решение демонстрируется в Проект Stackblitz ниже , что использует SetInterval () Обратный вызов для воспроизведения анимации каждые 8 секунд.
Это круто. Что еще мы можем сделать на холсте анимации?
Добавить взаимодействия в анимацию
Мы хотим, чтобы холст был отзывчивым к событиям клавиатуры, события мыши или сенсорных событий. Как? Верно, добавьте слушателей событий.
В этой демонстрации мы собираемся создать интерактивный холст. Когда мышь нажимает на холсте, цветет цветок. Когда вы нажимаете на другой точку на холсте, еще один цветок цветов. При удерживании ключа Ctrl и нажав, холст будет очиститься. На рисунке 3 показана окончательная анимация холста.
Как обычно, мы создаем класс InteractiveFlowers держать массив цветов. Кодовый фрагмент InteractiveFlowers класс выглядит следующим образом.
export class InteractiveFlowers {
private readonly context: CanvasRenderingContext2D;
private readonly canvasW: number;
private readonly canvasH: number;
private flowers: Flower[] = [];
private readonly randomizationService =
new FlowerRandomizationService();
private ctrlIsPressed = false;
private mousePosition = new Point(-100, -100);
constructor(private readonly canvas: HTMLCanvasElement) {
this.context = this.canvas.getContext('2d');
this.canvasW = this.canvas.width;
this.canvasH = this.canvas.height;
this.addInteractions();
}
clearCanvas() {
this.flowers = [];
this.context.clearRect(0, 0, this.canvasW, this.canvasH);
}
private animateFlowers() {
if (this.flowers.every(f => f.stopChanging)) {
return;
}
this.context.clearRect(0, 0, this.canvasW, this.canvasH);
this.flowers.forEach(flower => {
flower.increasePetalRadiusWithLimit();
flower.draw(this.context);
});
window.requestAnimationFrame(() => this.animateFlowers());
}
private addInteractions() {
this.canvas.addEventListener('click', e => {
if (this.ctrlIsPressed) {
this.clearCanvas();
return;
}
this.calculateMouseRelativePositionInCanvas(e);
const flower = this.randomizationService
.getFlowerAt(this.mousePosition);
this.flowers.push(flower);
this.animateFlowers();
});
window.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.which === 17 || e.keyCode === 17) {
this.ctrlIsPressed = true;
}
});
window.addEventListener('keyup', () => {
this.ctrlIsPressed = false;
});
}
private calculateMouseRelativePositionInCanvas(e: MouseEvent) {
this.mousePosition = new Point(
e.clientX +
(document.documentElement.scrollLeft ||
document.body.scrollLeft) -
this.canvas.offsetLeft,
e.clientY +
(document.documentElement.scrollTop ||
document.body.scrollTop) -
this.canvas.offsetTop
);
}
}Мы добавляем слушатель события для отслеживания событий щелчков мыши и положение мыши. Каждый клик добавит цветок в массив цветов. Поскольку мы не хотим, чтобы цветы развернулись до бесконечности, мы определяем метод PriverteptalradiuswithLimit () в Цветок Класс для увеличения радиуса лепестка до приращения 20. Таким образом, каждый цветок расцветает сам по себе и прекратит цветущий после того, как растиус лепесток увеличился на 20 единиц.
Я установил частный член Остановка В цветке, чтобы оптимизировать анимацию, так что анимация остановится, когда все цветы закончили цветущие.
Мы также можем слушать keyup. / КДУЩЬЮ События и добавьте элементы управления клавиатурой в холст. В этой демонстрации содержимое Canvas будет очищено, когда пользователь удерживает клавишу Ctrl и щелкнул мышью. Ключевое состояние прессы отслеживается по Ctrlispressed поле. Точно так же вы можете добавить другие поля для отслеживания других событий клавиатуры для облегчения гранулированных элементов управления на холсте.
Конечно, слушатели событий могут быть оптимизированы с использованием наблюдаемых, особенно при использовании угловых. Вы можете играть с Проект Stackblitz ниже Отказ
Что дальше? Мы можем почистить демонстративную демонстрационную демонстрацию, добавив некоторые звуковые эффекты и некоторые анимационные спрайты. Мы можем учиться, как сделать его плавно на всех платформах и сделать из него PWA или мобильное приложение.
Я надеюсь, что эта статья добавит некоторую ценность к теме анимации Canvas. Опять же, исходный код в Этот Github Reppo И вы также можете играть с Этот проект Stackblitz и посетить Демо-сайт Отказ Не стесняйтесь оставлять комментарии ниже. Спасибо.
Ваше здоровье!
Оригинал: “https://www.freecodecamp.org/news/how-to-compose-canvas-animations-in-typescript-9368dfa29028/”