Основные анимации
Поскольку мы используем JavaScript для управления элементами <canvas>, создание (интерактивных) анимаций также очень просто. В этой главе мы рассмотрим, как выполнить некоторые основные анимации.
Вероятно, самым большим ограничением является то, что после отрисовки фигуры она остается в том же состоянии. Если нам нужно ее переместить, мы должны перерисовать ее и все, что было нарисовано до нее. Перерисовка сложных кадров занимает много времени, а производительность сильно зависит от скорости компьютера, на котором она выполняется.
Шаги создания основной анимации
Вот шаги, которые необходимо выполнить для отрисовки кадра:
-
Очистка холста Если фигуры, которые вы будете рисовать, не заполняют весь холст (например, фоновое изображение), необходимо очистить все ранее нарисованные фигуры. Самый простой способ сделать это — использовать метод
clearRect(). - Сохранение состояния холста Если вы изменяете какие-либо настройки (такие как стили, преобразования и т. д.), которые влияют на состояние холста, и хотите убедиться, что исходное состояние используется каждый раз при отрисовке кадра, вам необходимо сохранить это исходное состояние.
- Отрисовка анимированных фигур Шаг, на котором происходит фактическая отрисовка кадра.
- Восстановление состояния холста Если вы сохранили состояние, восстановите его перед отрисовкой нового кадра.
Управление анимацией
Фигуры рисуются на холсте с помощью методов холста напрямую или с помощью вызова пользовательских функций. В обычных условиях мы видим эти результаты на холсте только после завершения выполнения скрипта. Например, невозможно выполнить анимацию внутри цикла for.
Это означает, что нам нужен способ выполнения наших функций рисования в течение определенного периода времени. Существует два способа управления такой анимацией.
Планируемые обновления
Во-первых, есть функции setInterval(), setTimeout() и requestAnimationFrame(), которые могут использоваться для вызова определенной функции через заданный промежуток времени.
setInterval()-
Повторяет выполнение функции, указанной в
function, каждыеdelayмиллисекунд. setTimeout()-
Выполняет функцию, указанную в
function, черезdelayмиллисекунд. requestAnimationFrame()-
Уведомляет браузер о том, что вы хотите выполнить анимацию, и запрашивает, чтобы браузер вызвал указанную функцию для обновления анимации перед следующей перерисовкой.
Если вам не нужна никакая пользовательская интерактивность, вы можете использовать функцию setInterval(), которая многократно выполняет предоставленный код. Если мы хотим создать игру, мы можем использовать события клавиатуры или мыши для управления анимацией и использовать setTimeout(). Установив обработчики с помощью addEventListener(), мы ловим любое взаимодействие пользователя и выполняем наши функции анимации.
Примечание: В примерах ниже мы будем использовать метод Window.requestAnimationFrame() для управления анимацией. Метод requestAnimationFrame обеспечивает более плавный и эффективный способ анимации, вызывая кадр анимации, когда система готова нарисовать кадр. Количество вызовов обычно составляет 60 раз в секунду и может быть уменьшено до меньшей скорости при запуске во вкладках фона. Более подробную информацию об цикле анимации, особенно для игр, см. в статье Архитектура видеоигры в нашем разделе Разработка игр.
Анимированная Солнечная система
В этом примере анимируется небольшая модель нашей Солнечной системы.
HTML
<canvas id="canvas" width="300" height="300"></canvas>
JavaScript
const sun = new Image();
const moon = new Image();
const earth = new Image();
const ctx = document.getElementById("canvas").getContext("2d");
function init() {
sun.src = "canvas_sun.png";
moon.src = "canvas_moon.png";
earth.src = "canvas_earth.png";
window.requestAnimationFrame(draw);
}
function draw() {
ctx.globalCompositeOperation = "destination-over";
ctx.clearRect(0, 0, 300, 300); // clear canvas
ctx.fillStyle = "rgb(0 0 0 / 40%)";
ctx.strokeStyle = "rgb(0 153 255 / 40%)";
ctx.save();
ctx.translate(150, 150);
// Earth
const time = new Date();
ctx.rotate(
((2 * Math.PI) / 60) * time.getSeconds() +
((2 * Math.PI) / 60000) * time.getMilliseconds(),
);
ctx.translate(105, 0);
ctx.fillRect(0, -12, 40, 24); // Shadow
ctx.drawImage(earth, -12, -12);
// Moon
ctx.save();
ctx.rotate(
((2 * Math.PI) / 6) * time.getSeconds() +
((2 * Math.PI) / 6000) * time.getMilliseconds(),
);
ctx.translate(0, 28.5);
ctx.drawImage(moon, -3.5, -3.5);
ctx.restore();
ctx.restore();
ctx.beginPath();
ctx.arc(150, 150, 105, 0, Math.PI * 2, false); // Earth orbit
ctx.stroke();
ctx.drawImage(sun, 0, 0, 300, 300);
window.requestAnimationFrame(draw);
}
init();
Результат
Анимированные часы
В этом примере рисуются анимированные часы, отображающие текущее время.
HTML
<canvas id="canvas" width="150" height="150">The current time</canvas>
JavaScript
function clock() {
const now = new Date();
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.save();
ctx.clearRect(0, 0, 150, 150);
ctx.translate(75, 75);
ctx.scale(0.4, 0.4);
ctx.rotate(-Math.PI / 2);
ctx.strokeStyle = "black";
ctx.fillStyle = "white";
ctx.lineWidth = 8;
ctx.lineCap = "round";
// Hour marks
ctx.save();
for (let i = 0; i < 12; i++) {
ctx.beginPath();
ctx.rotate(Math.PI / 6);
ctx.moveTo(100, 0);
ctx.lineTo(120, 0);
ctx.stroke();
}
ctx.restore();
// Minute marks
ctx.save();
ctx.lineWidth = 5;
for (let i = 0; i < 60; i++) {
if (i % 5 !== 0) {
ctx.beginPath();
ctx.moveTo(117, 0);
ctx.lineTo(120, 0);
ctx.stroke();
}
ctx.rotate(Math.PI / 30);
}
ctx.restore();
const sec = now.getSeconds();
// To display a clock with a sweeping second hand, use:
// const sec = now.getSeconds() + now.getMilliseconds() / 1000;
const min = now.getMinutes();
const hr = now.getHours() % 12;
ctx.fillStyle = "black";
// Write image description
canvas.innerText = `The time is: ${hr}:${min}`;
// Write Hours
ctx.save();
ctx.rotate(
(Math.PI / 6) * hr + (Math.PI / 360) * min + (Math.PI / 21600) * sec,
);
ctx.lineWidth = 14;
ctx.beginPath();
ctx.moveTo(-20, 0);
ctx.lineTo(80, 0);
ctx.stroke();
ctx.restore();
// Write Minutes
ctx.save();
ctx.rotate((Math.PI / 30) * min + (Math.PI / 1800) * sec);
ctx.lineWidth = 10;
ctx.beginPath();
ctx.moveTo(-28, 0);
ctx.lineTo(112, 0);
ctx.stroke();
ctx.restore();
// Write seconds
ctx.save();
ctx.rotate((sec * Math.PI) / 30);
ctx.strokeStyle = "#D40000";
ctx.fillStyle = "#D40000";
ctx.lineWidth = 6;
ctx.beginPath();
ctx.moveTo(-30, 0);
ctx.lineTo(83, 0);
ctx.stroke();
ctx.beginPath();
ctx.arc(0, 0, 10, 0, Math.PI * 2, true);
ctx.fill();
ctx.beginPath();
ctx.arc(95, 0, 10, 0, Math.PI * 2, true);
ctx.stroke();
ctx.fillStyle = "rgb(0 0 0 / 0%)";
ctx.arc(0, 0, 3, 0, Math.PI * 2, true);
ctx.fill();
ctx.restore();
ctx.beginPath();
ctx.lineWidth = 14;
ctx.strokeStyle = "#325FA2";
ctx.arc(0, 0, 142, 0, Math.PI * 2, true);
ctx.stroke();
ctx.restore();
window.requestAnimationFrame(clock);
}
window.requestAnimationFrame(clock);
Результат
Примечание: Хотя часы обновляются только раз в секунду, анимированное изображение обновляется со скоростью 60 кадров в секунду (или со скоростью обновления экрана вашего веб-браузера). Для отображения часов с движущейся секундной стрелкой замените определение const sec выше на закомментированную версию.
Циклическая панорама
В этом примере панорама прокручивается слева направо. Мы используем изображение национального парка Йосемити с Википедии, но вы можете использовать любое изображение, которое больше холста.
HTML
HTML включает <canvas>, в котором прокручивается изображение. Обратите внимание, что указанная здесь ширина и высота должны соответствовать значениям переменных canvasXSize и canvasYSize в коде JavaScript.
<canvas id="canvas" width="800" height="200" >Yosemite National Park, meadow at the base of El Capitan</canvas >
JavaScript
const img = new Image();
// User Variables - customize these to change the image being scrolled, its
// direction, and the speed.
img.src = "capitan_meadows_yosemite_national_park.jpg";
const canvasXSize = 800;
const canvasYSize = 200;
const speed = 30; // lower is faster
const scale = 1.05;
const y = -4.5; // vertical offset
// Main program
const dx = 0.75;
let imgW;
let imgH;
let x = 0;
let clearX;
let clearY;
let ctx;
img.onload = () => {
imgW = img.width * scale;
imgH = img.height * scale;
if (imgW > canvasXSize) {
// Image larger than canvas
x = canvasXSize - imgW;
}
// Check if image dimension is larger than canvas
clearX = Math.max(imgW, canvasXSize);
clearY = Math.max(imgH, canvasYSize);
// Get canvas context
ctx = document.getElementById("canvas").getContext("2d");
// Set refresh rate
return setInterval(draw, speed);
};
function draw() {
ctx.clearRect(0, 0, clearX, clearY); // clear the canvas
// If image is <= canvas size
if (imgW <= canvasXSize) {
// Reset, start from beginning
if (x > canvasXSize) {
x = -imgW + x;
}
// Draw additional image1
if (x > 0) {
ctx.drawImage(img, -imgW + x, y, imgW, imgH);
}
// Draw additional image2
if (x - imgW > 0) {
ctx.drawImage(img, -imgW * 2 + x, y, imgW, imgH);
}
} else {
// Image is > canvas size
// Reset, start from beginning
if (x > canvasXSize) {
x = canvasXSize - imgW;
}
// Draw additional image
if (x > canvasXSize - imgW) {
ctx.drawImage(img, x - imgW + 1, y, imgW, imgH);
}
}
// Draw image
ctx.drawImage(img, x, y, imgW, imgH);
// Amount to move
x += dx;
}
Результат
Анимация следования за мышью
HTML
<canvas id="cw" >Animation creating multi-colored disappearing stream of light that follow the cursor as it moves over the image </canvas>
CSS
#cw {
position: fixed;
z-index: -1;
}
body {
margin: 0;
padding: 0;
background-color: rgb(0 0 0 / 5%);
}
JavaScript
const canvas = document.getElementById("cw");
const context = canvas.getContext("2d");
context.globalAlpha = 0.5;
const cursor = {
x: innerWidth / 2,
y: innerHeight / 2,
};
let particlesArray = [];
generateParticles(101);
setSize();
anim();
addEventListener("mousemove", (e) => {
cursor.x = e.clientX;
cursor.y = e.clientY;
});
addEventListener(
"touchmove",
(e) => {
e.preventDefault();
cursor.x = e.touches[0].clientX;
cursor.y = e.touches[0].clientY;
},
{ passive: false },
);
addEventListener("resize", () => setSize());
function generateParticles(amount) {
for (let i = 0; i < amount; i++) {
particlesArray[i] = new Particle(
innerWidth / 2,
innerHeight / 2,
4,
generateColor(),
0.02,
);
}
}
function generateColor() {
let hexSet = "0123456789ABCDEF";
let finalHexString = "#";
for (let i = 0; i < 6; i++) {
finalHexString += hexSet[Math.ceil(Math.random() * 15)];
}
return finalHexString;
}
function setSize() {
canvas.height = innerHeight;
canvas.width = innerWidth;
}
function Particle(x, y, particleTrailWidth, strokeColor, rotateSpeed) {
this.x = x;
this.y = y;
this.particleTrailWidth = particleTrailWidth;
this.strokeColor = strokeColor;
this.theta = Math.random() * Math.PI * 2;
this.rotateSpeed = rotateSpeed;
this.t = Math.random() * 150;
this.rotate = () => {
const ls = {
x: this.x,
y: this.y,
};
this.theta += this.rotateSpeed;
this.x = cursor.x + Math.cos(this.theta) * this.t;
this.y = cursor.y + Math.sin(this.theta) * this.t;
context.beginPath();
context.lineWidth = this.particleTrailWidth;
context.strokeStyle = this.strokeColor;
context.moveTo(ls.x, ls.y);
context.lineTo(this.x, this.y);
context.stroke();
};
}
function anim() {
requestAnimationFrame(anim);
context.fillStyle = "rgb(0 0 0 / 5%)";
context.fillRect(0, 0, canvas.width, canvas.height);
particlesArray.forEach((particle) => particle.rotate());
}
Результат
Другие примеры
- Дополненные анимации
-
В следующей главе мы рассмотрим некоторые продвинутые анимационные техники и физику.
© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Basic_animations