Package Exports
- @stanko/ctrls
- @stanko/ctrls/dist/ctrls/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@stanko/ctrls) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Ctrls
Made for algorithmic art
I made Ctrls for my algorithmic art projects. By default, the state is saved in the URL, which lets you navigate history and easily share links. Besides standard components like checkboxes and ranges, it also includes two that are especially useful for generative projects: RNG seed and easing. These not only let you adjust parameters but also provide functions (rng and easing) that you can call directly.
You can play with the live example above or check the older version on my Space Invaders generator.
Features
- Minimal API
- Six components (RNG seed, easing, boolean, radio, range and dual range)
- Fully typed, including the dynamic typings for properties it controls
- Light and dark themes included
- Easy to theme through CSS variables
Quick start
Install the library:
npm install @stanko/ctrlsDefine your controls as an array and instantiate a 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,
});
export type Options = ReturnValue<options.getValues()>;Add your handlers. Ctrls provides onChange and onInput methods.
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
To instantiate:
type ControlsOptions = {
showRandomizeButton?: boolean;
storage?: "hash" | null;
theme?: "system" | "light" | "dark";
};Public API:
elementonChangeonInputgetValuesrandomize
But technically all of the methods are public. I've done this on purpose to let you experiment.
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, if it is not passed it will be randomly generated
defaultValue?: T;
// Should the value be randomized when randomize button is clicked
isRandomizationDisabled?: boolean; // default: false
}Some controls will have a few more properties explained below.
Boolean
A checkbox control for boolean parameter.
{
"type": "boolean",
"name": "debug"
}Seed
A text input which generates a random text seed and creates a random number generator using the seed.
{
"type": "seed",
"name": "mainSeed"
}A seeded RNG will be created and it will be included in the values. For the example above the return value of .getValues() would look like this:
{
mainSeed: "a-random-seed-value",
mainSeedRng: () => number;
}Easing
Cubic-bezier easing control which also creates easing function you can use directly. It includes five presets, which can be changed through the presets property. To disable presets pass an empty array.
{
// 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 it will be included in the values. For the example above the return value of .getValues() would look like this:
{
distribution: [0.8, 0.1, 0.2, 0.9],
distributionEasing: (t: number) => number;
}Range
A 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
A range input with two handles that control minimum and maximum values. Includes the same additional properties as the range input.
{
// 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
A 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 relies on CSS variables for theming. There is a lot of variables you can tweak, but I suggest starting with these four:
--ctrls-c: 0.7;
--ctrls-h: 220;
--ctrls-radius: 4px;
--ctrls-range-thumb-radius: 4px;Why?
You might ask why I made another library when dat.GUI and TweakPane already exist. They both solve a general use case, with extensive options and elaborate APIs, while I wanted something smaller and easier to adapt for my algorithmic drawings.
I originally built a crude library with no plans to release it. Over time I polished it, ported it to TypeScript, and realized it might actually be useful to others. The code is solid, though I would love to add a few more features.
Ctrls is a very opinionated library tailored to my likings. I'm open to suggestions, but they need to fit algorithmic art workflows and align with my vision. It's not meant to be a general-purpose library - others already cover that space well.
I also want to mention that I really like TweakPane, and Ctrls' homepage is heavily influenced by it.
Cheers!
Thank you for stopping by! If you end up using Ctrls, please let me know, I would love to see it.
TODO
- Demo - favicon and metadata
- Readme - screenshots
- Prefix input names' with
ctrl_${id}_${name}to avoid collisions - Allow users to pass a custom PRNG lib
- Hash storage - check if there is an instance using hash storage already
- Demo - stop animation when not in viewport
- Add title which collapses the controls
- Storage - local storage
- Use web safe / system fonts by default
- Experiment with chroma for light and dark shades of the main color
- On input event / handler
- Revisit naming
controlsvsoptionsvsvalues - Remove lucide as a dependency, swap with local SVGs
- Add Aleas PRNG instead of seedrandom
- Storage options - none
- Fix get random value float point error in dual range (modulo with floats)
- Easing - fix handles jumping to previous positions after selecting preset (no storage)
- Scope the CSS
- Controls -> Ctrls
- Easing - option to pass custom presets (or an empty array to disable them)
- onChange - maybe add current options (?) - decided not to do