SVG · 2D and 3D · themeable
cseen
Themeable 2D and 3D SVG illustrations and animations for the web.
Describe a scene in a few lines of TypeScript, animate it on a timeline, and put it on a page. It draws real SVG, so it follows your CSS, renders on the server, and plays live in the browser.
npm install cseenSee it move
Laptop Screen, keyboard and trackpad. lean tilts the screen back. Devices
Monitors, laptops, routers, a keyboard.
Typing code on a screen Screens have code(), and typeOn types it letter by letter. 1 const s = new Scene(); 2 s.add(ground(), laptop()); 3 4 // animate it 5 tl.play(fadeIn(laptop)); Animation
Scrub, pause, render one frame.
Interference Two sources, with the lines where the waves cancel. Waves and optics
Sonar, stereo rigs, lenses.
Progress and loading Bars, rings and segments with an animatable value. Done 100% 100% Interfaces
Progress, chat and dialogs on screens.
A flowchart Start, a decision and two ways to the end. Edges trace on one after another. start signed in? sign in home yes no Graphs and flow
Flowcharts, trees and networks that place themselves.
A scene in a few lines
import { Construct, Scene, Timeline, ground, group, par, tween } from 'cseen';
// The letters of the logo, c s e e n, stand on five invisible squares. They trade places to spell s c e n e, then trade back, again and again.
const s = new Scene({ view: { yaw: -16, pitch: 40 }, size: [640, 250], padding: 16 });
s.add(ground());
const stage = s.add(group());
const flat = { u: [1, 0, 0] as V3, v: [0, 1, 0] as V3 };
// five squares of 120 with 30 between, 100 to the world unit; the letters move over them (the squares themselves are not drawn)
const row = stage.add(new Construct({ ...flat, box: [0, 0, 720, 120], size: [7.2, 1.2] }));
const [rx, ry] = row.at(360, 60);
stage.props.pos = [-rx, -ry, 0];
// each letter is outlines on a square of 100 (y runs down), with the strokes 18 thick, like the mark of the logo; the e has a hole
const glyphs: Record<string, string[]> = {
c: ['0 0 100 0 100 18 18 18 18 82 100 82 100 100 0 100'],
s: ['0 0 100 0 100 18 18 18 18 41 100 41 100 100 0 100 0 82 82 82 82 59 0 59'],
e: ['0 0 100 0 100 59 18 59 18 82 100 82 100 100 0 100', '18 18 18 41 82 41 82 18'],
n: ['0 0 100 0 100 100 82 100 82 18 18 18 18 100 0 100'],
};
const points = (l: string) => { const n = l.split(' ').map(Number); return n.filter((_, i) => i % 2 === 0).map((x, i): Pt => [x, n[2 * i + 1]]); };
// two colours, as in the logo: c and n in the accent, s and both e in ink
const INK = 'ink', ACCENT = 'accent';
const word = 'cseen', colours = [ACCENT, INK, INK, INK, ACCENT], after = [1, 0, 2, 4, 3]; // after[i] is the square that letter i swaps to
const PITCH = 1.5, SIDE = 1.2, K = SIDE / 100, THICK = 0.3;
const sample = new Construct({ ...flat, box: [0, 0, 100, 100], size: [SIDE, SIDE] });
const [cx, cy] = sample.at(50, 50);
const letters = [...word].map((name, i) => {
const [wx, wy] = row.at(60 + 150 * i, 60);
const lift = stage.add(group()); // lifts the letter over the others
const home: V3 = [wx - cx, wy - cy, 0], slide = lift.add(group({ at: home })); // carries it to its other square
const goal: V3 = [home[0] + (after[i] - i) * PITCH, home[1], 0];
const shade = `color-mix(in oklab, var(--c-${colours[i]}) 70%, var(--c-bg))`;
// the top of the letter, raised by its thickness, and a wall standing on each edge of its outline
const top = slide.add(new Construct({ ...flat, box: [0, 0, 100, 100], size: [SIDE, SIDE], at: [0, 0, THICK] }));
top.add({ d: glyphs[name].map((l) => 'M' + points(l).map((q) => q.join(' ')).join('L') + 'Z').join(''), paint: colours[i], stroke: 'none', props: { fill: 1 } });
for (const l of glyphs[name]) {
const ring = points(l);
ring.forEach(([x, y], k) => {
const [x2, y2] = ring[(k + 1) % ring.length], dx = (x2 - x) * K, dy = -(y2 - y) * K, len = Math.hypot(dx, dy);
const wall = slide.add(new Construct({ box: [0, 0, len * 100, THICK * 100], size: [len, THICK], at: [x * K, (100 - y) * K, 0], u: [dx / len, dy / len, 0], v: [0, 0, 1] }));
wall.add({ d: `M0 0H${len * 100}V${THICK * 100}H0Z`, paint: shade, stroke: shade, width: 1, props: { fill: 1 } });
});
}
return { lift, slide, home, goal };
});
const soft = (t: number) => (1 - Math.cos(Math.PI * t)) / 2;
const t = (p: Animatable, to: Record<string, number | number[]>, from: Record<string, number | number[]>, at: number, len: number) =>
tween(p, to, { from, delay: at, duration: len, ease: soft });
// all the letters swap in pairs: the c steps over the s, and the n over the second e; `flip` is which way
const swap = (flip: boolean): Anim => par(...letters.flatMap((l, i) => {
if (l.goal[0] === l.home[0]) return [];
const [a, b] = flip ? [l.home, l.goal] : [l.goal, l.home], at = i > 2 ? 0.5 : 0, over = l.goal[0] > l.home[0];
return [t(l.slide, { pos: b }, { pos: a }, at, 1.6), ...(over ? [t(l.lift, { pos: [0, 0, 1.1] }, { pos: [0, 0, 0] }, at, 0.8), t(l.lift, { pos: [0, 0, 0] }, { pos: [0, 0, 1.1] }, at + 0.8, 0.8)] : [])];
}));
const tl = new Timeline();
// c s e e n -> s c e n e -> c s e e n, and round again
tl.wait(1.6);
tl.play(swap(true));
tl.wait(1.6);
tl.play(swap(false));
tl.wait(0.4);What it does
- 01
One space for 2D and 3D
x to the right, y into the scene, z up. A flat diagram is a scene seen from the front.
- 02
Real SVG
It follows your CSS, renders on the server, and plays live in the browser. Nothing to rasterise.
- 03
A scene is a function of time
Playing, pausing, stepping and rendering a single frame all work the same way.
- 04
Pick what you use
The core is small. Every pack has its own import, so a page that needs a monitor does not load the charts.
- 05
Ready-made parts
Monitors, laptops, routers, charts, waves, lenses, graphs and flowcharts are built in. Place them, do not draw them.
- 06
Camera and orbit
Parallel or perspective, and the camera animates like anything else. Let readers drag to turn, zoom and reset.
- 07
Follows your theme
Set up to three CSS variables on a figure or any parent, and every colour is derived from them. Light and dark come with it.
- 08
Show how it is made
Build a drawing from guides, circles and outlines that trace on, fill and turn, then take it apart again. The cube above is built this way.
Pick what you use
- CoreScenes, cameras, shapes, text, animation and arrows.25 in the gallery
- DevicesScreens, computers, peripherals and network gear.19 in the gallery
- UI on screensProgress, notifications, dialogs, badges and chat.3 in the gallery
- ChartsTen chart types drawn on a plane, coloured by the theme.18 in the gallery
- WavesTransverse, spherical, sonar, interference and echo.12 in the gallery
- OpticsPinhole camera, focal length and stereo depth.2 in the gallery
- MathsGraphs, matrices, planes under a matrix, fields and circles.7 in the gallery
- Graphs and flowFlowcharts, trees and networks that lay themselves out.6 in the gallery