Canvas позволяет рисовать прямо внутри браузера с помощью JavaScript. Квадраты, линии, круги, текст, изображения, персонажи, игровые уровни — всё это можно вывести на обычной HTML-странице без игрового движка и дополнительных библиотек.

Материалы к уроку

Откройте готовые файлы по шагам во встроенном просмотрщике или скачайте архив целиком.

Скачать материалы к уроку (ZIP)

На первый взгляд Canvas кажется большой отдельной технологией, которую нужно долго изучать перед созданием первой игры. На практике для старта достаточно понять координаты и запомнить несколько основных команд.

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

Что мы сделаем за этот урок

Начнём с пустой HTML-страницы и постепенно пройдём основные возможности Canvas:

  • создадим Canvas и получим контекст рисования;
  • разберём координаты X и Y;
  • нарисуем квадрат и прямоугольник;
  • проведём линии и соберём треугольник;
  • нарисуем круг;
  • добавим цвета;
  • выведем текст;
  • разберём загрузку изображений;
  • очистим предыдущий кадр;
  • запустим первую анимацию через requestAnimationFrame().

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

Что такое Canvas в JavaScript

<canvas> — это HTML-элемент, внутри которого JavaScript может рисовать графику. Сам по себе Canvas почти ничего не делает. Мы создаём прямоугольную область на странице, а затем получаем объект для работы с ней из JavaScript.

Если обычный HTML состоит из отдельных элементов — заголовков, кнопок, картинок и блоков, — то Canvas больше похож на лист бумаги. Браузер не знает, что внутри него находится персонаж, стена или монета. Для браузера это просто пиксели, которые нарисовал наш код.

Именно поэтому Canvas удобно использовать для игр. Мы сами решаем, что рисовать, где находится объект и как изображение должно меняться от кадра к кадру.

Создаём первый Canvas

Создадим файл index.html и добавим в него холст размером 800 на 450 пикселей:

<canvas id="game" width="800" height="450"></canvas>

У Canvas есть обычные размеры width и height. Они задают не только размер элемента, но и внутреннюю систему координат, в которой мы будем рисовать.

Теперь найдём Canvas из JavaScript:

const canvas = document.querySelector('#game');

В переменной canvas находится сам HTML-элемент. Но рисовать через него напрямую мы не будем. Сначала нужно получить контекст.

Что такое getContext('2d')

Для обычной двумерной графики используется контекст 2d:

const canvas = document.querySelector('#game');
const ctx = canvas.getContext('2d');

Дальше почти всё рисование будет происходить через переменную ctx.

Можно представить это так:

canvas — сам холст

ctx — набор инструментов для рисования на нём

Через ctx мы будем создавать прямоугольники, линии, круги, текст и изображения.

Как работают координаты Canvas

Перед рисованием нужно разобраться с одной вещью, на которой постоянно спотыкаются новички: системой координат.

Точка 0, 0 находится в левом верхнем углу Canvas.

(0, 0) ───────────────────→ X
   │
   │
   │
   │
   ↓
   Y

Координата X растёт вправо, а координата Y — вниз.

Например, точка:

x = 100
y = 50

находится на 100 пикселей правее левого края и на 50 пикселей ниже верхней границы.

Если Canvas имеет размер 800 × 450, его правый нижний угол находится примерно в точке 800, 450.

Почти вся игровая графика дальше будет сводиться к работе с такими координатами.

Как нарисовать прямоугольник на Canvas

Начнём с самой простой фигуры. Метод fillRect() рисует закрашенный прямоугольник:

ctx.fillRect(100, 80, 200, 100);

У него четыре параметра:

ctx.fillRect(x, y, width, height);
  • x — положение по горизонтали;
  • y — положение по вертикали;
  • width — ширина;
  • height — высота.

В нашем примере прямоугольник начинается в точке 100, 80, имеет ширину 200 пикселей и высоту 100.

Как нарисовать квадрат

Квадрат — это тот же прямоугольник, только ширина и высота совпадают:

ctx.fillRect(100, 100, 80, 80);

Получился квадрат размером 80 × 80 пикселей.

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

Как изменить цвет фигуры

По умолчанию прямоугольник получится чёрным. Цвет заливки задаётся через свойство fillStyle:

ctx.fillStyle = '#5eead4';
ctx.fillRect(100, 100, 80, 80);

Сначала мы выбираем цвет, затем рисуем фигуру.

Это важно: Canvas работает последовательно. Настройки применяются к тому, что будет нарисовано после них.

Можно сразу вывести несколько фигур разного цвета:

ctx.fillStyle = '#5eead4';
ctx.fillRect(50, 50, 80, 80);

ctx.fillStyle = '#f472b6';
ctx.fillRect(150, 50, 80, 80);

ctx.fillStyle = '#facc15';
ctx.fillRect(250, 50, 80, 80);

Как нарисовать контур прямоугольника

Если нужна не заливка, а только рамка, используется strokeRect():

ctx.strokeRect(100, 100, 150, 80);

Цвет контура задаётся через strokeStyle:

ctx.strokeStyle = '#22d3ee';
ctx.strokeRect(100, 100, 150, 80);

Толщину линии можно изменить через lineWidth:

ctx.strokeStyle = '#22d3ee';
ctx.lineWidth = 4;
ctx.strokeRect(100, 100, 150, 80);

Как рисовать линии на Canvas

С прямоугольниками всё просто: вызвали одну функцию и получили фигуру. Для произвольных линий Canvas использует путь — path.

Создадим линию:

ctx.beginPath();

ctx.moveTo(100, 100);
ctx.lineTo(300, 200);

ctx.stroke();

Здесь происходит три действия.

beginPath() начинает новый путь. moveTo() переносит условный карандаш в нужную точку, ничего не рисуя. lineTo() задаёт следующую точку.

Но до вызова stroke() линия существует только внутри текущего пути. Именно stroke() выводит её на экран.

Как соединить несколько линий

Можно продолжить путь:

ctx.beginPath();

ctx.moveTo(100, 100);
ctx.lineTo(200, 50);
ctx.lineTo(300, 100);
ctx.lineTo(200, 200);

ctx.stroke();

Каждый следующий lineTo() проводит линию от предыдущей точки к новой.

На этом принципе можно собирать произвольные фигуры.

Как нарисовать треугольник на Canvas

Треугольник — хороший пример того, зачем вообще нужен путь.

ctx.beginPath();

ctx.moveTo(200, 80);
ctx.lineTo(120, 220);
ctx.lineTo(280, 220);

ctx.closePath();

ctx.fillStyle = '#a78bfa';
ctx.fill();

Мы задали три вершины, а затем вызвали closePath(), чтобы соединить последнюю точку с первой.

Метод fill() заполняет получившуюся фигуру цветом.

Если вместо заливки нужен только контур:

ctx.strokeStyle = '#a78bfa';
ctx.lineWidth = 4;
ctx.stroke();

Как нарисовать круг на Canvas

Для кругов и дуг используется метод arc().

ctx.beginPath();

ctx.arc(200, 150, 50, 0, Math.PI * 2);

ctx.fillStyle = '#facc15';
ctx.fill();

Запись выглядит сложнее обычного прямоугольника, но нам пока достаточно понять пять параметров:

ctx.arc(x, y, radius, startAngle, endAngle);
  • x — координата центра по горизонтали;
  • y — координата центра по вертикали;
  • radius — радиус;
  • startAngle — начало дуги;
  • endAngle — конец дуги.

Главное отличие от прямоугольника: координаты круга указывают на его центр.

У прямоугольника:

ctx.fillRect(100, 100, 80, 80);

точка 100, 100 находится в левом верхнем углу фигуры.

У круга:

ctx.arc(100, 100, 40, 0, Math.PI * 2);

точка 100, 100 находится прямо в центре.

Зачем нужен Math.PI * 2

Canvas принимает углы в радианах. Для полного круга нужен полный оборот, который равен Math.PI * 2.

На старте необязательно глубоко уходить в математику. Достаточно запомнить:

Math.PI * 2 // полный круг

Если изменить конечный угол, получится только часть круга.

Как нарисовать полукруг

Например:

ctx.beginPath();

ctx.arc(200, 150, 60, 0, Math.PI);

ctx.stroke();

Теперь вместо полного оборота используется только Math.PI, поэтому получится половина окружности.

Через дуги позже можно делать индикаторы здоровья, круговые интерфейсы, эффекты, части персонажей и даже простые игровые объекты.

Как добавить текст на Canvas

В играх постоянно нужен текст: очки, таймер, жизни, сообщения о победе и проигрыше.

Для вывода используется fillText():

ctx.fillText('Очки: 120', 30, 50);

Размер и шрифт задаются через font:

ctx.font = '24px Arial';
ctx.fillStyle = '#ffffff';

ctx.fillText('Очки: 120', 30, 50);

Как и с фигурами, сначала настраиваем стиль, затем рисуем.

Как выровнять текст

По умолчанию координата текста привязана к его левому краю. Можно изменить выравнивание:

ctx.textAlign = 'center';
ctx.fillText('Победа!', canvas.width / 2, 80);

Теперь текст будет расположен по центру относительно указанной координаты.

Выражение:

canvas.width / 2

даёт горизонтальный центр Canvas. Такой приём пригодится для меню, экранов победы и других элементов интерфейса.

Как очистить Canvas

До этого мы только добавляли рисунки. Но игре недостаточно однажды вывести персонажа. Картинка должна постоянно меняться.

Для очистки используется clearRect():

ctx.clearRect(0, 0, canvas.width, canvas.height);

Мы очищаем прямоугольную область от точки 0, 0 до полной ширины и высоты Canvas.

Проще говоря — стираем весь предыдущий кадр.

Именно здесь начинает появляться принцип игры:

очистить экран
↓
изменить данные
↓
нарисовать новый кадр
↓
повторить

Как добавить изображение на Canvas

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

Создадим объект Image:

const playerImage = new Image();

playerImage.src = 'player.png';

Когда изображение загрузится, его можно вывести через drawImage():

playerImage.onload = () => {
    ctx.drawImage(playerImage, 100, 100);
};

Координаты 100, 100 задают положение картинки.

Как изменить размер изображения

У drawImage() есть вариант с шириной и высотой:

ctx.drawImage(
    playerImage,
    100,
    100,
    64,
    64
);

Теперь картинка будет нарисована размером 64 × 64 пикселя.

Важный момент: изображение загружается не мгновенно. Если вызвать drawImage() раньше загрузки файла, Canvas может просто ничего не показать. Поэтому при первой работе удобно использовать onload.

Заставляем объект двигаться

До этого Canvas был обычным листом бумаги. Теперь сделаем первый шаг в сторону игры.

Создадим координату игрока:

let playerX = 50;

Нарисуем квадрат в этой координате:

ctx.fillStyle = '#5eead4';
ctx.fillRect(playerX, 300, 50, 50);

Чтобы объект начал двигаться, достаточно менять значение playerX:

playerX += 1;

Проблема только в одном: этот код выполнится один раз. Координата изменится, браузер нарисует кадр и остановится.

Нам нужен повторяющийся цикл.

Первая анимация через requestAnimationFrame

Для браузерной анимации используется requestAnimationFrame().

function loop() {
    requestAnimationFrame(loop);
}

loop();

Функция loop() вызывает следующий кадр, браузер снова запускает loop(), а затем процесс повторяется.

Добавим очистку, изменение координаты и рисование:

let playerX = 50;

function loop() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    playerX += 1;

    ctx.fillStyle = '#5eead4';
    ctx.fillRect(playerX, 300, 50, 50);

    requestAnimationFrame(loop);
}

loop();

Теперь квадрат будет двигаться вправо.

Посмотри, что происходит внутри каждого кадра:

  1. стираем предыдущую картинку;
  2. изменяем координату игрока;
  3. рисуем игрока в новом месте;
  4. просим браузер вызвать следующий кадр.

Это уже простейшая версия игрового цикла.

Для полноценной игры цикл придётся немного усложнить: учитывать прошедшее время, управление, столкновения, состояния победы и проигрыша. Но принцип останется тем же.

Собираем маленькую игровую сцену

Отдельные квадраты и круги полезны для изучения API, но оставлять урок на этом месте скучно. Соберём всё, что уже прошли, в одну сцену.

Нарисуем:

  • тёмный фон;
  • землю;
  • солнце;
  • движущегося игрока;
  • счётчик очков.

Создадим Canvas:

<canvas id="game" width="800" height="450"></canvas>

А теперь напишем функцию рисования сцены:

const canvas = document.querySelector('#game');
const ctx = canvas.getContext('2d');

let playerX = 50;

function drawScene() {
    // Фон
    ctx.fillStyle = '#111827';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // Солнце
    ctx.beginPath();
    ctx.arc(680, 80, 35, 0, Math.PI * 2);
    ctx.fillStyle = '#facc15';
    ctx.fill();

    // Земля
    ctx.fillStyle = '#334155';
    ctx.fillRect(0, 380, canvas.width, 70);

    // Игрок
    ctx.fillStyle = '#5eead4';
    ctx.fillRect(playerX, 330, 50, 50);

    // Интерфейс
    ctx.font = '22px Arial';
    ctx.fillStyle = '#ffffff';
    ctx.fillText('Очки: 0', 30, 40);
}

drawScene();

У нас уже появилась маленькая игровая сцена, хотя в проекте пока нет ни картинок, ни движка, ни библиотек.

Добавляем движение

Теперь будем менять playerX и перерисовывать сцену каждый кадр:

function loop() {
    playerX += 2;

    if (playerX > canvas.width) {
        playerX = -50;
    }

    drawScene();

    requestAnimationFrame(loop);
}

loop();

Когда квадрат полностью уйдёт за правую границу, мы переносим его обратно за левый край. Получается бесконечное движение.

Обрати внимание: внутри drawScene() фон каждый раз рисуется заново поверх предыдущего кадра. Поэтому отдельный clearRect() здесь уже не обязателен — сплошной фон сам полностью перекрывает Canvas.

В других проектах это будет зависеть от способа отрисовки. Сам принцип остаётся тем же: перед новым кадром нужно убрать изображение предыдущего.

Полный код проекта

Ниже весь пример целиком. Его можно сохранить как обычный index.html и открыть двойным кликом в браузере.

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Моя первая сцена Canvas</title>

    <style>
        body {
            margin: 0;
            min-height: 100vh;
            display: grid;
            place-items: center;
            background: #020617;
        }

        canvas {
            max-width: 100%;
            border: 1px solid #334155;
        }
    </style>
</head>

<body>

<canvas id="game" width="800" height="450"></canvas>

<script>
    const canvas = document.querySelector('#game');
    const ctx = canvas.getContext('2d');

    let playerX = 50;

    function drawScene() {
        // Фон.
        ctx.fillStyle = '#111827';
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        // Солнце.
        ctx.beginPath();
        ctx.arc(680, 80, 35, 0, Math.PI * 2);
        ctx.fillStyle = '#facc15';
        ctx.fill();

        // Земля.
        ctx.fillStyle = '#334155';
        ctx.fillRect(0, 380, canvas.width, 70);

        // Игрок.
        ctx.fillStyle = '#5eead4';
        ctx.fillRect(playerX, 330, 50, 50);

        // Интерфейс.
        ctx.font = '22px Arial';
        ctx.fillStyle = '#ffffff';
        ctx.fillText('Очки: 0', 30, 40);
    }

    function loop() {
        playerX += 2;

        if (playerX > canvas.width) {
            playerX = -50;
        }

        drawScene();

        requestAnimationFrame(loop);
    }

    loop();
</script>

</body>
</html>

Сейчас это ещё не игра. Здесь нет управления, цели, столкновений или условий победы. Но технически мы уже собрали основу, на которой всё это появляется: есть игровая область, координаты, состояние объекта, функция отрисовки и повторяющийся цикл.

10 команд Canvas, которых достаточно для начала

Canvas API намного больше того, что мы рассмотрели. Но изучать его целиком перед первой игрой нет смысла.

Что нужно сделать Команда
Нарисовать прямоугольник fillRect()
Нарисовать контур strokeRect()
Очистить область clearRect()
Начать новый путь beginPath()
Перейти в точку moveTo()
Провести линию lineTo()
Нарисовать круг или дугу arc()
Залить фигуру fill()
Вывести текст fillText()
Нарисовать изображение drawImage()

А для перехода от статичной картинки к игре понадобится ещё одна:

requestAnimationFrame()

Для первых проектов этого набора более чем достаточно.

Что можно сделать на Canvas после этого урока

Теперь нет особого смысла продолжать изучать функции Canvas изолированно. Лучше брать конкретную задачу и осваивать новые возможности прямо внутри проекта.

Нарисовать своего персонажа

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

Нарисовать первого героя на Canvas →

Научить персонажа двигаться

Следующий шаг — перестать менять координату автоматически и связать движение с клавиатурой. Здесь появляются обработчики нажатий и настоящее управление.

Разобраться с игровым циклом

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

Разобраться, как работает игровой цикл →

Собрать полноценную игру

После движения можно добавлять стены, столкновения, предметы, победу, проигрыш и уровни. В большом проекте «Побег из лабиринта» эти части уже соединяются в одну законченную игру.

Частые ошибки новичков

Canvas есть на странице, но ничего не рисуется

Сначала проверь, найден ли сам элемент и получен ли контекст:

const canvas = document.querySelector('#game');
const ctx = canvas.getContext('2d');

console.log(canvas);
console.log(ctx);

Если canvas равен null, скорее всего, JavaScript запускается раньше появления элемента либо в селекторе указано неправильное значение.

Фигура находится не там, где ожидалось

Помни, что точка 0, 0 находится сверху слева, а координата Y увеличивается вниз.

Также учитывай разницу между фигурами. У fillRect() координаты задают левый верхний угол, а у arc() — центр окружности.

Цвет фигуры не изменился

Настройку нужно задать до рисования:

ctx.fillStyle = 'red';
ctx.fillRect(100, 100, 50, 50);

Если поменять fillStyle после fillRect(), уже нарисованная фигура не перекрасится.

Круг не появляется

Проверь, что перед arc() вызывается beginPath(), а после него — fill() или stroke().

ctx.beginPath();
ctx.arc(100, 100, 40, 0, Math.PI * 2);
ctx.fill();

Во время движения за объектом остаётся след

Скорее всего, ты рисуешь новый кадр поверх старого, но не очищаешь Canvas.

Перед отрисовкой следующего кадра очисти экран:

ctx.clearRect(0, 0, canvas.width, canvas.height);

Или полностью перерисовывай фон, как мы сделали в финальном примере.

Изображение не отображается

Проверь путь до файла и дождись загрузки изображения перед первым вызовом drawImage().

const image = new Image();

image.src = 'player.png';

image.onload = () => {
    ctx.drawImage(image, 100, 100);
};

Частые вопросы

Что такое Canvas в JavaScript?

Canvas — HTML-элемент для программного рисования графики. Через JavaScript на нём можно выводить фигуры, текст, изображения, анимацию и игровые сцены.

Нужно ли знать JavaScript перед изучением Canvas?

Полностью знать язык не требуется, но базовые конструкции сильно упростят работу. Желательно понимать переменные, функции, условия и хотя бы на базовом уровне события.

Можно ли сделать игру только на JavaScript и Canvas?

Да. Для небольшой 2D-игры достаточно обычных HTML, CSS, JavaScript и Canvas. Игровой движок становится полезен позже, когда хочется получить готовые системы сцен, физики, загрузки ресурсов, анимаций и другие инструменты.

Чем Canvas отличается от обычного HTML?

В HTML кнопка, картинка или блок остаются самостоятельными DOM-элементами. Внутри Canvas браузер видит только один холст. Всё содержимое сцены рисует и обновляет JavaScript.

Чем Canvas отличается от SVG?

SVG хранит графические элементы как отдельные объекты, с которыми браузер может работать через DOM. Canvas рисует итоговую картинку на поверхности. Для динамической игровой сцены Canvas часто оказывается проще, особенно когда объектов нужно постоянно перерисовывать.

Canvas подходит только для 2D?

Контекст 2d, который мы использовали в этом уроке, предназначен для двумерной графики. Для трёхмерной графики в браузере существуют WebGL и более высокоуровневые библиотеки поверх него.

Нужно ли учить все методы Canvas?

Нет. Для первой игры лучше освоить небольшой рабочий набор: прямоугольники, пути, круги, текст, изображения, очистку экрана и requestAnimationFrame(). Остальные возможности можно изучать по мере появления конкретных задач.

Что изучать после Canvas?

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

Что дальше

Мы начали с пустого прямоугольника и за один урок дошли до движущейся игровой сцены. Теперь ты знаешь, как устроены координаты Canvas, умеешь рисовать базовые фигуры, линии, круги и текст, загружать изображения и перерисовывать экран через requestAnimationFrame().

Этого достаточно, чтобы закончить изучать Canvas как набор отдельных команд и перейти к более интересной части — использовать их для создания игровых объектов.

Следующий шаг — вместо безымянного квадрата нарисовать настоящего персонажа из тех же самых кругов, линий и фигур.

Обложка книги JavaScript с нуля: учимся программировать, создавая игры

Книга по JavaScript для начинающих

«JavaScript с нуля: учимся программировать, создавая игры»

Пошаговый формат с маленькими практическими задачами: от базового синтаксиса до первых игровых механик без перегруза теорией.

Читать книгу на ЛитРес →