Canvas API— это интерфейс для отрисовки графики в браузере. С помощью HTML-элемента <canvas> можно создавать холст (именно так canvas переводится с английского) и рисовать графику внутри него с помощью JavaScript-кода.

<canvas id="canvas"></canvas>

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

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

💫 Научитесь создавать анимации

Приходите на наши курсы — изучите самые популярные и интересные техники для создания эффектов на CSS и JavaScript.

Шаг 1. Напишем разметку

Создание графика начнём, конечно, с разметки. Кроме самого элемента <canvas> мы также сверстаем небольшую форму, которая будет принимать на вход значения. Так мы сможем динамически обновлять график с учётом введённых данных.

<section class="chart">
    <h1 class="chart__title">Популярность языков программирования</h1>

    <form class="chart__data" action="#" method="POST">
      <div class="chart__input-group">
        <label class="chart__label" for="javascript">JavaScript</label>
        <input
          class="chart__input input-field"
          type="number"
          name="javascript"
          id="javascript"
          min="0"
          max="100"
          required
        >
        <span>%</span>
      </div>

      <!-- Остальные поля ввода: PHP и Python -->

      <input
        class="chart__submit-button input-field"
        type="submit"
        value="Показать график"
      >
    </form>

    <div class="chart__canvas-wrapper">
      <canvas id="canvas" width="500" height="250">
        Браузер не поддерживает Canvas
      </canvas>
    </div>
  </section>

На что здесь стоит обратить внимание:

  • Вместо пути в атрибуте action мы оставили заглушку #. В теории нам может понадобится отправить на сервер данные, введённые пользователем, но пока такая задача не стоит.
  • Для каждого поля ввода прописали атрибут name, в котором отражено его назначение. Например, в нашем случае значение javascript поможет нам использовать полученные данные при отрисовке графика.
  • Так как значения в полях ввода должны задаваться в процентах, мы установили диапазон допустимых значений с помощью min и max.
  • Элементу <canvas> прописали размер 500×250 пикселей. По умолчанию он равен 300×150 пикселей.
  • Внутри <canvas> оставили информационное сообщение на случай, если браузер пользователя не поддерживает технологию.

Стилизация элементов будет максимально простой, поэтому не будем её разбирать в рамках туториала. Стили можно посмотреть в файле style.css в интерактивной демонстрации.

Шаг 2. Соберём данные для графика

Прежде чем рисовать график, нам нужно получить данные из формы. Для этого найдём все поля ввода в форме и создадим на основе введённых значений массив объектов с информацией о каждом элементе графика. В нашем случае это будут языки JavaScript, PHP и Python — три объекта.

const COLORS = [
  `#b3d5fc`,
  `#98d9d9`,
  `#ede493`
];

const inputElements = document.querySelectorAll(`.chart__input`);

const getData = (inputElements) => {
  return Array.from(inputElements).map((input, index) => ({
    name: input.name,
    value: input.value,
    color: COLORS[index]
  }));
};

const items = getData(inputElements);

Функция getData() получает на вход коллекцию элементов, преобразует её в массив, а затем на основе каждого элемента создаёт объект с нужными для нашего графика данными: названием (язык программирования), значением (популярность в процентах) и цветом (его будем использовать для рисования столбца на графике).

Для задания цвета мы прибегли к самому примитивному способу — просто завели константу с нужным количеством цветов и присвоили каждому из объектов соответствующий по индексу цвет. В реальной разработке лучше получить цвет другим способом — например, сгенерировать в формате HEX с помощью JavaScript. Тогда получится избежать жёсткой привязки к количеству элементов.

Шаг 3. Подготовим холст

Начнём работу над созданием графика. Из элемента <canvas> можно получить объект, который называется контекстом отрисовки. Объект содержит свойства и методы для рисования, именно с помощью него можно создавать весь контент внутри Canvas, а также манипулировать им. Для получения объекта контекста нужно воспользоваться методом getContext():

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

В качестве параметра в метод getContext() мы передали значение 2d — это контекст отрисовки для работы с двумерной графикой. Именно его мы будем использовать, но есть и другие — например, webgl для трёхмерной графики.

Шаг 4. Нарисуем график

Свойства и методы контекста отрисовки

Для отрисовки чего-либо на Canvas нужно вызывать соответствующие методы контекста, а для управления параметрами отрисовки — задавать свойства. Мы рассмотрим те из них, которые понадобятся в туториале. Описание остальных можно найти на MDN.

  • fillStyle — свойство определяет цвет заливки элементов. Например, текста или геометрической фигуры.
  • font — свойство задаёт параметры шрифта. Синтаксис такой же, как у CSS-свойства font.
  • fillText(text, x, y) — отрисовывает строку text, начиная с указанных координат. Координата x определяет расстояние от точки отсчёта по горизонтальной оси, а y — по вертикальной. Начало отсчёта холста расположено в левом верхнем углу.
  • fillRect(x, y, width, height) — рисует прямоугольник с заливкой с указанными шириной и высотой, начиная с координат x и y.
  • clearRect(x, y, width, height) — очищает прямоугольник, описанный параметрами. Можно использовать для очистки холста при перерисовке.
  • translate(x, y) — перемещает начало координат холста на x по горизонтали и y по вертикали относительно начального положения.
  • rotate(angle) — поворачивает систему координат по часовой стрелке на указанный угол. Значение задаётся в радианах.
  • save() — сохраняет текущее состояние холста, в том числе заданные свойства (например, fillStyle и font).
  • restore() — применяет сохранённое ранее состояние холста.

Константы и перечисления

Этот пункт можно было бы пропустить и, если нам в коде понадобилось бы значение, например, ширины столбца графика, написать просто 50. Такие числа в программировании называются «магическими» и считаются плохой практикой в реальной разработке. И для этого есть несколько веских причин. Например, если число нужно будет изменить, то это придётся делать в каждом фрагменте программы, где оно используется. А ещё часто из контекста может быть неясно, что это вообще за число. Эту проблему как раз решают константы с понятным названием. Мы будем привыкать сразу писать хороший код, поэтому вынесем все значения в константы. А если несколько констант относятся к одной сущности, объединим их в перечисления.

const MAX_PERCENTAGE = 100;

const Gap = {
  HORIZONTAL: 100,
  VERTICAL: 30
}

const BarCoordinate = {
  INITIAL_X: 80,
  INITIAL_Y: 220
}

const BarSize = {
  MAX_HEIGHT: 190,
  WIDTH: 50
};

const LabelCoordinate = {
  INITIAL_X: 30,
  INITIAL_Y: 70
}

const Font = {
  SIZE: `18px`,
  FAMILY: `Tahoma`
};

Здесь всё, что нам понадобится для рисования графика — начальные координаты элементов, размеры, отступы, параметры шрифта.

Сдвиг системы координат

При отрисовке графика мы будем использовать сочетание методов translate() и rotate():

ctx.translate(0, canvas.height);
ctx.rotate(-Math.PI/2);

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

График

Теперь нам нужно воспользоваться методами 2d контекста, которые мы разобрали выше, и нарисовать график на основе данных из формы.

// Получаем на вход items — массив объектов с данными
const renderChart = (items) => {
  // Очищаем всю область холста
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Задаём координаты для первого столбца и подписи
  let currentBarX = BarCoordinate.INITIAL_X;
  let currentLabelY = LabelCoordinate.INITIAL_Y;
    // Определяем горизонтальный отступ между соседними столбцами
    const gapBetweenBars = BarSize.WIDTH + Gap.HORIZONTAL;

  // Проходим в цикле по каждому объекту в массиве с данными
  // Для каждого будет нарисован отдельный столбец
  for (const item of items) {
    // Вычисляем высоту столбца с учётом процентов из данных
    const barHeight = (item.value * BarSize.MAX_HEIGHT) / MAX_PERCENTAGE;

    // Задаём цвет заливки любых элементов, которые будут создаваться дальше
    ctx.fillStyle = item.color;
    // Задаём параметры шрифта
    ctx.font = `${Font.SIZE} ${Font.FAMILY}`;
    // Запоминаем текущие параметры холста
    ctx.save();
    // Сдвигаем начало коодинат вниз по оси y на величину canvas.height
    ctx.translate(0, canvas.height);
    // Поворачиваем систему координат на 90 градусов против часовой стрелки
    // Math.PI/2 — перевод 90 градусов в радианы
    ctx.rotate(-Math.PI/2);
    // В изменённой системе координат рисуем текст снизу вверх
    ctx.fillText(item.name.toUpperCase(), LabelCoordinate.INITIAL_X, currentLabelY);
    // Возвращаемся к изначальной системе координат
    ctx.restore();
    // Рисуем столбец
    // Отрицательное значение — отрисовка снизу вверх
    ctx.fillRect(currentBarX, BarCoordinate.INITIAL_Y, BarSize.WIDTH, -barHeight);

    // Для следующего столбца обновляем координаты с учётом отступа
    currentBarX += gapBetweenBars;
    currentLabelY += gapBetweenBars;
  }
};

Вы заметили, что в самом начале мы очистили весь холст? Если этого не сделать, изображения будут накладываться друг на друга. В данном случае такой вариант не подходит, потому что мы хотим, чтобы при каждом клике на кнопку «Показать график» происходила перерисовка с учётом новых данных. Именно эту функциональность нам осталось запрограммировать.

Шаг 5. Реализуем отрисовку графика при отправке формы

У нас уже есть функция getData(), которая возвращает данные, и renderChart(), способная отрисовать график. Осталось только связать их вместе при клике на кнопку «Показать график».

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

const formElement = document.querySelector(`.chart__data`);

formElement.addEventListener(`submit`, (evt) => {
  // Отменяем действие по умолчанию — отправку формы на сервер (которого нет)
  evt.preventDefault();

  // Отрисовываем график
  renderChart(getData(inputElements));
  // Сбрасываем значения полей ввода
  formElement.reset();
});

Готово! Мы создали простой график на Canvas. Его можно доработать, например, добавив проверку на сумму введённых значений, которая по логике должна быть равна 100%, сейчас это правило можно нарушить. Поэкспериментировать с этим и другими улучшениями можно в нашей интерактивной демонстрации.

Полезности


«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.

ТелеграмПодкастБесплатные учебники

Читать дальше

AOT против JIT-компилятора: что лучше для разработки на Angular?

AOT против JIT-компилятора: что лучше для разработки на Angular?

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

Читать дальше
JS
  • 25 мая 2025
Динамические формы в Angular 19: пошаговое руководство

Динамические формы в Angular 19: пошаговое руководство

Формы — неотъемлемая часть большинства веб-приложений: будь то регистрация, ввод данных или опросы. Модуль реактивных форм в Angular отлично подходит для создания статичных форм, но во многих случаях требуется, чтобы форма могла динамически адаптироваться в зависимости от действий пользователя или внешних данных.

В этой статье мы рассмотрим, как создавать динамические формы с использованием автономных компонентов в Angular 19, применяя модульный подход, который избавляет от необходимости использовать традиционные модули Angular. В сопроводительном репозитории на GitHub для оформления форм используется Tailwind CSS, однако в статье внимание сосредоточено исключительно на логике динамических форм. Tailwind и связанные с ним настройки намеренно не включены в примеры, чтобы сохранить акцент на основной теме.

Читать дальше
JS
  • 25 мая 2025
Как обнаружить изменения в Angular: пошаговая инструкция

Как обнаружить изменения в Angular: пошаговая инструкция

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

Читать дальше
JS
  • 24 мая 2025
Компоненты в Angular 18: пошаговое руководство

Компоненты в Angular 18: пошаговое руководство

Angular развивается стремительно, и с выходом версии 18 появились новые возможности, которые разработчики могут использовать в своей работе. Одним из ключевых изменений в Angular 18 стало удаление традиционного файла app.module.ts — ему на смену пришли standalone-компоненты. Если вы только начинаете работать с Angular или переходите с более ранней версии, это пошаговое руководство поможет вам разобраться в базовых принципах компонентов в Angular 18. Независимо от вашего уровня — новичок вы или опытный разработчик — этот туториал покажет, как создавать, управлять и эффективно использовать компоненты в Angular.

Читать дальше
JS
  • 19 мая 2025
Полное руководство по Angular @if

Полное руководство по Angular @if

Одно из самых заметных нововведений в Angular — это встроенный синтаксис для управляющих конструкций, который появился в версии 17. Он решает одну из самых частых задач, с которой сталкивается каждый разработчик: показывать или скрывать элементы на странице в зависимости от условия. Раньше для этого использовали привычную структурную директиву *ngIf. Теперь у нас есть более современная альтернатива — синтаксис @if, часть нового подхода к управлению шаблоном.

В этом гайде мы сравним оба варианта, разберёмся, чем @if лучше, и покажем, как можно перейти на него автоматически. Также поговорим об одной распространённой ошибке — о том, как не стоит использовать @if вместе с пайпом async.

Читать дальше
JS
  • 18 мая 2025
Модули Angular для организации кода и ленивой загрузки

Модули Angular для организации кода и ленивой загрузки

Модули — один из ключевых инструментов Angular для построения масштабируемых и поддерживаемых приложений. В этой статье мы подробно рассмотрим:

  • что такое модули в Angular;
  • зачем они нужны;
  • как их использовать для структурирования кода;
  • как реализовать «ленивую» загрузку модулей;
  • и чем отличаются Feature, Core и Shared модули.

Если вы только начинаете изучать Angular или хотите углубить свои знания, эта статья поможет вам лучше понять, как правильно организовать архитектуру Angular-приложения.

Читать дальше
JS
  • 12 мая 2025
Навигация в Angular: RouterLink, Router.navigate и Router.navigateByUrl

Навигация в Angular: RouterLink, Router.navigate и Router.navigateByUrl

Директива RouterLink позволяет настраивать переходы между маршрутами прямо в шаблоне Angular. А методы Router.navigate и Router.navigateByUrl, доступные в классе Router, дают возможность управлять навигацией программно — прямо из кода компонентов.

Разберёмся, как работают RouterLink, Router.navigate и Router.navigateByUrl.

Читать дальше
JS
  • 11 мая 2025
Полное руководство по Lazy Loading в Angular

Полное руководство по Lazy Loading в Angular

Если вы создаёте большое Angular-приложение, вам наверняка важно, чтобы оно загружалось быстро. Представьте, что вы устраиваете вечеринку и хотите подавать закуски не сразу, а по мере прихода гостей, чтобы не перегрузить кухню. «Ленивая» загрузка в Angular работает примерно так же: вместо того чтобы загружать всё приложение целиком сразу, вы подгружаете только те части, которые нужны — и только когда они нужны.

В этом пошаговом руководстве мы разберём, как реализовать lazy loading в Angular.

Читать дальше
JS
  • 10 мая 2025
Все (ну или почти все) способы автоматически перезагрузить страницу раз в N секунд

Все (ну или почти все) способы автоматически перезагрузить страницу раз в N секунд

Иногда страницу нужно просто перезагрузить. Полностью. Не компонент, не блок, а именно целиком. Без обсуждений, без лишней логики. Например, чтобы:

  • экран с результатами обновлялся каждые 10 секунд;
  • интерфейс на стенде показывал последние данные без кнопок;
  • страницы в интранете не устаревали, пока никто не смотрит.

Это можно сделать в любой связке: HTML, JS, Python, PHP, Go, Node.js — не важно. Ну и если говорить совсем прямо, то совсем разных способов всего три, а остальное просто вариации.

Читать дальше
JS
  • 5 мая 2025