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

Canvas 2D — программируемая графика в браузере

Разработчику

См. также: Работа с HTML (DOM) · События · ResizeObserver · Применение JS в вебе · Web API в браузере


Задача

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

Сценарий из учебной практики:

  1. В разметке — <canvas width="400" height="300">.
  2. В скрипте — контекст getContext('2d').
  3. Рисование — методами fillRect, stroke, drawImage, fillText.
  4. Обновление кадра — clearRect и перерисовка; для анимации — requestAnimationFrame.

Canvas — Web API, не часть ECMAScript. Для 3D используют WebGL; для векторной разметки в DOM — SVG.


Разметка и контекст

<canvas id="chart" width="400" height="300">
<p>Ваш браузер не поддерживает canvas</p>
</canvas>
АтрибутРоль
width, heightвнутренний размер буфера в пикселях (по умолчанию 300×150)
Содержимое внутри тегаfallback для старых браузеров
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');

console.log(ctx.canvas === canvas); // true
console.log(canvas.width, canvas.height);

Контекст CanvasRenderingContext2D (ctx) — единая точка входа: цвета, линии, пути, текст, картинки. Свойство ctx.canvas возвращает элемент <canvas>.

CSS и размер буфера

Если задать canvas { width: 800px; height: 600px; }, а атрибуты width="400" height="300", браузер растянет bitmap — картинка будет размытой. Для Retina умножайте атрибуты на devicePixelRatio или синхронизируйте размеры в ResizeObserver.


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

  • Начало координат — левый верхний угол холста.
  • Ось X растёт вправо, Y — вниз.
  • Единица — пиксель внутреннего буфера (не CSS-пиксель при масштабировании).

Углы для arc() задают в радианах:

const degrees = 90;
const radians = (Math.PI / 180) * degrees;

Заливка, контур, прямоугольники

СвойствоНазначение
fillStyleцвет или градиент заливки (по умолчанию #000)
strokeStyleцвет контура
lineWidthтолщина линии (px)
lineCap'butt', 'round', 'square'
lineJoin'miter', 'bevel', 'round'
globalAlphaпрозрачность 0.0–1.0 для следующих операций
МетодНазначение
fillRect(x, y, w, h)залитый прямоугольник
strokeRect(x, y, w, h)контур прямоугольника
clearRect(x, y, w, h)очистить область (прозрачный фон)
ctx.fillStyle = 'rgb(0, 0, 255)';
ctx.fillRect(50, 50, 200, 200);

ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
ctx.fillRect(100, 100, 150, 150);

ctx.strokeStyle = 'rgb(0, 0, 255)';
ctx.lineWidth = 3;
ctx.strokeRect(50, 50, 200, 200);

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

Цвета — в любом формате CSS: 'red', '#ff0000', 'rgb(...)', 'rgba(...)'.


Пути — линии, дуги, фигуры

Сложные фигуры строят контуром (path):

МетодНазначение
beginPath()начать новый контур
moveTo(x, y)перенести «перо», не рисуя
lineTo(x, y)отрезок от текущей точки
arc(x, y, r, start, end, counterclockwise?)дуга / окружность
arcTo(x1, y1, x2, y2, radius)дуга через опорные точки
quadraticCurveTo(cpx, cpy, x, y)квадратичная кривая Безье
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)кубическая кривая Безье
rect(x, y, w, h)добавить прямоугольник к пути
closePath()замкнуть контур
fill() / stroke()залить или обвести текущий путь
clip()маска по контуру — дальнейшая отрисовка только внутри
ctx.beginPath();
ctx.moveTo(20, 20);
ctx.lineTo(100, 20);
ctx.lineTo(100, 80);
ctx.closePath();
ctx.strokeStyle = 'rgb(255, 0, 0)';
ctx.lineWidth = 3;
ctx.stroke();
ctx.fillStyle = 'rgb(0, 0, 255)';
ctx.fill();

ctx.beginPath();
ctx.arc(200, 150, 100, 0, Math.PI * 2);
ctx.stroke();

Пунктир: setLineDash([15, 10]), смещение — lineDashOffset. Сброс — setLineDash([]).


Текст

Свойство / методНазначение
fontстрока как в CSS ('bold 16px Verdana')
textAlign'left', 'right', 'center', 'start', 'end'
textBaseline'top', 'middle', 'bottom', 'alphabetic', …
fillText(text, x, y [, maxWidth])залитый текст
strokeText(text, x, y [, maxWidth])контурный текст
measureText(text).widthширина строки в px
ctx.font = 'italic 16pt Verdana';
ctx.fillStyle = 'blue';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('JavaScript', canvas.width / 2, canvas.height / 2);

Изображения — drawImage

Источник — HTMLImageElement, другой <canvas>, <video> или new Image() после загрузки.

const img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0); // целиком в (0,0)
ctx.drawImage(img, 0, 0, 200, 100); // масштаб в w×h
ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh); // фрагмент → область
};
img.src = 'photo.jpg';

До события load рисовать нельзя — bitmap пустой. Для <img id="photo"> в DOM достаточно drawImage(document.getElementById('photo'), 0, 0).


Градиенты и текстуры

Линейный градиент:

const lg = ctx.createLinearGradient(0, 0, 200, 0);
lg.addColorStop(0, 'black');
lg.addColorStop(0.5, 'rgba(0, 0, 255, 0.5)');
lg.addColorStop(1, '#ff0000');
ctx.fillStyle = lg;
ctx.fillRect(0, 0, 200, 100);

Радиальный: createRadialGradient(x0, y0, r0, x1, y1, r1) + те же addColorStop.

Паттерн (текстура): createPattern(image, 'repeat' | 'repeat-x' | 'repeat-y' | 'no-repeat') → присвоить fillStyle или strokeStyle.


Сохранение состояния и трансформации

save() кладёт в стек текущие стили, трансформации и clip; restore() возвращает предыдущие.

МетодНазначение
translate(tx, ty)сдвиг начала координат
rotate(angle)поворот (радианы), вокруг текущего origin
scale(sx, sy)масштаб по осям
ctx.save();
ctx.translate(200, 150);
for (let i = 0; i < 3; i++) {
ctx.rotate(Math.PI / 6);
ctx.strokeRect(-50, -50, 100, 100);
}
ctx.restore();

Без save/restore трансформации накапливаются — типичный источник «уплывшей» графики.


Наложение, тени, прозрачность

СвойствоНазначение
globalCompositeOperationрежим смешивания ('source-over', 'destination-over', 'lighter', 'xor', …)
globalAlphaобщая прозрачность следующих фигур
shadowOffsetX, shadowOffsetYсмещение тени
shadowBlurразмытие
shadowColorцвет тени

Тень применяется к следующим операциям рисования (fill, stroke, fillText).


Пиксели — ImageData

МетодНазначение
getImageData(x, y, w, h)снять фрагмент bitmap
createImageData(w, h)пустой буфер
putImageData(data, x, y)вывести буфер на холст

Массив imageData.data — последовательность RGBA байт (0–255) по строкам: [R, G, B, A, R, G, B, A, …].

const imageData = ctx.createImageData(100, 100);
const { data } = imageData;
for (let i = 0; i < data.length; i += 4) {
data[i] = 255; // R
data[i + 1] = 0; // G
data[i + 2] = 0; // B
data[i + 3] = 255; // A
}
ctx.putImageData(imageData, 0, 0);

Подходит для фильтров и процедурной генерации; для обычных диаграмм чаще хватает path API.


Клики и hit-testing

Метод isPointInPath(x, y) проверяет, попадает ли точка в текущий контур — основа выбора фигур мышью.

canvas.addEventListener('click', (event) => {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
// при CSS-масштабе умножьте на canvas.width / rect.width

ctx.beginPath();
ctx.arc(100, 100, 40, 0, Math.PI * 2);
if (ctx.isPointInPath(x, y)) {
console.log('Попали в круг');
}
});

Координаты события — в CSS-пикселях относительно элемента; при несовпадении с canvas.width нужно масштабировать.


Анимация и перерисовка

function loop(timestamp) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFrame(timestamp);
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

requestAnimationFrame синхронизирует кадры с частотой экрана и ставит на паузу во вкладке в фоне. Для графиков в flex-layout пересчитывайте размеры в ResizeObserver.


Canvas, SVG и WebGL

ТехнологияКогда выбирать
Canvas 2Dмного пикселей, анимация, игры, растровые эффекты
SVGмасштабируемая векторная графика, доступность, CSS к фигурам
WebGL3D, тяжёлая GPU-графика

Краткий пример в справочнике BOM — 22.md, графика Canvas; DOM и события — 102.md, 23.md.


Частые ошибки

ОшибкаЧто делать
Рисование до img.onloadждать загрузки или decode()
Размытый холст на Retinaсинхронизировать width/height с devicePixelRatio
«Уехали» координатыоборачивать трансформации в save/restore
Нет beginPath() между фигурамиконтуры сливаются — начинать путь заново
isPointInPath без учёта CSS-размерамасштабировать координаты клика

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").