Functions for css-doodle that live outside its bundle and are released on their own. Name one in the code and the doodle loads it:
@use: gradient-2d;
background: @mesh-gradient(red gold, teal navy);| Addon | Adds | |
|---|---|---|
| gradient-2d | @mesh-gradient(), @freeform-gradient() |
2D gradients, drawn with WebGL |
| marble | @marble(), @marble.ink() |
paper marbling and ink in water |
| pigment | @pigment() |
colors that mix like paint |
| spring | @spring() |
spring-like easing |
| trace | @trace() |
an image's outline as an SVG path |
| wfc | @wfc(), @wfc.tiles(), @wfc.turn(), @wfc.order() |
wave function collapse |
@use: <name> loads the addon from @css-doodle/addons@1 on unpkg, or
jsDelivr if unpkg is down or takes over 5 seconds, once per page. The
doodle draws when it is in.
- Addons go by name, never by URL, so doodle code can only load css-doodle's own.
var(--rule)items in the same list still insert code:@use: gradient-2d, var(--rule).- Write it in the outer doodle; a nested
@doodleuses what the outer one loaded. - An addon already on the page, from a script tag or an import, is not fetched again.
Set a base before the first doodle draws. It is tried before unpkg and
jsDelivr, and each addon is one file in it, like
/vendor/css-doodle-addons/gradient-2d.js:
customElements.get('css-doodle').addonBase = '/vendor/css-doodle-addons/';Add it after css-doodle. This is the way for addons that are not css-doodle's own, and for a page that blocks unpkg:
<script src="https://unpkg.com/css-doodle/css-doodle.min.js"></script>
<script src="https://unpkg.com/@css-doodle/addons/gradient-2d.js"></script>import 'css-doodle';
import '@css-doodle/addons/gradient-2d';Doodles already on the page that call an addon draw again, with the same
seed, when it arrives. They warn unknown function @name() once before.
An addon is a folder in src/ with an index.js and a README.md.
index.js is one self-contained script, with no imports, that registers its
functions once the element is defined:
customElements.whenDefined('css-doodle').then(() => {
let CSSDoodle = customElements.get('css-doodle');
CSSDoodle.use('stripes', {
value: ([color = 'black'], { i }) => i % 2 ? color : 'transparent',
});
});use(name, { value }) makes @name(…) a value. value(args, api) gets the
arguments, already evaluated and split at commas, and returns CSS text.
api has:
i,x,y: the cell's index, column and row, like@i,@x,@yI,X,Y: the cell count, columns and rowsrandom(): the doodle's seeded random, the same stream as@r;random(n)gives [0, n),random(a, b)gives [a, b)seed: the doodle's seedstream(key): a seeded random of the addon's own, called likerandom. It leaves@ralone, and starts the same for the same seed and key every time, in the addon's variants toostate: an object kept for this call across the cells of one draw, for work done once for the whole grid (fill it withrandom, not a cache of your own, so a redraw draws the same numbers)wait(promise): the doodle draws again, with the same seed, once the promise settles; return a stand-in until then (prerender doesn't wait)
use(name, { shader }) makes @name(…) an image drawn with WebGL, like
@pattern. shader(text, api) gets the argument text and returns
@shaders code: a fragment shader, or
fragment {} / vertex {} / texture {} sections. css-doodle supplies
u_resolution, u_time, u_mouse, u_seed and FragColor, sizes the
image, and animates it when it reads u_time.
Its api also has expr(text): the math of @pattern as GLSL for a
float, with t for the time in seconds, 9% as 9 and an angle in
degrees, or null, with a warning, when it doesn't read. Math that reads
t reads u_time, so the image moves.
The api of both kinds has:
color(value, space):[r, g, b, a]for a CSS color in acolor-mix()space (defaultsrgb), ornullsplit(text): cuts at top-level commaswords(text): cuts at top-level spaceswarn(message): reports a warning on the doodle
use('name.kind', …) adds @name.kind(…), of either kind, once name
itself is in. Only an addon's own name takes variants.
- A function with options takes them as declarations, like
@shape():@name(colors: red, blue; scale: 2), a flag as a name alone. One or two plain values go in as they are, like the arguments of a CSS function. - A number option of a shader takes math through
expr(), sotcan move it; a number alone still reads as it did. - Output should depend only on the input,
random,seedandstream, so a seed keeps drawing the same picture. Version the addon file. - A name that is already taken is kept: a built-in name warns, an addon loaded twice is left out quietly.
- An error thrown inside an addon becomes a warning.
npm install
npm test # against ../css-doodle, or set CSSD_SRC to its src/
npm run build # dist/<name>.js and the package.json to publish
npm run test:dist # builds, tests dist/, and draws the shaders in Chromedist/ is the published package, so dist/gradient-2d.js is served as
@css-doodle/addons@1/gradient-2d.js. A new addon is a minor version, and
css-doodle needs no release. Pushing the version tag publishes it: the
release workflow runs the tests against css-doodle's main, then
npm publish ./dist:
npm version minor
git push --follow-tagsNever change the syntax of a published function, only add, and never what
it draws: test/golden/ keeps each addon's output for a fixed input and
seed, and the tests fail when it changes. A breaking change, or a fix that
redraws shared doodles, goes out as the next major, which a new css-doodle
opts into. GOLDEN=update npm test writes the outputs anew, for that and
for a new addon.