Это всегда был ты, Canvas2D

Аарон Краески
Aaron Krajeski

В мире шейдеров, сеток и фильтров Canvas2D может вас не впечатлить. Но он должен! 30–40% веб-страниц имеют элемент <canvas> , а 98% всех холстов используют контекст рендеринга Canvas2D. Canvas2D есть в автомобилях, на холодильниках и в космосе (на самом деле).

Конечно, API немного отстает от времени, когда дело касается современного 2D-рисования. К счастью, мы усердно работали над внедрением новых функций в Canvas2D, чтобы догнать CSS, оптимизировать эргономику и улучшить производительность.

Часть 1: изучение CSS

В CSS есть несколько команд рисования, которых очень не хватает в Canvas2D. С новым API мы добавили несколько наиболее востребованных функций:

Круглый прямоугольник

Скругленные прямоугольники: краеугольный камень Интернета, вычислительной техники, почти цивилизации.

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

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'magenta';

const top = 10;
const left = 10;
const width = 200;
const height = 100;
const radius = 20;

ctx.beginPath();
ctx.moveTo(left + radius, top);
ctx.lineTo(left + width - radius, top);
ctx.arcTo(left + width, top, left + width, top + radius, radius);
ctx.lineTo(left + width, top + height - radius);
ctx.arcTo(left + width, top + height, left + width - radius, top + height, radius);
ctx.lineTo(left + radius, top + height);
ctx.arcTo(left, top + height, left, top + height - radius, radius);
ctx.lineTo(left, top + radius);
ctx.arcTo(left, top, left + radius, top, radius);
ctx.stroke();

Все это было необходимо для скромного, простого скругленного прямоугольника:

Скругленный прямоугольник.

В новом API появился метод roundRect() .

ctx.roundRect(upper, left, width, height, borderRadius);

Таким образом, вышеизложенное можно полностью заменить следующим:

ctx.roundRect(10, 10, 200, 100, 20);

Метод ctx.roundRect() также принимает массив для аргумента borderRadius из четырех чисел. Эти радиусы управляют четырьмя углами скругленного прямоугольника так же, как и для CSS . Например:

ctx.roundRect(10, 10, 200, 100, [15, 50, 30]);

Посмотрите демо-версию и поиграйтесь !

Конический градиент

Вы видели линейные градиенты:

const gradient = ctx.createLinearGradient(0, 0, 200, 100);
gradient.addColorStop(0, 'blue');
gradient.addColorStop(0.5, 'magenta');
gradient.addColorStop(1, 'white');
ctx.fillStyle = gradient;
ctx.fillRect(10, 10, 200, 100);

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

Радиальные градиенты:

const radialGradient = ctx.createRadialGradient(150, 75, 10, 150, 75, 70);
radialGradient.addColorStop(0, 'white');
radialGradient.addColorStop(0.5, 'magenta');
radialGradient.addColorStop(1, 'lightblue');

ctx.fillStyle = radialGradient;
ctx.fillRect(0, 0, canvas.width, canvas.height);

Радиальный градиент.

А как насчет красивого конического градиента?

const grad = ctx.createConicGradient(0, 100, 100);

grad.addColorStop(0, 'red');
grad.addColorStop(0.25, 'orange');
grad.addColorStop(0.5, 'yellow');
grad.addColorStop(0.75, 'green');
grad.addColorStop(1, 'blue');

ctx.fillStyle = grad;
ctx.fillRect(0, 0, 200, 200);

Конический градиент.

Текстовые модификаторы

Возможности рендеринга текста Canvas2Ds катастрофически отстают. Chrome добавил несколько новых атрибутов для рендеринга текста Canvas2D:

Все эти атрибуты соответствуют своим аналогам CSS с такими же именами.

Часть 2: эргономичные настройки

Раньше некоторые вещи с Canvas2D были возможны, но излишне сложны в реализации. Вот некоторые улучшения качества жизни для разработчиков JavaScript, которые хотят использовать Canvas2D:

Сброс контекста

Чтобы объяснить очистку холста, я написал маленькую забавную функцию для рисования ретро-узора :

draw90sPattern();

Ретро-узор из треугольников и квадратов.

Отлично! Теперь, когда я закончил с этим узором, я хочу очистить холст и нарисовать что-нибудь еще. Подождите, как нам снова очистить холст? О, да! ctx.clearRect() , конечно.

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

Хм... не сработало. О да! Сначала мне нужно сбросить преобразование:

ctx.resetTransform();
ctx.clearRect(0, 0, canvas.width, canvas.height);
Чистый холст.

Отлично! Хороший чистый холст. Теперь давайте начнем рисовать красивую горизонтальную линию:

ctx.moveTo(10, 10);
ctx.lineTo(canvas.width, 10);
ctx.stroke();

Горизонтальная и диагональная линия.

Грррр! Это неправильно! 😡 Что здесь делает эта дополнительная линия? И почему она розовая? Ладно, давайте просто проверим StackOverflow.

canvas.width = canvas.width;

Почему это так глупо? Почему это так сложно?

Ну, теперь уже нет. С новым API у нас есть простое, элегантное, красивое новаторское:

ctx.reset();

Извините, что так долго.

Фильтры

Фильтры SVG — это целый мир. Если вы новичок в этом деле, я настоятельно рекомендую вам прочитать The Art Of SVG Filters And Why It Is Awesome , где показаны некоторые из их удивительных возможностей.

Фильтры в стиле SVG уже доступны для Canvas2D! Вам просто нужно быть готовым передать фильтр как URL-адрес, указывающий на другой элемент фильтра SVG на странице:

<svg>
  <defs>
    <filter id="svgFilter">
      <feGaussianBlur in="SourceGraphic" stdDeviation="5" />
      <feConvolveMatrix kernelMatrix="-3 0 0 0 0.5 0 0 0 3" />
      <feColorMatrix type="hueRotate" values="90" />
    </filter>
  </defs>
</svg>
const canvas = document.createElement('canvas');
canvas.width = 500;
canvas.height = 400;
const ctx = canvas.getContext('2d');
document.body.appendChild(canvas);

ctx.filter = "url('#svgFilter')";
draw90sPattern(ctx);

Что довольно сильно портит нашу картину:

Ретро-узор с нанесенным эффектом размытия.

Но что, если вы хотите сделать вышеописанное, но остаться в рамках JavaScript и не возиться со строками? С новым API это вполне возможно.

ctx.filter = new CanvasFilter([
  { filter: 'gaussianBlur', stdDeviation: 5 },
  {
    filter: 'convolveMatrix',
    kernelMatrix: [
      [-3, 0, 0],
      [0, 0.5, 0],
      [0, 0, 3],
    ],
  },
  { filter: 'colorMatrix', type: 'hueRotate', values: 90 },
]);

Проще простого! Попробуйте и поиграйтесь с параметрами в демо-версии здесь .

Часть 3: улучшение производительности

С новым API Canvas2D мы также хотели улучшить производительность там, где это возможно. Мы добавили несколько функций, чтобы дать разработчикам более тонкий контроль над их веб-сайтами и обеспечить максимально плавную частоту кадров:

Буду часто читать

Используйте getImageData() для чтения пиксельных данных обратно с холста. Это может быть очень медленно. Новый API дает вам возможность явно пометить холст для обратного чтения (например, для генеративных эффектов). Это позволяет оптимизировать вещи под капотом и поддерживать холст быстрым для большего разнообразия вариантов использования. Эта функция уже некоторое время присутствует в Firefox, и мы наконец-то делаем ее частью спецификации холста.

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });

Потеря контекста

Давайте снова сделаем грустные вкладки счастливыми! В случае, если у клиента закончится память GPU или случится какая-то другая катастрофа с вашим холстом, теперь вы можете получить обратный вызов и перерисовать по мере необходимости:

const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');

canvas.addEventListener('contextlost', onContextLost);
canvas.addEventListener('contextrestored', redraw);

Если вы хотите узнать больше о контексте и потерях холста, хорошее объяснение можно найти на вики WHATWG.

Заключение

Независимо от того, новичок ли вы в Canvas2D, пользуетесь ли вы им годами или избегаете его использования годами, я здесь, чтобы сказать вам, чтобы вы дали холсту еще один взгляд. Это API-next-door, который был там все это время.