Transforms and effects

Four methods act on the drawn result rather than declaring geometry: rotation, scale, opacity and effects.

Two levels of transform

rotation and scale exist in two places and mean different things.

Per shape

Passed inside a shape's own options:

.rect({ width: 100, height: 100, rotation: 30 })

Baked into that shape's geometry before it joins the group. Every other shape is untouched. Use it to tilt one element of a figure.

Per graphics

Called on the chain:

new Graphics()
  .rect({ width: 100, height: 100 })
  .ellipse({ x: 120, y: 0, width: 80, height: 80 })
  .fill('white')
  .rotation(30);          // turns the whole figure

Applied to the combined result as one transform, so the whole drawing turns together and keeps its internal arrangement.

// Two rects that each lean 15 degrees, in a figure turned 45 degrees
new Graphics()
  .rect({ x: -80, y: 0, width: 60, height: 200, rotation: 15 })
  .rect({ x:  80, y: 0, width: 60, height: 200, rotation: 15 })
  .fill('white')
  .rotation(45);

These are setters, not appenders. Calling .rotation() twice keeps the last value, and position in the chain does not matter for them.

Pivots

Both levels take a pivot.

Per shape it is the pivot prop, which accepts a Vector2 or a named anchor:

.rect({ width: 100, height: 100, rotation: 30, pivot: 'bottomLeft' })

Per graphics it is the second argument, resolved against the figure's bounding box:

.rotation(30, 'topRight')          // turn about the figure's top-right corner
.scale(1.4, { x: 0, y: -120 })     // grow about an explicit point

Omit it and both pivot about the centre, of the shape's box or of the figure's box.

opacity

Set on the graphics, and unlike a per-shape opacity it composites the whole drawing into one layer first:

new Graphics()
  .ellipse({ x: -40, y: 0, width: 160, height: 160 })
  .ellipse({ x:  40, y: 0, width: 160, height: 160 })
  .fill('white')
  .opacity(0.4);

The two circles overlap, and the overlap is not darker. The figure fades as one. Setting opacity: 0.4 on each shape instead would double up where they cross.

Like rotation and scale it is a setter, so the last call wins and it applies to everything in the list.

effects

effects() runs a filter over the drawing. It takes anything the effects attribute takes: one effect, an array, or an Effects chain.

import { Graphics, Effects } from 'motion-script';

new Graphics()
  .ellipse({ width: 200, height: 200 })
  .fill('#6990DD')
  .effects(Effects.blur(24));

It scopes like fill

effects() is not a setter. It applies to the shapes and paints recorded since the previous effects() call, or since the start, and then closes that group. Shapes after it start fresh and unfiltered.

new Graphics()
  .ellipse({ x: -60, y: 0, width: 200, height: 200 })
  .ellipse({ x:  60, y: 0, width: 200, height: 200 })
  .fill('#6990DD')
  .effects(Effects.blur(16))     // blurs those two circles together

  .rect({ width: 300, height: 8 })
  .fill('white');                 // sharp, drawn over the blur

Because the group is composited before filtering, the effect reads the group's combined outline. A blur bleeds across the shared edge rather than around each circle separately.

A second effects() later filters only the later group:

  .path(glowShape).fill('#F5C26B').effects(Effects.bloom({ threshold: 0.2 }))
  .path(flatShape).fill('white').effects(Effects.grayscale(1))

What a group covers

A group starts at the first shape after a paint or after an effects() call, so content already closed off by its own paint is not swept into a later effect:

new Graphics()
  .rect({ width: 800, height: 400, cornerRadius: 12 })
  .fill('#141824')                             // closes the backdrop group

  .rect({ width: 60, height: 200 })
  .fill('#6990DD')
  .effects(Effects.bloom({ threshold: 0.3 })); // only the bar glows

Summary

MethodKindApplies toRepeat behaviour
shape rotation / scaleshape propthat shape onlyper shape
.rotation() / .scale()setterthe whole figurelast call wins
.opacity()setterthe whole figure, as one layerlast call wins
.effects()operationthe current group, then closes iteach call filters its own group