Skip to main content

Modifiers

Modifiers are chainable operations that animate or style a shape. They return a new ShapeBuilder so you can keep chaining.

Timing Model

Most animated modifiers accept the same timing fields:

  • duration (ms, required)
  • start (number or TimeRef like scene+200 or prev.end+150)
  • delay (ms)
  • repeatDelay (ms)
  • loop (boolean)
  • ease (linear, easeIn, easeOut, easeInOut, or { type: 'cubicBezier', x1, y1, x2, y2 })
  • keyframes (array of { time, value } with time in ms)

If keyframes is provided, the last keyframe time defines the duration unless you also set duration.

Color & Opacity

fill

fill
Solid fill or animated color transitions.
DSL
Circle(48)
.fill({ from: '#0ea5e9', to: '#22c55e', duration: 1200, loop: true, ease: 'easeInOut' })

gradient

gradient
Linear or radial gradients with stops.
DSL
Rect(180, 100)
.gradient({
type: 'linear',
from: { x: -90, y: -50 },
to: { x: 90, y: 50 },
stops: [
{ pos: 0, color: '#0ea5e9' },
{ pos: 1, color: '#22c55e' },
],
})

stroke

stroke
Outline with width, dash patterns, and color.
DSL
Path([
{ x: -80, y: -40 },
{ x: 80, y: 40 },
], false)
.fill('rgba(0,0,0,0)')
.stroke({ color: '#22d3ee', width: 6, dash: [8, 6] })

opacity

opacity
Fade in or out over time.
DSL
Rect(140, 80)
.fill('#0f172a')
.opacity({ from: 0, to: 1, duration: 600, loop: true })

shadow

shadow
Drop shadow with blur and offsets.
DSL
Rect(160, 90)
.fill('#ffffff')
.shadow({ color: 'rgba(0,0,0,0.2)', blur: 10, offsetX: 0, offsetY: 8 })

blendMode

blendMode
Controls canvas compositing.
DSL
Circle(60)
.fill('#22d3ee')
.blendMode('screen')

fillMode

fillMode
Tints images with fill colors.
DSL
Image('https://placehold.co/320x200/png', 160, 120)
.fill('#0ea5e9')
.fillMode('multiply')

Position & Motion

at

at
Sets an absolute position in local space.
DSL
Circle(20)
.fill('#22c55e')
.at({ x: -120, y: 40 })

move

move
Linear motion between two points.
DSL
Circle(30)
.fill('#0ea5e9')
.move({ x: [-120, 120], duration: 1200, loop: true, ease: 'easeInOut' })

curve

curve
Move along a quadratic curve.
DSL
Circle(18)
.fill('#f97316')
.curve({
from: { x: -120, y: 60 },
control: { x: 0, y: -120 },
to: { x: 120, y: 60 },
duration: 1400,
loop: true,
})

pathMotion

pathMotion
Follow a custom path.
DSL
Circle(14)
.fill('#38bdf8')
.pathMotion({
path: [
{ x: -120, y: -40 },
{ x: 0, y: 60 },
{ x: 120, y: -40 },
],
duration: 1200,
loop: true,
})

orientToPath

orientToPath
Rotate to align with the path tangent.
DSL
Triangle(60, 40, { direction: 'right' })
.fill('#0f172a')
.pathMotion({
path: [
{ x: -120, y: -40 },
{ x: 0, y: 60 },
{ x: 120, y: -40 },
],
duration: 1200,
loop: true,
})
.orientToPath({ offset: Math.PI / 2 })

velocity

velocity
Constant velocity motion.
DSL
Circle(10)
.fill('#a3e635')
.velocity({ x: 0.08, y: 0.02, duration: 2000, loop: true })

acceleration

acceleration
Constant acceleration.
DSL
Circle(10)
.fill('#f472b6')
.acceleration({ x: 0.0002, y: 0.0004, duration: 2000, loop: true })

spring

spring
Spring easing between two points.
DSL
Circle(28)
.fill('#facc15')
.spring({ x: [-80, 80], duration: 1200, loop: true, stiffness: 160, damping: 18, mass: 1 })

Transform

scale

scale
Uniform scaling.
DSL
Rect(120, 70)
.fill('#0ea5e9')
.scale({ from: 0.8, to: 1.1, duration: 900, loop: true, ease: 'easeInOut' })

scaleX / scaleY

scaleX / scaleY
Independent axis scaling.
DSL
Rect(120, 70)
.fill('#22c55e')
.scaleX({ from: 0.6, to: 1.2, duration: 900, loop: true, ease: 'easeInOut' })
.scaleY({ from: 1.2, to: 0.6, duration: 900, loop: true, ease: 'easeInOut' })

rotate

rotate
Rotate around the anchor.
DSL
Rect(100, 100)
.fill('#0f172a')
.rotate({ from: 0, to: Math.PI * 2, duration: 2000, loop: true, ease: 'linear' })

skewX / skewY

skewX / skewY
Skew along an axis.
DSL
Rect(120, 70)
.fill('#38bdf8')
.skewX({ from: -0.3, to: 0.3, duration: 1000, loop: true, ease: 'easeInOut' })

flip

flip
Cosine-based flip on axis.
DSL
Rect(120, 70)
.fill('#f97316')
.flip({ from: 0, to: Math.PI, duration: 1200, loop: true, axis: 'y' })

anchor

anchor
Change transform pivot.
DSL
Rect(160, 100)
.fill('#111827')
.anchor('topLeft')
.rotate({ from: 0, to: Math.PI / 8, duration: 800, loop: true })

Shape-Specific

distort

distort
Warp path corners with bilinear corner offsets. Works with Rect, Path, and Compound paths.
DSL
Rect(180, 110)
.fill('#0f172a')
.distort({
tl: { x: -20, y: 0 },
tr: { x: -44, y: 16 },
br: { x: -8, y: 0 },
bl: { x: 6, y: -8 },
})

trim

trim
Trim a path by percentage.
DSL
Circle(60)
.fill('rgba(0,0,0,0)')
.stroke({ color: '#22d3ee', width: 6 })
.trim({ from: 0, to: 0.75, duration: 1200, loop: true })

morph

morph
Morph between compatible shapes.
DSL
Rect(120, 80)
.fill('#a855f7')
.morph({ width: 120, height: 80 }, { width: 200, height: 60 }, 1200, { loop: true })

clip

clip
Clip a shape using another shape.
DSL
Circle(60)
.fill('#22c55e')
.clip(Rect(120, 80).rotate({ from: 0, to: Math.PI / 8, duration: 1200, loop: true }))

textPath

textPath
Render text along a smooth Bezier path.
DSL
Text('Disclose', { fontSize: 30, fontWeight: 600, letterSpacing: 1 })
.fill('#0ea5e9')
.textPath({
path: BezierPath([
{ cmd: 'moveTo', x: -140, y: 10 },
{ cmd: 'quadTo', cpx: -70, cpy: -40, x: 0, y: 0 },
{ cmd: 'quadTo', cpx: 70, cpy: 40, x: 140, y: 10 },
]),
align: 'center',
})

Ordering & Time

zIndex

zIndex
Higher zIndex draws on top.
DSL
Scene(() => [
Circle(60).fill('#0f172a').zIndex(1),
Circle(40).fill('#38bdf8').zIndex(2),
])

timeScale

timeScale
Speed up or slow down a shape's time.
DSL
Circle(20)
.fill('#22d3ee')
.timeScale(2)
.move({ x: [-60, 60], duration: 600, loop: true })

Chained Examples

Chained Modifiers
Style, motion, and timing combined.
DSL
Rect(180, 100)
.fill('#0f172a')
.stroke({ color: '#ffffff', width: 2 })
.shadow({ color: 'rgba(0,0,0,0.35)', blur: 12, offsetY: 10 })
.rotate({ from: -0.08, to: 0.08, duration: 1200, loop: true, ease: 'easeInOut' })
.scale({ from: 0.96, to: 1.02, duration: 1200, loop: true, ease: 'easeInOut' })
.opacity({ from: 0.7, to: 1, duration: 1200, loop: true })
Chained Motion
Multiple motion modifiers stacked together.
DSL
Circle(28)
.fill('#22c55e')
.move({ x: [-140, 140], duration: 1800, loop: true, ease: 'easeInOut' })
.scale({ from: 0.8, to: 1.1, duration: 900, loop: true, ease: 'easeInOut' })
.opacity({ from: 0.5, to: 1, duration: 900, loop: true, ease: 'easeInOut' })