Made for algorithmic art
I built Ctrls for my algorithmic art projects. By default, the state is saved in the URL, which lets you navigate history and share links easily. Along with standard components like checkboxes and ranges, it includes two especially useful for generative work: RNG seed and easing. These not only let you adjust parameters but also expose functions (rng and easing) you can call directly.
Play with the live example above or check the older version in my Space Invaders generator.
Features
- Minimal API
- Six components : RNG seed, easing, boolean, radio, range and dual range
- Fully typed, including dynamic typings for controlled properties
- Light and dark themes included, theming via CSS variables
Quick start
Install the library:
npm install @stanko/ctrls
Define controls as an array, then instantiate the Ctrls
class:
import { Ctrls } from "@stanko/ctrls";
import type { TypedControlConfig } from "@stanko/ctrls";
// Import CSS
import "@stanko/ctrls/dist/ctrls.css";
// Define configuration for controls
const config = [
{
type: "boolean",
name: "animate",
defaultValue: true,
isRandomizationDisabled: true,
},
{
type: "seed",
name: "opacitySeed",
},
{
type: "range",
name: "hue",
defaultValue: 220,
min: 0,
max: 360,
step: 1,
},
// in the demo above there are three more components:
// - speed, shape and size
// Casting the config to enable editor's code completion
] as const satisfies readonly TypedControlConfig[];
// Create the instance of Ctrls
export const options = new Ctrls(config, {
showRandomizeButton: true,
});
// Render Ctrls elements on the page
document.querySelector('.my-controls').appendChild(options.element);
// If you need to pass values you can export a type for them for convenience
export type Options = ReturnValue<options.getValues()>;
Add onChange
or onInput
handlers. If you need to get the current values, use .getValues()
.
options.onChange = (updatedValues: Partial<Options>) => {
// Example: re-render your drawing
render(options.getValues()); // Get all values and pass it to the render method
};
options.onInput = (updatedValues: Partial<Options>) => {
// Example: update a CSS variable
if (updatedValues.hue) {
document.body.style.setProperty("--hue", updatedValues.hue);
}
};
API
Constructor accepts two parameters - an array of control config object (see below for details) and an options object.
constructor(controls: Configs, options?: ControlsOptions)
The available options are:
type ControlsOptions = {
showRandomizeButton?: boolean; // default: true
storage?: "hash" | "none"; // default: "hash"
theme?: "system" | "light" | "dark"; // default: "system"
};
Public API:
// The main Ctrls element. You'll have to add it to the page yourself:
// document.querySelector('.my-controls').appendChild(options.element);
.element: HTMLDivElement
// Change handler called whenever any of the values is changed.
// It's only parameter is an object containing the updated values.
// The only way this object can have multiple attributes is when using the hash storage.
.onChange(updatedValues)
// Input handler called while user is inputting values (while dragging a slider for example).
// Same as `onChange`, the only parameter is an object containing the updated values.
.onInput(updatedValues)
// Returns the values as an object, including rng and easing functions.
.getValues()
// Randomizes values for all controls that don't have randomization disabled.
.randomize()
Controls
All controls share the following properties:
{
// --- Mandatory --- //
type: CtrlType; // "seed" | "easing" | "boolean" | "range" | "dual-range" | "radio"
name: string;
// --- Optional --- //
// If passed, it will be used instead of the name
label?: string;
// Depends on the control type, the default value for the control
defaultValue?: T;
// Disabled the value randomization (via randomize method or button)
isRandomizationDisabled?: boolean; // default: false
}
Some controls will have a few more properties explained below.
Boolean
Checkbox control for boolean parameters.
{
"type": "boolean",
"name": "debug"
}
Seed
Text input that generates a random text seed and creates a seeded random number generator.
{
"type": "seed",
"name": "mainSeed"
}
A seeded RNG will be created and included in the values. For the example above .getValues()
returns:
{
mainSeed: "a-random-seed-value",
mainSeedRng: () => number;
}
Easing
Cubic-bezier easing control that also generates an easing function. Includes five presets (configurable via the presets
property). Pass an empty array to disable presets.
{
// Optional
presets?: Record<string, [number, number, number, number]>; // default: 1
}
Example:
{
"type": "easing",
"name": "distribution",
"defaultValue": [0.8, 0.1, 0.2, 0.9],
"presets": {
"ease_in": [0.42, 0, 1, 1],
"ease_out": [0, 0, 0.58, 1],
"ease_in_out": [0.42, 0, 0.58, 1]
}
}
Please note that the string ease_
will be removed from the preset labels.
An easing function will be created and included in the values. For the example above .getValues()
returns:
{
distribution: [0.8, 0.1, 0.2, 0.9],
distributionEasing: (t: number) => number;
}
Range
Range slider that controls a number parameter. Accepts minimum, maximum and step values. It includes a few additional properties:
{
// Mandatory
min: number;
max: number;
// Optional
step?: number; // default: 1
}
Example:
{
"type": "range",
"name": "width",
"defaultValue": 5,
"min": 0,
"max": 10,
"step": 1
}
Dual range
Range input with two handles that control minimum and maximum values. Includes the same additional properties as the range input. Uses my own dual-range-input library.
{
// Mandatory
min: number;
max: number;
// Optional
step?: number; // default: 1
}
Example:
{
"type": "dual-range",
"name": "size",
"defaultValue": { "min": 3, "max": 7 },
"min": 0,
"max": 10,
"step": 1
}
Radio
Grid of radio buttons. Can be set to have between one and five columns.
{
// Mandatory
items: Record<string, string>; // Map of radio items (key, value)
// Optional
columns?: number; // default: 3
}
Example:
{
"type": "radio",
"name": "main shape",
"items": {
"triangle": "3",
"rectangle": "4",
"hexagon": "6",
"octagon": "8",
"circle": "32"
},
"columns": 2
}
Theming
Ctrls uses CSS variables for theming. There are many you can adjust, but I recommend starting with these four:
.ctrls {
// Theme color's hue, default is blue
--ctrls-h: 245;
// Theme color's chroma
--ctrls-c: 0.25;
// Radius
--ctrls-radius: 4px;
--ctrls-range-thumb-radius: 4px;
}
Try tweaking these values live in this example. For convenience, the range thumb radius is set to half the main radius.
Check the source file to see all of the available variables.
Why?
You might ask why I made another library when dat.GUI and TweakPane already exist. Both target general use cases with extensive options and elaborate APIs, while I wanted something smaller and easier to adapt for my algorithmic drawings.
I first hacked together a crude library with no plans to release it. Over time I polished it, ported it to TypeScript, and realized it could be useful to others. The code is solid, though I would love to add a few more features.
Ctrls is opinionated and tailored to my likings. I'm open to suggestions, but they need to fit algorithmic art use-case and align with my vision. It's not meant to be a general-purpose library - others already cover that space well.
Finally, I should note that I really like Tweakpane, and Ctrls’ homepage is heavily inspired by it.
Cheers!
Thank you for stopping by! If you end up using Ctrls, please let me know, I would love to see it.