Getting Started
Install @sarmal/core and animate your first spinner.
Installation
npm install @sarmal/core
CDN Quick Start
No build step? Drop in the auto-init script and add data-sarmal to any canvas:
<script src="https://unpkg.com/@sarmal/core/dist/auto-init.js"></script>
<canvas data-sarmal="artemis2" width="200" height="200"></canvas>
The script scans for canvas[data-sarmal] on page load and starts a spinner for each one. Use any built-in curve name as the attribute value.
Your first spinner
createSarmal is the fastest way to get a spinner on screen. You point it to a canvas element and pick a curve. The animation starts automatically:
<canvas id="spinner" width="200" height="200"></canvas>
import { createSarmal, curves } from "@sarmal/core";
const canvas = document.getElementById("spinner") as HTMLCanvasElement;
const sarmal = createSarmal(canvas, curves.artemis2);
The instance animates immediately by default. Call sarmal.pause() to freeze the animation in place and sarmal.play() to resume. Call sarmal.destroy() when you’re done (unmount, navigation, etc.).
Choosing a curve
While it is possible to create your very own custom curve, you might wish to skip the initial hassle. The curves export is an object with all the built-in curves, so you can pick from predefined curve definitions to get started right away:
import { curves } from "@sarmal/core";
curves.rose3; // 3-petal rose
curves.astroid; // 4-pointed star
// ...
Import paths
There are three ways to import curves, each with different bundle-size characteristics:
1. Convenience: @sarmal/core
import { createSarmal, curves } from "@sarmal/core";
const sarmal = createSarmal(canvas, curves.rose3);
All curves are available through the curves object. Modern bundlers will tree-shake unused individual curves, but the main entry point (index.js) is still resolved into the module graph first. This means that the engine and renderer modules are always processed by the bundler even if they end up unused.
2. Barrel subpath: @sarmal/core/curves
import { createSarmal } from "@sarmal/core";
import { rose3 } from "@sarmal/core/curves";
const sarmal = createSarmal(canvas, rose3);
Directly imports the curves file. You end up only pulling in the curve files you reference, so individual named imports are still tree-shakable.
This is the best balance of convenience and bundle size: you get all named exports without the engine/renderer module graph.
3. Deep import: @sarmal/core/curves/<name>
import { createSarmal } from "@sarmal/core";
import { rose3 } from "@sarmal/core/curves/rose3";
const sarmal = createSarmal(canvas, rose3);
Imports a single curve definition file, resulting in maximum tree-shaking. You can use this to guarantee the smallest possible bundle and already know exactly which curve you need.
If you need both engine/renderer and curves, option 2 or 3 won’t save much over option 1, because you’re already pulling the main modules. The subpath advantage matters most when you only need curve definitions, for whatever reason I won’t judge.
Custom curves
You’re not limited to the built-in curves! You can pass any object that matches CurveDef:
import type { CurveDef } from "@sarmal/core";
import { createSarmal } from "@sarmal/core";
const myCurve: CurveDef = {
name: "circle",
fn: (t, time, params) => ({
x: Math.cos(t),
y: Math.sin(t),
}),
// period defaults to 2π, speed defaults to 1
};
const sarmal = createSarmal(canvas, myCurve);
Only name and fn are required.
The fn receives three arguments:
t(position along the curve, 0 -> period),time(elapsed seconds),params(reserved for now; always{}).
For behavior that varies independently of curve position (e.g. oscillating a shape parameter) use time:
const wobble: CurveDef = {
name: "wobble",
fn: (t, time, _params) => {
const r = 1 + 0.3 * Math.sin(time * 2); // radius pulses with real time
return { x: r * Math.cos(t), y: r * Math.sin(t) };
},
};
See the Curve Definition Schema for the full list of options. You can also prototype your curve ideas in the Playground page.
Styling options
Pass an optional custom object as the third argument to createSarmal:
const sarmal = createSarmal(canvas, curves.rose3, {
trailColor: "#a78bfa", // trail color (default: '#ffffff')
headColor: "#ffffff", // dot at the tip (default: '#ffffff')
skeletonColor: "#ffffff", // ghost path of the full curve (default: '#ffffff')
trailLength: 120 // number of trail points (default: 120)
});
Gradient trails
Set trailStyle to render a color-cycling gradient along the trail:
import { createSarmal, curves, palettes } from "@sarmal/core";
const sarmal = createSarmal(canvas, curves.astroid, {
trailStyle: "gradient-animated",
trailColor: palettes.bard // or a custom array: ['#ff6b6b', '#ffd93d', '#6bcb77']
});
SVG renderer
If you need the spinner to scale freely (no fixed pixel dimensions), use the SVG renderer instead:
<div id="spinner" style="width: 200px; height: 200px;"></div>
import { createSarmalSVG, curves } from "@sarmal/core";
const container = document.getElementById("spinner")!;
const sarmal = createSarmalSVG(container, curves.lissajous32);
createSarmalSVG appends an <svg> into the container and animates it. It accepts the same styling options as createSarmal.
trailStyle and palette properties are not yet supported, because it hasn’t been implemented yet.
Morphing between curves
Call morphTo to smoothly transition from one curve to another:
const sarmal = createSarmal(canvas, curves.astroid);
// at any point you want, transition to another curve over 500ms
await sarmal.morphTo(curves.deltoid, { duration: 500 });
The trail and skeleton crossfade during the transition. morphTo returns a Promise that resolves when the morph is complete, so you can chain them.
Frameworks and SSR
The engine is pure math and works anywhere, but the renderers need the DOM. If you’re using Next.js, Remix, or another SSR framework, you may need to guard the import and initialization:
import { createSarmal, curves } from "@sarmal/core";
if (typeof window !== "undefined") {
const canvas = document.getElementById("spinner") as HTMLCanvasElement;
const sarmal = createSarmal(canvas, curves.artemis2);
}
React
If you’re using React, install @sarmal/react for a drop-in <Sarmal> component and useSarmal hook:
npm install @sarmal/core @sarmal/react
"use client";
import { Sarmal } from "@sarmal/react";
import { curves } from "@sarmal/core";
export default function App() {
return <Sarmal curve={curves.artemis2} />;
}
See the Framework Guides for the full React API and SSR notes.