Учебное пособие по JavaScript: начало работы с анимацией Canvas

начало работы с анимацией Canvas Программирование и разработка

В этой статье вы научитесь рисовать и анимировать объекты с помощью HTML5 Canvas и JavaScript, прежде чем мы оптимизируем производительность.

«Анимация — это не искусство движущихся рисунков, а искусство нарисованных движений». — Норман Макларен

История Canvas

Apple представила Canvas в 2004 году для поддержки приложений и браузера Safari. Несколько лет спустя WHATWG стандартизировал его. Он обеспечивает более детальный контроль над рендерингом, но требует управления каждой деталью вручную. Другими словами, он может обрабатывать множество объектов, но нам нужно все детально кодировать.

Холст имеет контекст 2D-рисования, используемый для рисования фигур, текста, изображений и других объектов. Сначала выбираем цвет и кисть, а потом рисуем. Мы можем менять кисть и цвет перед каждым новым рисунком, или мы можем продолжить то, что у нас есть.

Canvas использует немедленную визуализацию : когда мы рисуем, она немедленно отображается на экране. Но это система «выстрелил и забыл». После того, как мы что-то рисуем, холст забывает об объекте и знает его только как пиксели. Итак, нет объекта, который мы могли бы переместить. Вместо этого мы должны нарисовать его снова.

Создание анимации на холсте похоже на создание покадрового фильма. В каждом кадре нужно немного двигать объекты, чтобы их оживить.

Что такое элемент холста?

Элемент HTML <canvas>предоставляет пустой контейнер, в котором мы можем рисовать графику. Мы можем рисовать на нем фигуры и линии с помощью Canvas API, который позволяет рисовать графику с помощью JavaScript.

Холст — это прямоугольная область на HTML-странице, которая по умолчанию не имеет границ или содержимого. Размер холста по умолчанию составляет 300 пикселей × 150 пикселей (ширина × высота). Однако пользовательские размеры можно определить с помощью HTML heightи widthсвойства:

<canvas id="canvas" width="600" height="300"></canvas>

Укажите id атрибут, чтобы иметь возможность ссылаться на него из сценария. Чтобы добавить границу, используйте styleатрибут или используйте CSS с classатрибутом:

<canvas id="canvas" width="600" height="300" style="border: 2px solid"></canvas>
<button onclick="animate()">Play</button>

Теперь, когда мы добавили границу, мы видим размер нашего пустого холста на экране. У нас также есть кнопка с onclickсобытием для запуска нашей animate()функции при нажатии на нее.

Теперь, когда мы добавили границу, мы видим размер нашего пустого холста на экране

Мы можем разместить наш код JavaScript в <script>элементах, которые мы помещаем в документ <body>после <canvas>элемента:

<script type="text/javascript" src="canvas.js"></script>

Мы получаем ссылку на <canvas>элемент HTML в DOM (объектной модели документа) с помощью getElementById()метода:

const canvas = document.getElementById('canvas');

Теперь у нас есть элемент холста, но мы не можем рисовать прямо на нем. Вместо этого на холсте есть контексты рендеринга, которые мы можем использовать.

Что такое canvas context?

Холст имеет контекст 2D-рисования, используемый для рисования фигур, текста, изображений и других объектов. Сначала выбираем цвет и кисть, а потом рисуем. Мы можем менять кисть и цвет перед каждым новым рисунком, или мы можем продолжить то, что у нас есть.

HTMLCanvasElement.getContext()Метод возвращает контекст рисования, где мы принесем графику. Предоставляя ’2d’в качестве аргумента, мы получаем контекст 2D-рендеринга холста:

const ctx = canvas.getContext('2d');

Существуют и другие доступные контексты, такие как контекст webglтрехмерной визуализации, которые выходят за рамки данной статьи.

В нем CanvasRenderingContext2Dесть множество методов рисования линий и фигур на холсте. Чтобы установить цвет линии, которую мы используем, strokeStyleи толщину, которую мы используем lineWidth:

ctx.strokeStyle = 'black';
ctx.lineWidth = 5;

Теперь мы готовы нарисовать нашу первую линию на холсте. Но прежде чем мы это сделаем, нам нужно понять, как мы указываем холсту, где рисовать. Холст HTML представляет собой двумерную сетку. Левый верхний угол холста имеет координаты (0, 0).

   X 
Y [(0,0), (1,0), (2,0), (3,0), (4,0), (5,0)]
 [(0,1), (1,1), (2,1), (3,1), (4,1), (5,1)]
  [(0,2), (1,2), (2,2), (3,2), (4,2), (5,2)]

Итак, когда мы говорим, что хотим moveTo(4, 1)на холсте, это означает, что мы начинаем с верхнего левого угла (0,0) и перемещаем четыре столбца вправо и одну строку вниз.

Рисуем ваш объект 🔵

Когда у нас есть контекст холста, мы можем рисовать на нем с помощью API контекста холста. Метод lineTo()добавляет прямую линию к текущему вложенному пути, соединяя ее последнюю точку с указанными координатами ( x, y).

Как и другие методы, изменяющие текущий путь, этот метод ничего не отображает напрямую. Чтобы нарисовать путь на холсте, вы можете использовать методы fill()или stroke().

ctx.beginPath();      // Start a new path
ctx.moveTo(100, 50);  // Move the pen to x=100, y=50.
ctx.lineTo(300, 150); // Draw a line to x=300, y=150.
ctx.stroke();         // Render the path

Как и другие методы, изменяющие текущий путь, этот метод ничего не отображает напрямую

Мы можем использовать fillRect()для рисования заполненного прямоугольника. Установка fillStyleопределяет цвет, используемый при заливке нарисованных фигур:

ctx.fillStyle = 'blue';
ctx.fillRect(100, 100, 30, 30); // (x, y, width, height);

Это рисует спрайт залитого синего прямоугольника:

Это рисует спрайт залитого синего прямоугольника

Анимация вашего объекта 🎥

Теперь давайте посмотрим, сможем ли мы заставить наш блок перемещаться по холсту. Мы начинаем с установки sizeквадрата xравным 30. Затем мы можем перемещать значение вправо с шагом в 1 sizeи рисовать объект снова и снова. Следующий код перемещает блок вправо, пока не достигнет конца холста:

const size = 30;
ctx.fillStyle = 'blue';

for (let x = 0; x < canvas.width; x += size) {
  ctx.fillRect(x, 50, size, size);
}

Теперь давайте посмотрим, сможем ли мы заставить наш блок перемещаться по холсту

Хорошо, мы смогли нарисовать квадрат так, как хотели. Но у нас есть две проблемы:

  1. Мы не убираем за собой.
  2. Слишком быстро, чтобы увидеть анимацию.

Нам нужно убрать старый блок. Что мы можем сделать, так это стереть пиксели в прямоугольной области с помощью clearRect(). Используя ширину и высоту холста, мы можем очищать его между красками.

for (let x = 0; x < canvas.width; x += size) {
  ctx.clearRect(0, 0, canvas.width, canvas.height); // Clean up
  ctx.fillRect(x, 50, size, size);
}

Мы исправили первую проблему. Теперь давайте попробуем замедлить рисование, чтобы увидеть анимацию.

Возможно, вы знакомы с setInterval(function, delay). Он начинает многократно выполнять указанные functionкаждые delayмиллисекунды. Я установил интервал 200 мс, что означает, что код запускается пять раз в секунду.

let x = 0;
const id = setInterval(() => {
  ctx.clearRect(0, 0, canvas.width, canvas.height);      
  ctx.fillRect(x, 50, size, size);
  x += size;

  if (>= canvas.width) {
    clearInterval(id);
  }
}, 200);

Чтобы остановить таймер, созданный setInterval(), нам нужно вызвать clearInterval()и дать ему идентификатор интервала, который нужно отменить. Используемый идентификатор — это тот, который возвращается setInterval(), и поэтому нам нужно его сохранить.

Теперь мы видим, что если мы нажмем кнопку, мы получим квадрат, который перемещается слева направо. Но если мы нажмем кнопку воспроизведения несколько раз, мы увидим, что возникает проблема с одновременной анимацией нескольких квадратов.

Теперь мы видим, что если мы нажмем кнопку, мы получим квадрат

Каждому квадрату присваивается интервал, который очищает доску и закрашивает квадрат. Это повсюду! Посмотрим, как это исправить.

Анимируйте несколько объектов

Чтобы иметь возможность запускать анимацию для нескольких блоков, нам нужно переосмыслить логику. На данный момент каждый блок получает свой метод анимации с setInterval(). Вместо этого мы должны управлять движущимися объектами, прежде чем отправлять их на отрисовку, все сразу.

Мы можем добавить переменную, startedчтобы она запускалась только setInterval()при первом нажатии кнопки. Каждый раз, когда мы нажимаем кнопку воспроизведения, мы добавляем новое значение 0 в squaresмассив. Этого достаточно для этой простой анимации, но для чего-то более сложного мы могли бы создать Squareобъект с координатами и возможными другими свойствами, такими как цвет.

let squares = [];
let started = false;

function play() {
  // Add 0 as x value for object to start from the left.
  squares.push(0);

  if (!started) {
      started = true;
      setInterval(() => {
        tick();
      }, 200)
  }
}

function tick() {
  // Clear canvas
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Paint objects
  squares.forEach(=> ctx.fillRect(x, 50, size, size));

  squares = squares.map(=> x += size) // move x to right
      .filter(=> x < canvas.width);  // remove when at end
}

tick() Функция очищает экран и рисует все объекты в массиве каждые 200 мс. И, имея только один интервал, мы избегаем мерцания, которое у нас было раньше. И теперь у нас получились более качественные анимации:

tick() Функция очищает экран и рисует все объекты в массиве каждые 200 мс

То, что мы здесь сделали, — это первый шаг к созданию игрового цикла. Этот цикл — сердце каждой игры. Это управляемый бесконечный цикл, который поддерживает вашу игру; это место, где все ваши маленькие кусочки обновляются и рисуются на экране.

Оптимизируйте свою анимацию 🏃

Другой вариант анимации — использовать requestAnimationFrame(). Он сообщает браузеру, что вы хотите выполнить анимацию, и просит браузер вызвать функцию для обновления анимации перед следующей перерисовкой. Другими словами, мы говорим браузеру: «В следующий раз, когда вы рисуете на экране, запустите эту функцию, потому что я тоже хочу что-то нарисовать».

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

function play() {
  // Add 0 as x value for object to start from the left.
  squares.push(0);

  if (!started) {
      animate();
  }
}

function animate() {
  tick();
  requestAnimationFrame(animate);  
}

Если мы попробуем это сделать, мы заметим, что можем увидеть анимацию, чего не было setInterval(), хотя она очень быстрая. Количество обратных вызовов обычно составляет 60 раз в секунду.

В requestAnimationFrame()методе возвращает, idкоторый мы используем для отмены запланированного кадра анимации. Чтобы отменить запланированный кадр анимации, вы можете использовать cancelAnimationFrame(id)метод.

Чтобы замедлить анимацию, нам нужен таймер, чтобы проверять elapsedвремя с момента последнего вызова tick()функции. А также чтобы помочь нам, функции обратного вызова передается аргумент a DOMHighResTimeStamp, указывающий момент времени, когда requestAnimationFrame()начинается выполнение функций обратного вызова.

let start = 0;

function animate(timestamp) {    
  const elapsed  = timestamp - start;
  if (elapsed > 200) {
    start = timestamp;
    tick();
  }
  requestAnimationFrame(animate);  
}

При этом у нас есть те же функции, что и раньше setInterval().

Итак, в заключение, почему мы должны использовать requestAnimationFrame()вместо setInterval()?

  • Это позволяет оптимизировать браузер.
  • Он обрабатывает частоту кадров.
  • Анимация запускается только тогда, когда она видна.

Подведение итогов

В этой статье мы создали холст HTML5 и использовали JavaScript и его контекст 2D-рендеринга для рисования на холсте. Мы познакомились с некоторыми методами, доступными в контексте холста, и использовали их для визуализации различных форм.

Наконец, мы смогли анимировать несколько объектов на холсте. Мы узнали, как использовать setInterval()для создания цикла анимации, который управляет объектами на экране и рисует их. Мы также узнали, как оптимизировать анимацию с помощью requestAnimationFrame().

Теперь, когда вы изучили некоторые основы анимации, вы сделали свои первые шаги в разработке игры. Ваш следующий шаг — взяться за проект для начинающих разработчиков игр, чтобы узнать, как:

  • Создавайте игровые петли.
  • Реализуйте интерактивные элементы управления с помощью addEventListener.
  • Обнаружение столкновения объектов.
  • Отслеживание результатов.
Читайте также:  Как скопировать текст в буфер обмена в Next.js?
Оцените статью
bestprogrammer.ru
Добавить комментарий