Canvas vs SVG
Choose the right renderer for your use case.
Both renderers require a DOM. You may need to guard initialization in SSR frameworks. See Frameworks and SSR.
Same curve with the same options in two different renderers. You just pick based on what your context needs.
Canvas Renderer
The Canvas renderer is the default. It uses the HTML5 Canvas 2D API for rendering.
How it works:
- Renders to a
<canvas>element you provide - Uses an OffscreenCanvas to cache the skeleton path for performance
- Handles device pixel ratio (DPR) automatically for crisp rendering on high-DPI displays
- Supports all trail styles including animated gradients
Best for:
- Fixed-size loading indicators where dimensions are known upfront
- Performance-critical scenarios (cached skeleton, efficient trail rendering)
- Exporting frames as images
import { createSarmal, curves, palettes } from "@sarmal/core";
const canvas = document.getElementById("spinner") as HTMLCanvasElement;
const sarmal = createSarmal(canvas, curves.artemis2, {
trailStyle: "gradient-animated",
trailColor: palettes.bard,
});
In current implementation, canvas dimensions are captured at initialization. If the canvas is resized after creation, call destroy() and re-create the instance. This may be automatically handled in a future update.
SVG Renderer
The SVG renderer generates DOM elements and updates them via requestAnimationFrame. It is ideal when you need vector graphics that are independent of resolution.
How it works:
- Appends an
<svg>element into a container you provide - Updates path data and attributes each frame
- Automatically scales to fit the container. Unlike canvas, does not require DPR handling
- Includes built-in accessibility features (
role="img",<title>) - Pre-allocates one
<path>DOM element per trail segment at construction time. This is because SVG is a retained-mode API — the browser owns a live scene graph, so creating and destroying elements mid-animation is expensive. Canvas is immediate-mode: every frame is a fresh paint with no persistent objects, so it has no equivalent overhead.
Because path elements are pre-allocated at construction, SVG renderer performance scales with trailLength. Very high values (500+) may be noticeable in the DOM. Canvas is unaffected by trail length.
Best for:
- Resolution-independent graphics that need to scale smoothly
- Containers with fluid or responsive dimensions
- Situations where accessibility is a priority
import { createSarmalSVG, curves } from "@sarmal/core";
const container = document.getElementById("spinner-container")!;
const sarmal = createSarmalSVG(container, curves.lissajous32, {
ariaLabel: "Loading data...",
});
When to Use Which
| Feature | Canvas | SVG |
|---|---|---|
| Animated gradients | Yes | Yes |
| Custom palettes | Yes | Yes |
| Responsive scaling | Manual | Automatic |
| Accessibility | Manual | Built-in |
| Performance | Excellent (cached skeleton, trail length has no DOM cost) | Good (pre-allocates one DOM node per trail segment) |
| SSR-friendly | No | No |
| Export to image | Native | Requires conversion |
Choose Canvas when:
- You need to export frames as images
- You have fixed dimensions and want maximum performance
- You’re building a game or canvas-heavy application
Choose SVG when:
- The spinner lives in a responsive container
- You need it to scale crisply at any size
- Accessibility is a priority
- You want to style or inspect the output with CSS or DevTools
Same API, different first argument
Both renderers share the same instance methods (play, pause, morphTo, seek, etc.). The only differences are the creation function and the first argument type:
// Canvas: pass a canvas element
const canvasSarmal = createSarmal(canvasElement, curve, options);
// SVG: pass a container element
const svgSarmal = createSarmalSVG(containerElement, curve, options);
See the API Reference for the complete list of options and methods available to both renderers.