All guides

Text and math

Text and equations are real vector glyphs. Handwrite them, stagger them, or morph one equation into another, term by term.

Text#

text(content, options) lays out a block of glyphs. The node's position is the middle of the block.

import { film, text, write } from 'vizmatic/stage'

export default film({ fps: 30 }, (s) => {
    const head = s.add(text('Every glyph is a path.', { font: 'text-serif', size: 170 }).at(0, -80))
    const body = s.add(text('So every letter can move.', { size: 62, color: 'dim' }).at(0, 120))
    s.play(write(head), { duration: 1.6 })
    s.play(write(body), { duration: 1 })
    s.wait(0.8)
})
OptionMeaning
sizeFont size in stage units.
font'sans' (Inter), 'serif' (Instrument Serif), 'text-serif' (Source Serif 4), 'mono' (JetBrains Mono), or 'noto'. Defaults to the font of the film, which is 'sans' unless film({ font }) sets another.
weight400, 600, or 700. 'sans' has all three, and 'text-serif' has 400 and 600. A family that has no file for a weight uses its regular.
italicUse the italic of the family.
colorA look token, CSS color, or gradient.
align'left', 'center', or 'right' inside the block.
anchorThe point that .at(x, y) places: 'left', 'center' (the default), or 'right'.
maxWidthWrap to this width.
leadingLine height as a multiple of size.
trackingExtra letter space as a fraction of size.

.at(x, y) places the middle of a text block. For a label that starts or ends at a known x, such as a row of a table or the name of an axis, set anchor. The y of the point does not depend on the letters, so labels with different anchors on one y share a baseline.

import { text } from 'vizmatic/stage'

const metric = text('latency', { size: 32, anchor: 'right' }).at(-20, 0)   // ends at x = -20
const value = text('5.0 ms', { size: 32, anchor: 'left' }).at(20, 0)       // starts at x = 20

Use 'text-serif' for sentences, labels, and numbers. Its strokes stay visible when a film plays at phone size, and it keeps 1 and l, and O and 0, easy to tell apart. It also sits well next to TeX. Use 'serif' for short titles at a large size. It is narrow and has fine strokes, and it has no Greek letters or math symbols.

To use one font in a whole film, set it once: film({ font: 'text-serif' }, (s) => { ... }). Every text() with no font then has that font, and so has each label that an object draws. The Objects guide lists the texts that stay in mono.

If a font does not have a character, the engine draws that character with the next bundled font. 'serif' tries Source Serif 4 first. Then every family tries Noto Sans and Noto Sans Math. If no bundled font has the character, the engine draws an empty box and vizmatic check reports film.missing_glyph.

Glyphs are in node.chars. node.find('word') returns the glyphs of a fragment, so you can color or animate one word. node.font is the family that the text is set in.

import { film, recolor, text } from 'vizmatic/stage'

export default film({ fps: 30 }, (s) => {
    const line = s.add(text('Look at this word.', { font: 'text-serif', size: 170 }))
    s.wait(0.5)
    s.play(...line.find('this').map((glyph) => recolor(glyph, 'yellow')))
    s.wait(1)
})

Equations#

tex(source, options) typesets TeX with a bundled engine, so you do not install LaTeX. The output is vector glyphs in the Computer Modern style.

import { film, tex, write } from 'vizmatic/stage'

export default film({ fps: 30 }, (s) => {
    const eq = s.add(tex('x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}', { size: 130 }))
    s.play(write(eq, { duration: 2.4 }))
    s.wait(1)
})

The math engine is a separate download of about 1.6 MB in the browser. It loads only when a film first calls tex.

Selecting terms#

eq.part('b^2') returns the glyphs of a fragment. eq.parts('x') returns every occurrence. Use them to color a term or point at it.

import { film, recolor, tex } from 'vizmatic/stage'

export default film({ fps: 30 }, (s) => {
    const eq = s.add(tex('a^2 + b^2 = c^2', { size: 170 }))
    s.wait(0.4)
    s.play(...eq.part('a^2').map((glyph) => recolor(glyph, 'teal')))
    s.play(...eq.part('b^2').map((glyph) => recolor(glyph, 'orange')))
    s.wait(0.8)
})

Derivations#

morphMatching matches glyphs by what they are. A line of a derivation can grow out of the line before it.

import { film, morphMatching, tex, write } from 'vizmatic/stage'

export default film({ fps: 30 }, (s) => {
    const step = (source: string) => tex(source, { size: 170 })
    const a = s.add(step('2x + 3 = 11'))
    const b = step('2x = 11 - 3')
    const c = step('2x = 8')
    const d = step('x = 4')
    s.play(write(a))
    s.play(morphMatching(a, b))
    s.play(morphMatching(b, c))
    s.play(morphMatching(c, d))
    s.wait(0.8)
})

Readouts#

decimal(read, options) draws a number that follows a function of the timeline. Use the mono font so digits do not jump.

Search

↑ ↓ to navigateEnter to openEsc to close
Editable TSX

Example source

Open in the builder

Open the builder to edit this scene, preview animations, and export your visual.

Example preview