Перейти к содержимому

HTML5+JS

Эффекты создаются с использованием чистого JavaScript на элементе canvas HTML5. В этом разделе рассматриваются основы: рисование фигур, рисование с итерациями и добавление движения.

В этом руководстве используется общий шаблон lightscript:

<head>
<meta description="Template"/>
<meta publisher="WhirlwindFX" />
</head>
<body style="margin: 0; padding: 0;">
<canvas id="exCanvas" width="320" height="200"></canvas>
</body>
<script>
// Get the canvas element from the DOM
var c = document.getElementById("exCanvas");
var ctx = c.getContext("2d");
var width = 320;
var height = 200;
var hue = 0;
function update() {
// Code goes here
window.requestAnimationFrame(update);
}
window.requestAnimationFrame(update);
</script>

Независимо от количества линий процесс остаётся одинаковым.

  1. ctx.beginPath() начинает фигуру
  2. ctx.moveTo(x, y) задаёт первую точку
  3. ctx.lineTo(x, y) задаёт следующую точку и линию обратно к первой. Используйте несколько lineTo для рисования более крупных фигур.
  4. Задайте ctx.strokeStyle и/или ctx.fillStyle (если фигура замкнута)
  5. Используйте ctx.stroke() для отрисовки линий и ctx.fill() для заливки замкнутых фигур. Важно помнить, что до этого момента всё является лишь гипотетическим — ничего не рисуется до выполнения этих команд.

Три примера с использованием линий:

function update() {
// One Line
ctx.beginPath();
ctx.moveTo(30, 30);
ctx.lineTo(30, 170);
ctx.strokeStyle = "blue";
ctx.stroke();
// L
ctx.beginPath();
ctx.moveTo(80, 30);
ctx.lineTo(80, 100);
ctx.lineTo(120, 100);
ctx.strokeStyle = "blue";
ctx.stroke();
// Filled Triangle
ctx.beginPath();
ctx.moveTo(130, 30);
ctx.lineTo(130, 100);
ctx.lineTo(170, 100);
ctx.lineTo(130, 30);
ctx.fillStyle = "red";
ctx.strokeStyle = "blue";
ctx.fill();
ctx.stroke();
window.requestAnimationFrame(update);
}

Важно отметить порядок отрисовки: закрашенный треугольник рисуется последним, поэтому он перекрыл бы две другие фигуры, если бы находился на той же позиции. Кроме того, обводка треугольника рисуется после заливки. Если поменять эти шаги местами, внутренняя половина обводки будет перекрыта красной заливкой.

Прямоугольники просты и полезны, особенно для типичного разрешения RGB-клавиатуры. Процесс аналогичен рисованию линий — просто замените команды moveTo и lineTo на rect(x, y, width, height).

function update() {
// Basic Square
ctx.beginPath();
ctx.rect(30, 30, 75, 75);
ctx.strokeStyle = "blue";
ctx.stroke();
// Filled Rectangle
ctx.beginPath();
ctx.rect(125, 30, 125, 100);
ctx.fillStyle = "red";
ctx.strokeStyle = "blue";
ctx.fill();
ctx.stroke();
window.requestAnimationFrame(update);
}

Дуги рисуются путём вращения внешней точки вокруг центральной и могут использоваться для создания окружностей. Команда дуги: ctx.arc(x, y, radius, start angle, end angle), где оба угла задаются в радианах. В отличие от прямоугольников, дуга рисуется с началом координат (x, y) в центре дуги.

function update() {
// Half-circle
ctx.beginPath();
ctx.arc(70, 100, 30, 0, Math.PI);
ctx.strokeStyle = "blue";
ctx.stroke();
// 3/4 circle
ctx.beginPath();
ctx.arc(140, 100, 30, 0, Math.PI * (3/2));
ctx.strokeStyle = "blue";
ctx.stroke();
// Full circle
ctx.beginPath();
ctx.arc(210, 100, 30, 0, Math.PI * 2);
ctx.strokeStyle = "blue";
ctx.stroke();
ctx.fillStyle = "red";
ctx.fill();
window.requestAnimationFrame(update);
}

Как видно, дуги рисуются по часовой стрелке по умолчанию. Это можно изменить, задав конечный угол меньше начального или добавив «true» в качестве последнего аргумента метода ctx.arc().

Нарисовать три фигуры вручную легко занимает двадцать строк кода, поэтому если нужно нарисовать сто фигур, необходим более эффективный подход. Циклы в JavaScript — простой способ повторять задачи, и они применимы и к рисованию.

Пример с использованием for-цикла для рисования ряда шахматных квадратов:

function update() {
for(let i = 0; i < 8; i++){
// 1. Create rectangle path and draw the stroke for each.
ctx.beginPath();
ctx.rect(i * 20, 20, 20, 20);
ctx.strokeStyle = "black";
ctx.stroke();
// 2. If 'i' is even, the fillStyle is black. Otherwise it's white.
if(i % 2 == 0){
ctx.fillStyle = "black";
} else {
ctx.fillStyle = "white";
}
// 3. Fill the shape after setting the fillStyle.
ctx.fill();
}
window.requestAnimationFrame(update);
}

Пример с while-циклом для рисования шахматной сетки. В каждой итерации яркость чёрных квадратов и оттенок белых квадратов будут немного меняться с использованием шаблонных литералов.

function update() {
var i = 0;
while(i < 16){
// 1. X and Y calculations with 'row' and 'column'
// This operation rounds the result down to the nearest integer. For i = (0-3) the row will be 0, for i = (4-7) the row will be 1, and so on
var iRow = Math.floor(i / 4);
// This operation finds the remainder after division, limiting column to the (0-3) range.
var iCol = i % 4;
// Multiply by the square width to find this square's x-origin
var ix = iCol * 20;
// Multiply by the square height to find this square's y-origin
var iy = iRow * 20;
// 2. Path and stroke
ctx.beginPath();
ctx.rect(ix, iy, 20, 20);
ctx.strokeStyle = "black";
ctx.stroke();
// 3. If the row is even, every other square is filled black. If odd, switch the black squares. Each iteration adds a small amount of the lightness component to the black, and hue to the white.
if(iRow % 2 == 0){
if(i % 2 == 0){
// 'Black'
ctx.fillStyle = `hsl(1, 0%, ${5 * i}%)`
} else {
// 'White'
ctx.fillStyle = `hsl(${10 * i}, 100%, 50%)`;
}
} else {
if(i % 2 == 0){
// 'White'
ctx.fillStyle = `hsl(${10 * i}, 100%, 50%)`;
} else {
// 'Black'
ctx.fillStyle = `hsl(1, 0%, ${5 * i}%)`
}
}
ctx.fill();
i++;
}
window.requestAnimationFrame(update);
}

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

Золотым стандартом осцилляции (регулярного движения туда-обратно) является вычисление sin() или cosine() от таймера. Обе операции вернут значение от -1 до 1 для ЛЮБОГО переданного значения, даже если оно только увеличивается. Единственное различие между ними — небольшой сдвиг фазы: когда cos равен 0, sin равен -1 или 1, и наоборот. Мы можем использовать это в нашей анимации:

function update() {
// Find current time in milliseconds, then divide to slow down the animation speed
let time = Date.now() / 100;
// Draw a background so the old shapes don't stick around
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
ctx.beginPath();
//USE ONE EXAMPLE AT A TIME
// 1st example - side to side, y is constant
ctx.arc(15 * Math.cos(time) + 160, 100, 30, 0, Math.PI * 2);
// 2nd example - up and down, x is constant
ctx.arc(160, 15 * Math.sin(time) + 100, 30, 0, Math.PI * 2);
// 3rd example - circle, x and y are variable
ctx.arc(15 * Math.cos(time) + 160, 15 * Math.sin(time) + 100, 30, 0, Math.PI * 2);
ctx.fillStyle = "black";
ctx.fill();
window.requestAnimationFrame(update);
}

Осталось изменить ещё несколько атрибутов со временем — радиус круга, заливку и обводку. Углы дуги пока оставим без изменений, чтобы фигура оставалась простой, — остальное будет достаточно эффектным.

function update() {
let time = Date.now() / 100;
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 320,200);
ctx.beginPath();
ctx.arc(15 * Math.cos(time) + 160, 15 * Math.sin(time) + 100, 30 + 15 * Math.sin(time), 0, Math.PI * 2);
ctx.fillStyle = `hsl(${180 + 180 * Math.cos(time)}, 100%, 50%)`;
ctx.fill();
ctx.strokeStyle = `hsl(${180 + 180 * Math.sin(time)}, 100%, 50%)`;
ctx.stroke();
window.requestAnimationFrame(update);
}

Каждый атрибут каждой созданной нами фигуры можно изменять со временем, и любая анимация — это комбинация описанных выше навыков. Для получения подробной информации о создании многократно используемых эффективных анимаций ознакомьтесь со страницей Обратные вызовы!