Guide
Addons
Extra functions for css-doodle, released on their own in @css-doodle/addons.
Name one with @use and the doodle loads it before it draws.
Using an addon
@use: marble;
@grid: 1 / 60vmin;
background: @marble(
colors: #f4ecd8, #1d3557, #e63946, #f1c453, #2a9d8f
);
Several names go in one list, as in @use: pigment, wfc,
next to any var(--rule) items of @use.
Click an example on this page to draw it again with a new seed.
| Addon | For |
|---|---|
gradient-2d | mesh and freeform 2D gradients |
marble | paper marbling and ink in water |
pigment | colors that mix like paint |
spring | a spring-like easing |
trace | the outline of an image as an SVG path |
wfc | wave function collapse |
gradient-2d
@mesh-gradient() and @freeform-gradient() draw
the 2D gradients proposed for CSS with WebGL.
- @mesh-gradient([in <space>,] <row>, <row>, …)
- @freeform-gradient([in <space>,] <color> [<spread>] at <position>, …)
A mesh is rows of colors, top row first. <color> at <x> <y> moves one point:
@use: gradient-2d;
@grid: 1 / 60vmin;
background: @mesh-gradient(
#f43 gold #0bf,
#a0f white at 40% 60% #0c6,
navy #f0a teal
);
In a freeform gradient each color spreads out from its position.
A position or a spread also takes math in parentheses, as in @pattern,
and t, the time in seconds, moves it.
A moving gradient draws faster in @content, a live canvas, than in background.
Each call of $drift() below gets its own @r() speeds,
so every point wanders its own way, and a click sends them on new paths:
@use: gradient-2d;
@grid: 1 / 60vmin;
--drift(): (50% + 30% * sin(t * @r(.2, .6) + @r(9))
+ 15% * sin(t * @r(.6, 1.2) + @r(9)));
@content: @freeform-gradient(
#f56 at $drift() $drift(),
gold at $drift() $drift(),
#0c9 at $drift() $drift(),
#2b2d86 at $drift() $drift()
); marble
@marble() is paper marbling, ink drops combed into feathers.
@marble.ink() is clouds of ink in water.
Where the inks meet they mix like paint.
- @marble(colors: <color>, <color>, …; drops: <n>; size: <percentage>; comb: <spacing> [<angle>], …; flow: <n>)
- @marble.ink(colors: <color>, <color>, …; scale: <n>; warp: <n>; flow: <n>)
colors is the paper, then the inks.
drops and size set how many drops fall and how big they are,
and each comb is pulled over the one before it:
@use: marble;
@grid: 1 / 60vmin;
background: @marble(
colors: white, #002185, #fcd200;
drops: 60;
size: 6%;
comb: 5% 90deg, 14% 0deg;
);
In @marble.ink() the first color is the water.
scale sets how many clouds fit, and warp how much they curl.
flow makes them drift:
@use: marble;
@grid: 1 / 60vmin;
@content: @marble.ink(
colors: #111, #ffd166, #ef476f, #06d6a0;
scale: 1.5;
warp: 3;
flow: 1;
);
In @marble(), flow ripples the feathers.
Any number option also takes math with t, as in gradient-2d,
so drops: min(t * 5, 60) lets the drops fall in one by one.
pigment
@pigment() mixes colors the way paint does, so blue and yellow make green instead of gray.
Percentages work as in color-mix().
- @pigment(<color> [<percentage>], <color> [<percentage>], …)
Blue gets more to the right and yellow more to the bottom, and red has what is left.
At the bottom right, where color-mix() would make gray, they make green:
@use: pigment;
@grid: 7 / 60vmin;
@size: 86%;
border-radius: 50%;
background: @pigment(
#002185 $((@x - 1) / 6 * 100)%,
#fcd200 $((@y - 1) / 6 * 100)%,
#d7192e
); spring
@spring() is an easing that overshoots and settles, for animation or transition.
- @spring([<bounce>])
<bounce> goes from 0 to .95, and is .3 by default.
Each row below bounces more than the one above:
@use: spring;
@grid: 1x5 / 60vmin;
background: radial-gradient(
closest-side, #1d3557 85%, #0000 90%
) 0 0 / 20% 100% no-repeat;
animation: slide 2s @spring($((@y - 1) * .2)) infinite;
@keyframes slide {
from { translate: 0 }
70%, to { translate: 50% }
} trace
@trace() turns an image into an outline, for path: anywhere a shape goes.
- @trace(<image>, [<level>])
The ink is the opaque part of the image, or the dark part when it has no transparency.
<level> is where the ink starts, 50% by default:
@use: trace;
@grid: 1 / 60vmin;
background: #1d3557;
clip-path: @shape(path: @trace(/assets/wukong.jpg, 70%)); The path is in the image's own pixels, so in an @svg as big as the image, outlines at two levels line up as a print in two inks:
@use: trace;
@grid: 1 / 60vmin;
background: #fbe9d0 @svg(
viewBox: 0 0 640 640;
path*2 {
d: @trace(/assets/wukong.jpg, @pn(30%, 70%));
fill: @pn(#e63946, #1d3557);
}
); An image from another site needs CORS headers.
wfc
Wave function collapse, after Maxim Gumin's WaveFunctionCollapse.
@wfc() fills the grid so it looks like a small sample,
and @wfc.tiles() so tiles match at their edges.
- @wfc(sample: <sample>; legend: <key> <value>, …; n: <2|3>; symmetry: <1-8>; ground)
- @wfc.tiles(<value> <sides> [<weight>], …)
- @wfc.turn(), @wfc.order()
The sample has one character per pixel, and legend gives characters a value.
ground keeps the bottom row at the bottom; use it with symmetry: 2 so nothing is turned upside down:
@use: wfc;
@grid: 24 / 60vmin;
background: @wfc(
sample:
................
..ooo...........
..o*o......ooo..
..ooo......o*o..
...|.......ooo..
...|l.......|...
..l|........|...
...|.......l|...
...|........|l..
################;
legend: . #fbe9d0, o #e63946, * #f1c453,
| #2d6a4f, l #52b788, # #6b4423;
symmetry: 2;
ground;
);
A tile is a value and the labels of its top, right, bottom and left sides.
Every turn of a tile is tried, and @wfc.turn() gives the one the cell needs,
so here the crossing turns to put one rope or the other on top:
@use: wfc;
@grid: 12 / 60vmin;
:doodle { background: #1d3557 }
--rope: #0000 35%, #f1c453 0 65%, #0000 0;
--line: linear-gradient(90deg, var(--rope));
--corner: radial-gradient(
circle at 100% 0, #0000 24.7%, #f1c453 0 46%, #0000 0
);
--cross: linear-gradient(90deg,
#0000 25%, #1d3557 0 35%, #f1c453 0 65%,
#1d3557 0 75%, #0000 0
), linear-gradient(var(--rope));
background: @wfc.tiles(
var(--line) 1010, var(--corner) 1100, var(--cross) 1111
);
rotate: @wfc.turn(); @wfc.order() is when the cell was solved, from 0 to 1, so as an animation delay the doodle fills in that order.
When a sample doesn't fit the grid it warns contradiction; another size or seed usually works.
Without @use
An addon is also a plain script, added after css-doodle:
<script src="https://unpkg.com/css-doodle/css-doodle.min.js"></script>
<script src="https://unpkg.com/@css-doodle/addons@1/marble.js"></script> import 'css-doodle';
import '@css-doodle/addons/marble'; To serve the files yourself, set where they are before the first doodle draws:
customElements.get('css-doodle').addonBase = '/vendor/css-doodle-addons/'; Writing your own
CSSDoodle.use() adds a function. A value function gets the arguments and the cell, and returns CSS text:
customElements.whenDefined('css-doodle').then(() => {
const CSSDoodle = customElements.get('css-doodle');
CSSDoodle.use('stripes', {
value: ([color = 'black'], { i }) => i % 2 ? color : 'transparent',
});
});
Then @stripes(red) works in any doodle on the page.
The addons README covers the rest.