Использование API рисования CSS
API рисования CSS предназначен для того, чтобы разработчики могли программно определять изображения, которые затем можно использовать в любом месте, где можно вызывать изображение CSS, например, в CSS background-image, border-image, mask-image и т. д.
Для программированного создания изображения, используемого таблицей стилей CSS, нам нужно выполнить несколько шагов:
- Определить работу с помощью функции
registerPaint() - Зарегистрировать работу
- Включить функцию CSS
paint()
Чтобы подробнее рассмотреть эти шаги, начнем с создания половинного выделения фона, как в этом заголовке:
Примечание: Полный исходный код всех примеров в этой статье можно найти по адресу https://github.com/mdn/dom-examples/tree/main/css-painting, а примеры работают в режиме реального времени по адресу https://mdn.github.io/dom-examples/css-painting/.
CSS обработка работы
В файле внешнего скрипта мы используем функцию registerPaint() для именования нашего CSS Paint worklet. Она принимает два параметра. Первый — имя, которое мы даем worklet — это имя, которое мы будем использовать в нашем CSS в качестве параметра функции paint() при применении этого стилизации к элементу. Второй параметр — класс, который выполняет всю магию, определяя параметры контекста и то, что необходимо отобразить на двумерном холсте, который будет нашим изображением.
registerPaint(
"headerHighlight",
class {
/*
define if alpha transparency is allowed alpha
is set to true by default. If set to false, all
colors used on the canvas will be fully opaque
*/
static get contextOptions() {
return { alpha: true };
}
/*
ctx is the 2D drawing context
a subset of the HTML Canvas API.
*/
paint(ctx) {
ctx.fillStyle = "hsl(55 90% 60% / 100%)";
ctx.fillRect(0, 15, 200, 20); /* order: x, y, w, h */
}
},
);
В этом примере класса мы определили один параметр контекста с помощью функции contextOptions(): мы вернули объект, указывающий на то, что разрешена прозрачность альфа-канала.
Затем мы использовали функцию paint() для рисования на нашем холсте.
Функция paint() может принимать три аргумента. Здесь мы предоставили один аргумент: контекст отрисовки (мы рассмотрим более подробно позже), который часто обозначается переменной ctx. Двумерный контекст отрисовки является подмножеством API HTML Canvas; версия, доступная Houdini (называемая PaintRenderingContext2D), является еще более ограниченной версией, содержащей большинство функций, доступных в полном API Canvas, за исключением API CanvasImageData, CanvasUserInterface, CanvasText и CanvasTextDrawingStyles.
Мы определили fillStyle как hsl(55 90% 60% / 100%), что является оттенком желтого цвета, а затем вызываем fillRect() для создания прямоугольника этого цвета. Параметры fillRect() — это, по порядку, начало координат оси x, начало координат оси y, ширина и высота. fillRect(0, 15, 200, 20) приводит к созданию прямоугольника шириной 200 единиц и высотой 20 единиц, расположенного на расстоянии 0 единиц слева и 15 единиц сверху от области содержимого.
Мы можем использовать свойства CSS background-size и background-position для изменения размера или перемещения этого изображения фона, но это значения по умолчанию для размера и расположения желтой рамки, созданной в нашем worklet.
Мы постарались сохранить пример простым. Для получения дополнительных параметров ознакомьтесь с документацией холста. Мы также добавим немного сложности в дальнейшем в этом руководстве.
Регистрация worklet
Для использования worklet рисования нам необходимо зарегистрировать его с помощью addModule() и включить его в наш CSS, убедившись, что селектор CSS соответствует узлу DOM в нашем HTML.
Настройка и проектирование нашего worklet рисования происходили во внешнем скрипте, показанном выше. Нам необходимо зарегистрировать этот worklet из нашего основного скрипта.
CSS.paintWorklet.addModule("nameOfPaintWorkletFile.js");
Это можно сделать с помощью метода addModule() worklet рисования в <script> в основном HTML-файле или во внешнем JavaScript-файле, связанном с документом.
Использование worklet рисования
В нашем примере worklet рисования хранится вместе с основным скриптом. Для его использования мы сначала регистрируем его:
CSS.paintWorklet.addModule("header-highlight.js");
Ссылка на worklet рисования в CSS
После регистрации worklet рисования мы можем использовать его в CSS. Используйте функцию CSS paint() как любую другую функцию типа <image>, используя тот же идентификатор строки, что и в функции registerPaint() worklet рисования.
.fancy {
background-image: paint(headerHighlight);
}
Сборка вместе
Затем мы можем добавить класс fancy к любому элементу страницы, чтобы добавить желтую рамку в качестве фона:
<h1 class="fancy">My Cool Header</h1>
Следующий пример будет выглядеть так же, как изображение выше в браузерах, поддерживающих API рисования CSS .
Хотя вы не можете взаимодействовать со скриптом worklet, вы можете изменить background-size и background-position для изменения размера и положения изображения фона.
PaintSize
В примере выше мы создали прямоугольник размером 20x200 единиц, нарисованный на расстоянии 15 единиц от верхней части элемента, что одинаково независимо от размера элемента. Если текст небольшой, желтый прямоугольник выглядит как большая подчёркивание. Если текст большой, прямоугольник может выглядеть как полоса над первыми тремя буквами. Лучше, если изображение фона относилось к размеру элемента — мы можем использовать свойство paintSize элемента для обеспечения пропорциональности изображения фона размеру области элемента.
На изображении выше фон пропорционален размеру элемента. В третьем примере установлено width: 50%; установленное для элемента уровня блока, делающее элемент уже, а следовательно, и изображение фона уже.
Worklet рисования
Код для этого выглядит следующим образом:
registerPaint(
"headerHighlight",
class {
static get contextOptions() {
return { alpha: true };
}
/*
ctx is the 2D drawing context
size is the paintSize, the dimensions (height and width) of the box being painted
*/
paint(ctx, size) {
ctx.fillStyle = "hsl(55 90% 60% / 100%)";
ctx.fillRect(0, size.height / 3, size.width * 0.4, size.height * 0.6);
}
},
);
В этом примере кода есть два отличия от первого примера:
- Мы включили второй аргумент, который представляет размер рисования.
- Мы изменили размеры и расположение нашего прямоугольника для относительного размера области элемента, а не абсолютных значений.
Мы можем передать второй параметр в функцию paint() для доступа к ширине и высоте элемента через свойства .width и .height.
Теперь наш заголовок имеет подсветку, которая изменяется в зависимости от его размера.
Использование worklet рисования
HTML
<h1 class="fancy">Largest Header</h1> <h6 class="fancy">Smallest Header</h6> <h3 class="fancy half">50% width header</h3>
CSS
Хотя вы не можете взаимодействовать со скриптом worklet, вы можете изменить font-size и width элемента для изменения размера изображения фона.
.fancy {
background-image: paint(headerHighlight);
}
.half {
width: 50%;
}
JavaScript
CSS.paintWorklet.addModule("header-highlight.js");
Результат
В браузерах, поддерживающих API рисования CSS, элементы в примере ниже должны получить желтый фон, пропорциональный размеру шрифта.
Пользовательские свойства
В дополнение к доступу к размеру элемента, worklet также может получить доступ к пользовательским свойствам CSS и обычным свойствам CSS.
registerPaint(
"cssPaintFunctionName",
class {
static get inputProperties() {
return ["PropertyName1", "--customPropertyName2"];
}
static get inputArguments() {
return ["<color>"];
}
static get contextOptions() {
return { alpha: true };
}
paint(drawingContext, elementSize, styleMap) {
// Paint code goes here.
}
},
);
Три параметра функции paint() включают контекст рисования, размер рисования и свойства. Чтобы получить доступ к свойствам, мы включаем статический метод inputProperties(), который предоставляет прямой доступ к свойствам CSS, включая обычные свойства и пользовательские свойства, и возвращает массив array имен свойств. Мы рассмотрим inputArguments в последнем разделе.
Давайте создадим список элементов с изображением фона, которое вращается между тремя различными цветами и тремя ширинами.
Для этого мы определим два пользовательских свойства CSS, --boxColor и --widthSubtractor.
Worklet рисования
В нашем worklet мы можем ссылаться на эти пользовательские свойства.
registerPaint(
"boxbg",
class {
static get contextOptions() {
return { alpha: true };
}
/*
use this function to retrieve any custom properties (or regular properties, such as 'height')
defined for the element, return them in the specified array
*/
static get inputProperties() {
return ["--boxColor", "--widthSubtractor"];
}
paint(ctx, size, props) {
/*
ctx -> drawing context
size -> paintSize: width and height
props -> properties: get() method
*/
ctx.fillStyle = props.get("--boxColor");
ctx.fillRect(
0,
size.height / 3,
size.width * 0.4 - props.get("--widthSubtractor"),
size.height * 0.6,
);
}
},
);
Мы использовали метод inputProperties() в классе registerPaint() для получения значений двух пользовательских свойств, заданных для элемента, которому применён класс boxbg, а затем использовали их в нашей функции paint(). Метод inputProperties() может вернуть все свойства, влияющие на элемент, а не только пользовательские свойства.
Использование worklet рисования
HTML
<ul> <li>item 1</li> <li>item 2</li> <li>item 3</li> <li>item 4</li> <li>item 5</li> <li>item 6</li> <li>item 7</li> <li>item 8</li> <li>item 9</li> <li>item 10</li> <li>item 11</li> <li>item 12</li> <li>item 13</li> <li>item 14</li> <li>item 15</li> <li>item 16</li> <li>item 17</li> <li>item</li> </ul>
CSS
В нашем CSS мы определяем пользовательские свойства --boxColor и --widthSubtractor.
li {
background-image: paint(boxbg);
--boxColor: hsl(55 90% 60% / 100%);
}
li:nth-of-type(3n) {
--boxColor: hsl(155 90% 60% / 100%);
--widthSubtractor: 20;
}
li:nth-of-type(3n + 1) {
--boxColor: hsl(255 90% 60% / 100%);
--widthSubtractor: 40;
}
JavaScript
В нашем <script> мы регистрируем worklet:
CSS.paintWorklet.addModule("boxbg.js");
Результат
Хотя вы не можете взаимодействовать со скриптом worklet, вы можете изменить значения пользовательских свойств в DevTools, чтобы изменить цвета и ширину изображения фона.
Добавление сложности
Приведённые выше примеры, возможно, не кажутся очень интересными, поскольку их можно пересоздать несколькими способами с помощью имеющихся свойств CSS, например, разместив некоторый декоративный генерируемый контент с помощью ::before, или включив background: linear-gradient(yellow, yellow) 0 15px / 200px 20px no-repeat;. То, что делает API CSS Painting таким интересным и мощным, — это возможность создавать сложные изображения, передавая переменные, которые автоматически масштабируются.
Давайте рассмотрим более сложный пример рисования.
Paint-воркелет
registerPaint(
"headerHighlight",
class {
static get inputProperties() {
return ["--highColor"];
}
static get contextOptions() {
return { alpha: true };
}
paint(ctx, size, props) {
/* set where to start the highlight & dimensions */
const x = 0;
const y = size.height * 0.3;
const blockWidth = size.width * 0.33;
const highlightHeight = size.height * 0.85;
const color = props.get("--highColor");
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(blockWidth, y);
ctx.lineTo(blockWidth + highlightHeight, highlightHeight);
ctx.lineTo(x, highlightHeight);
ctx.lineTo(x, y);
ctx.closePath();
ctx.fill();
/* create the dashes */
for (let start = 0; start < 8; start += 2) {
ctx.beginPath();
ctx.moveTo(blockWidth + start * 10 + 10, y);
ctx.lineTo(blockWidth + start * 10 + 20, y);
ctx.lineTo(
blockWidth + start * 10 + 20 + highlightHeight,
highlightHeight,
);
ctx.lineTo(
blockWidth + start * 10 + 10 + highlightHeight,
highlightHeight,
);
ctx.lineTo(blockWidth + start * 10 + 10, y);
ctx.closePath();
ctx.fill();
}
} // paint
},
);
Использование paint-воркелета
Затем мы можем создать немного HTML, который будет принимать это изображение в качестве фонов:
<h1 class="fancy">Largest Header</h1> <h3 class="fancy">Medium size header</h3> <h6 class="fancy">Smallest Header</h6>
Каждому заголовку мы задаём разное значение для --highColor пользовательского свойства
.fancy {
background-image: paint(headerHighlight);
}
h1 {
--highColor: hsl(155 90% 60% / 70%);
}
h3 {
--highColor: hsl(255 90% 60% / 50%);
}
h6 {
--highColor: hsl(355 90% 60% / 30%);
}
И регистрируем наш воркелет
CSS.paintWorklet.addModule("header-highlight.js");
Результат выглядит так:
Хотя вы не можете редактировать сам воркелет, вы можете экспериментировать с CSS и HTML. Возможно, стоит попробовать float и clear для заголовков?
Вы могли бы попробовать создать фоновые изображения без API CSS Paint. Это возможно, но вам пришлось бы объявить другой, довольно сложный линейный градиент для каждого цвета, который вы хотите создать. С помощью API CSS Paint один воркелет может быть повторно использован с разными цветами в данном случае.
Передача параметров
Примечание: Для работы следующего примера необходимо включить экспериментальные функции платформы в Chrome или Edge, перейдя по ссылке about://flags.
С помощью API CSS Paint мы не только имеем доступ к пользовательским свойствам и обычным свойствам, но и можем передавать пользовательские аргументы в функцию paint().
Мы можем добавить эти дополнительные аргументы при вызове функции в CSS. Предположим, мы хотим иногда обводить фон, а не заполнять его — передадим дополнительный аргумент для этого случая.
li {
background-image: paint(hollowHighlights, stroke);
}
Теперь мы можем использовать метод inputArguments() в классе registerPaint() для доступа к пользовательскому аргументу, который мы добавили к нашей функции paint().
static get inputArguments() { return ['*']; }
У нас теперь есть доступ к этому аргументу.
paint(ctx, size, props, args) {
// use our custom arguments
const hasStroke = args[0].toString();
// if stroke arg is 'stroke', don't fill
if (hasStroke === 'stroke') {
ctx.fillStyle = 'transparent';
ctx.strokeStyle = color;
}
// …
}
Мы также можем указать, какой тип аргумента мы хотим.
Допустим, мы добавим второй аргумент с шириной обводки в пикселях:
li {
background-image: paint(hollowHighlights, stroke, 10px);
}
При получении нашего списка значений аргументов, мы можем запросить конкретную единицу <length>.
static get inputArguments() { return ['*', '<length>']; }
В этом случае мы специально запросили атрибут <length>. Первый элемент в возвращаемом массиве будет CSSUnparsedValue. Второй — CSSStyleValue.
Если пользовательский аргумент является значением CSS, например, единицей, мы можем вызвать класс CSSStyleValue Typed OM (и подклассы) с помощью ключевого слова типа значения при получении его в функции registerPaint().
Теперь мы можем получить доступ к свойствам типа и значения, что означает, что мы можем получить количество пикселей и числовой тип сразу. (Признаем, ctx.lineWidth принимает число с плавающей точкой, а не значение с единицами длины, но ради примера…)
paint(ctx, size, props, args) {
const strokeWidth = args[1];
if (strokeWidth.unit === 'px') {
ctx.lineWidth = strokeWidth.value;
} else {
ctx.lineWidth = 1.0;
}
// …
}
Стоит отметить разницу между использованием пользовательских свойств для управления разными частями этого воркелета и аргументами, указанными здесь. Пользовательские свойства (и, в действительности, любые свойства в стиле карты) являются глобальными — они могут использоваться где-либо ещё в нашем CSS (и JS).
Например, у вас может быть --mainColor, что будет полезно для установки цвета внутри функции paint(), но также может использоваться для установки цветов в других частях вашего CSS. Если вы захотите изменить его только для рисования, это может оказаться сложным. Вот где пригождается функция пользовательских аргументов. Можно ещё сказать, что аргументы предназначены для управления тем, что вы рисуете, а свойства — для управления стилем.
Теперь мы можем действительно начать видеть преимущества этого API, если мы можем управлять множеством параметров рисования из нашего CSS с помощью пользовательских свойств и дополнительных аргументов функции paint(), тогда мы действительно сможем начать создавать многократно используемые и высокоуправляемые функции стилей.
Paint-воркелет
registerPaint(
"hollowHighlights",
class {
static get inputProperties() {
return ["--boxColor"];
}
// Input arguments that can be passed to the `paint` function
static get inputArguments() {
return ["*", "<length>"];
}
static get contextOptions() {
return { alpha: true };
}
paint(ctx, size, props, args) {
// ctx -> drawing context
// size -> size of the box being painted
// props -> list of custom properties available to the element
// args -> list of arguments set when calling the paint() function in the CSS
// where to start the highlight & dimensions
const x = 0;
const y = size.height * 0.3;
const blockWidth = size.width * 0.33;
const blockHeight = size.height * 0.85;
// the values passed in the paint() function in the CSS
const color = props.get("--boxColor");
const strokeType = args[0].toString();
const strokeWidth = parseInt(args[1]);
// set the stroke width
ctx.lineWidth = strokeWidth ?? 1.0;
// set the fill type
if (strokeType === "stroke") {
ctx.fillStyle = "transparent";
ctx.strokeStyle = color;
} else if (strokeType === "filled") {
ctx.fillStyle = color;
ctx.strokeStyle = color;
} else {
ctx.fillStyle = "none";
ctx.strokeStyle = "none";
}
// block
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(blockWidth, y);
ctx.lineTo(blockWidth + blockHeight, blockHeight);
ctx.lineTo(x, blockHeight);
ctx.lineTo(x, y);
ctx.closePath();
ctx.fill();
ctx.stroke();
// dashes
for (let i = 0; i < 4; i++) {
let start = i * 2;
ctx.beginPath();
ctx.moveTo(blockWidth + start * 10 + 10, y);
ctx.lineTo(blockWidth + start * 10 + 20, y);
ctx.lineTo(blockWidth + start * 10 + 20 + blockHeight, blockHeight);
ctx.lineTo(blockWidth + start * 10 + 10 + blockHeight, blockHeight);
ctx.lineTo(blockWidth + start * 10 + 10, y);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
} // paint
},
);
Использование paint-воркелета
Мы можем установить разные цвета, ширину обводки и выбрать, заполнять ли изображение фоном или нет:
li {
--boxColor: hsl(155 90% 60% / 50%);
background-image: paint(hollowHighlights, stroke, 5px);
}
li:nth-of-type(3n) {
--boxColor: hsl(255 90% 60% / 50%);
background-image: paint(hollowHighlights, filled, 3px);
}
li:nth-of-type(3n + 1) {
--boxColor: hsl(355 90% 60% / 50%);
background-image: paint(hollowHighlights, stroke, 1px);
}
В нашем <script> мы регистрируем воркелет:
CSS.paintWorklet.addModule("hollow.js");
См. также
© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/CSS_Painting_API/Guide