The CSS Paint API is a powerful feature in modern CSS that allows you to create custom graphics and visual effects directly in CSS. It provides a way to dynamically generate image data for use in properties like background-image, border-image, and mask-image.
Before diving into advanced techniques, let’s understand the basic concepts of the CSS Paint API.
Custom properties, also known as CSS variables, are used to define reusable values in CSS. They are defined using the --
prefix and can be referenced throughout your CSS.
/* Define custom properties */
:root {
--primary-color: #007bff;
}
/* Use custom properties */
.element {
color: var(--primary-color);
}
Paint worklets are JavaScript modules that define custom paint functions. These functions generate image data to be used in CSS properties.
To use the CSS Paint API, you need to define a custom paint function using a paint worklet. Here’s the basic syntax:
registerPaint('paint-name', class {
static get inputProperties() { return ['--property']; }
paint(ctx, size, props) {
// Paint logic here
}
});
In this syntax:
paint-name
is the name of the custom paint function.inputProperties
specifies the custom properties that the paint function depends on.ctx
is the canvas rendering context.size
is the size of the painted area.props
contains the computed values of the custom properties.Now, let’s create some custom paint functions using the CSS Paint API.
registerPaint('gradient-border', class {
static get inputProperties() { return ['--border-color', '--border-width']; }
paint(ctx, size, props) {
const color = props.get('--border-color').toString();
const width = parseInt(props.get('--border-width').toString());
ctx.strokeStyle = color;
ctx.lineWidth = width;
ctx.strokeRect(0, 0, size.width, size.height);
}
});
.element {
--border-color: linear-gradient(to right, red, blue);
--border-width: 5px;
border-image: paint(gradients);
}
In this example:
gradient-border
.--border-color
and --border-width
.
registerPaint('pattern-background', class {
paint(ctx, size, props) {
const pattern = ctx.createPattern(image, 'repeat');
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, size.width, size.height);
}
});
.element {
background-image: paint(pattern-background);
}
In this example:
pattern-background
.
registerPaint('dynamic-shapes', class {
static get inputProperties() { return ['--shape']; }
paint(ctx, size, props) {
const shape = props.get('--shape').toString();
if (shape === 'circle') {
ctx.beginPath();
ctx.arc(size.width / 2, size.height / 2, Math.min(size.width, size.height) / 2, 0, 2 * Math.PI);
ctx.fillStyle = 'red';
ctx.fill();
} else if (shape === 'square') {
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, size.width, size.height);
}
}
});
.element {
--shape: circle;
background-image: paint(dynamic-shapes);
}
In this example:
dynamic-shapes
.--shape
to determine the shape to be drawn.
registerPaint('animated-background', class {
static get inputProperties() { return ['--animation-time', '--animation-type']; }
paint(ctx, size, props) {
const animationTime = parseFloat(props.get('--animation-time').toString());
const animationType = props.get('--animation-type').toString();
const gradient = ctx.createLinearGradient(0, 0, size.width, size.height);
gradient.addColorStop(0, 'red');
gradient.addColorStop(0.5, 'green');
gradient.addColorStop(1, 'blue');
if (animationType === 'horizontal') {
const offset = (performance.now() / animationTime) % size.width;
gradient.x0 = offset;
gradient.x1 = offset + size.width;
} else if (animationType === 'vertical') {
const offset = (performance.now() / animationTime) % size.height;
gradient.y0 = offset;
gradient.y1 = offset + size.height;
}
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, size.width, size.height);
}
});
.element {
--animation-time: 2000ms; /* 2 seconds */
--animation-type: horizontal;
background-image: paint(animated-background);
}
In this example:
animated-background
.--animation-time
(duration of animation) and --animation-type
(direction of animation).
registerPaint('image-filters', class {
paint(ctx, size, props) {
const imageUrl = props.get('--image-url').toString();
const filter = props.get('--filter').toString();
const image = new Image();
image.src = imageUrl;
image.onload = () => {
ctx.filter = filter;
ctx.drawImage(image, 0, 0, size.width, size.height);
};
}
});
.element {
--image-url: url('example.jpg');
--filter: grayscale(100%);
background-image: paint(image-filters);
}
In this example:
image-filters
.--image-url
(URL of the image) and --filter
(CSS filter to apply).The CSS Paint API is a powerful tool for creating custom graphics and visual effects directly in CSS. By understanding its basic concepts and exploring advanced techniques, you can enhance the visual appeal of your web projects and achieve unique design effects without relying on external image resources. Experiment with different paint functions and unleash your creativity to create stunning visual experiences on the web. Happy Coding! ❤️