Ymotion
Help
Dashboard
All pages

Guides

Text animators and presets

Animating type by character, word or line with animators and selectors, and the text presets that are built from them.

A text layer moves as a whole with keyframes on its transform, like any layer. Its characters can also move on their own — a typewriter, a word that pops, a wave through a line — and that's what text animators do. This page explains them, and the text presets built from them.

How an animator works

A text animator, as in After Effects, has two halves:

  • Properties — what it changes: a position offset, a scale, an opacity, a fill colour.
  • Selectors — which characters it applies to, and how strongly. A selector gives each character, word or line an amount, usually from 0 to 1, and the properties apply in proportion.

An animator with "opacity":0 hides what its selector fully selects, half-hides what it half selects, and leaves the rest alone. Offsets add, scale and opacity multiply, and colours blend towards the animator's by the amount. With an omitted or empty selectors list on a new animator, it applies to all of the text in full, and its pieces are whole words unless another animator's selector counts characters.

Movement comes from keyframing the selector: as a selection slides across the text, each character takes the animator's values. Treat those values as where characters come from or go to, and the text's own style as where they rest; then selecting nothing leaves the text as set, and deleting the keys leaves it readable.

Building a typewriter by hand

An animator that hides what it selects, with a square selector over the whole line:

ymotion_edit {"label":"typewriter","ops":[{"verb":"addAnimator","args":{"layerId":"<layer id>","animator":{"name":"Type on","properties":{"opacity":0},"selectors":[{"kind":"range","basedOn":"characters","shape":"square","start":0,"end":1,"offset":1,"smoothness":0}]}}}]}

The reply says where it landed — addAnimator -> index=0 on a layer with none. Keying its selector's offset from 0 to 1 slides the selection off the end of the line, one character at a time:

ymotion_edit {"ops":[{"verb":"keyRamp","args":{"layerId":"<layer id>","path":"content.text.animators.0.selectors.0.offset","from":0,"to":1,"opts":{"start":0,"end":1.5,"ease":"linear"}}}]}

The static offset of 1 selects nothing, so without the keys the text shows in full. Key times are seconds from the layer's start, and on a layer that already had animators, use the index the reply gives.

Range selectors

A range selector selects a stretch of the text. Its fields, with the value each takes when left out:

  • start, end, offset (0, 1, 0) — the stretch, and offset slides it along. With units of percent, the default, these are fractions of the whole text: 0.5 is halfway, where After Effects' panel would show 50%. With "units":"index" they count units, so "units":"index","basedOn":"words","start":1,"end":2 is the second word however long the words are.
  • basedOn (characters) — characters, charactersExcludingSpaces, words or lines. With characters, a space takes a beat of its own in a typewriter.
  • shape (square) — square switches at the boundary; rampUp, rampDown, triangle, round and smooth fade across the range, so a sweep reads as one gesture.
  • smoothness (1), square only — at 1 a character partly inside the range is partly selected, so letters dissolve on; at 0 each is in or out, so letters switch on.
  • easeHigh, easeLow (0), −1 to 1 — soften the approach to full selection and the departure from none.
  • amount (1), −1 to 1 — how much of the animator this selector lets through.
  • randomize with a seed — visits the units in a shuffled order that depends only on the seed and the count, so every render agrees.
  • mode (add) — how it combines with the selectors before it: add, subtract, intersect, min, max or difference. The combination starts from nothing, so a lone subtract gives a negative amount: offsets, turns and scales run the other way, while opacity and colour stay as they are.

offset, start, end and amount can be keyframed. A highlight running through a line word by word, from half a second to three — on the layer above it lands at index 1. keyRamp eases out unless told otherwise, which would rush the first words, so this one is linear:

ymotion_edit {"label":"highlight sweep","ops":[{"verb":"addAnimator","args":{"layerId":"<layer id>","animator":{"name":"Highlight","properties":{"fill":"#FFD23F"},"selectors":[{"kind":"range","basedOn":"words","shape":"square","start":0,"end":1,"offset":-1}]}}}]}
ymotion_edit {"ops":[{"verb":"keyRamp","args":{"layerId":"<layer id>","path":"content.text.animators.1.selectors.0.offset","from":-1,"to":0,"opts":{"start":0.5,"end":3,"ease":"linear"}}}]}

Wiggly selectors

A wiggly selector gives each unit an amount that wanders over time. Its fields, with the value each takes when left out:

  • basedOn (characters) and mode (add), as on a range selector.
  • min and max (−1, 1) — the ends it wanders between. Below zero the animator's values push the other way, so a wobble swings both sides of rest; a min of 0 keeps it on one side.
  • frequency (2) — wiggles a second.
  • correlation (0.5) — 1 moves every unit together. Up to about 0.9 each unit still wanders on its own; closer to 1, neighbours follow one another as a ripple.
  • amount (1) and seed (0).

It's computed from the seed and the time, so a preview and an export agree. To wobble only part of the text, put a range selector first and the wiggly one after it with "mode":"intersect":

ymotion_edit {"ops":[{"verb":"addAnimator","args":{"layerId":"<layer id>","animator":{"name":"Wobble","properties":{"rotate":6,"y":0.08},"selectors":[{"kind":"range","units":"index","basedOn":"words","start":1,"end":2},{"kind":"wiggly","basedOn":"characters","mode":"intersect","frequency":2,"correlation":0,"seed":3}]}}}]}

What an animator can change

Distances are multiples of the type size (em), so an animator reads the same at any size:

  • x, y — position offset in em; positive y is down.
  • scale — 1 is unchanged. rotate — degrees.
  • opacity — 1 is unchanged. fillOpacity and strokeOpacity fade the fill or the outline alone.
  • tracking — extra letter-spacing in em, pushing the characters after it along, as After Effects' tracking amount does.
  • fill, stroke — paints, blended in by the amount. strokeWidth — in em; it can outline type that has no stroke.
  • skew, skewAxis — degrees. blur — a Gaussian sigma in em.
  • lineSpacing — pushes each line after the first further away, em per line.
  • charOffset wraps characters within fixed supported Unicode15.1 groups, preserving case/digit kind: ASCII, Latin-1/fullwidth Latin, basic Greek/Cyrillic, Hebrew letters, Hiragana/Katakana, Hangul syllables, Deseret and decimal digit sets. Marks, joining/variation controls and unknown groups stay unchanged; this adds no script shaping or full-Unicode mode. charValue instead replaces by code point (65 is "A"). Substitutions change glyph widths before layout; selectors count characters/words, not lines.

Numbers are keyed with keyOn, keyRamp or putTracks, colours with keyPaint or paintRamp. ymotion_read {"what":"properties","layerId":"<layer id>","like":"Animator"} lists every animator path on the layer, labelled with the animator's name.

content.text.grouping decides what characters turn and scale about — character (the default), word, line or all — and groupingAlignment, −1 to 1 on each axis, moves that pivot within its group:

ymotion_edit {"ops":[{"verb":"setText","args":{"layerId":"<layer id>","patch":{"grouping":"line","groupingAlignment":{"x":-1,"y":0}}}}]}

Changing animators afterwards

  • setAnimator takes an index and a patch, keeping unnamed nested properties fields. A named selectors list replaces whole: valid keys at retained indices stay, and keys for absent rows are removed, so a new selector does not inherit them. removeSelectorAt removes a selector and carries later keys down one.
  • addSelector adds a selector to an animator; removeSelectorAt takes the animator's index and the selector's position, at.
  • removeAnimatorAt and moveAnimator remove and reorder. Keyframes follow their animator to its new index.

Full field lists: ymotion_help {"topic":"types","name":"TextAnimatorProperties"}, ymotion_help {"topic":"types","name":"RangeSelector"} and ymotion_help {"topic":"types","name":"WigglySelector"}.

Changing the words over time

A Source Text keyframe sets what the layer says from a moment on, holding until the next. As in After Effects, the first key also holds before itself, so a lone key at 2 seconds changes the words from the start. To change them at 2 seconds, key the opening words at 0 as well:

ymotion_edit {"ops":[{"verb":"keyText","args":{"layerId":"<layer id>","localT":0,"segments":[{"text":"Every word"}]}},{"verb":"keyText","args":{"layerId":"<layer id>","localT":2,"segments":[{"text":"Every word matters"}]}}]}

A note on cost

For ordinary horizontal text, the finest unit any live selector counts is the unit painted. A selector stack proved to select nothing is set aside, including a zero-amount wiggly or an index window safely past the displayed source. Uncertain replacement-dependent counts stay live. charOffset 0 is idle; charValue is idle only when every source character already has that value. A leading constant solid paint can become every run's evaluated style, so Karaoke ends in its actual final colour with ordinary word shaping. Gradients, varying paints and paint operations after an earlier live animator remain live. A fill also stays live when a later width-only animator can depend on the original run fill as its missing-stroke colour; the optimiser never invents a stroke to make that fold. For longer horizontal text, base live selectors on words or lines where the gesture allows; an empty selectors list keeps whole words. Type on a path always follows shaped glyphs, so settling an animator never changes its curve geometry.

Text presets

A preset is a ready-made animator with its keyframes, applied in one call:

ymotion_edit {"ops":[{"verb":"applyPreset","args":{"layerId":"<layer id>","preset":"riseByWord"}}]}

There are 119, filed as After Effects files them. The text ones are under Text › Animate In, Animate Out, Loop and Looks; the rest are grades, camera moves and layer motions:

  • Text › Animate In (29) textFadeIn, slideUp, slideRight, zoomIn, zoomDown, slam, popUp, spring, spinIn, skewIn, bounceIn, trackIn, riseByWord, wordPop, fadeUpCharacters, waveIn, dropInByCharacter, rainingCharacters, scaleUpCharacters, spinInByCharacter, centerSpiral, typewriter, randomTypewriter, decoderFadeIn, flickerIn, glitchIn, openFromCentre, karaoke, outlineFill
  • Text › Animate Out (24) textFadeOut, slideUpOut, slideRightOut, zoomOut, blowUp, spinOut, springOut, popDown, trackOut, fadeOutWords, sinkWords, popOutWords, fadeOutCharacters, waveOut, twirlOff, skewOut, horizontalClose, hollowOut, fallAway, backspace, randomErase, flickerOut, glitchOut, decoderOut
  • Text › Loop (25) wave, enlargeLetters, jump, randomBounce, jiggly, ripple, stretch, tilt, shiver, glitch, shimmer, twinkle, flash, colorPulse, hollowSweep, typewriterLoop, cipher, accordion, lean, hover, hop, orbit, wobble, pulse, heartbeat
  • Text › Looks (7) textShadow, textLift, textHollow, textSplice, textOutline, textGlow, textNeon
  • Grades (14) clean, warm, cool, film, noir, teal, vhs, bleach, faded, vivid, mutedDaylight, moodyTravel, lumetriBasic, lumetriCreative
  • Opacity (3) fadeIn, fadeOut, fadeInOut
  • Entrance (4) scaleIn, popOn, bounce, slideIn
  • Exit (1) slideOut
  • Camera (7) pushIn, pushOut, drift, handheld, shake, float, bokehRack
  • Loop (3) breathe, sway, spin
  • Transition (1) linearWipe
  • Distort (1) heatHaze

ymotion_find {"catalogue":"presets","group":"Animate In"} narrows to one folder, and asking by id gives each knob's label, range, default and unit:

ymotion_find {"catalogue":"presets","id":"slideUp"}

Pass only the knobs you want to change. over is seconds; an em knob is a multiple of the type size:

ymotion_edit {"ops":[{"verb":"applyPreset","args":{"layerId":"<layer id>","preset":"slideUp","params":{"over":0.6,"by":1.2}}}]}

A knob the preset doesn't have is refused with a list of those it does, and a text preset on a layer that isn't text is refused too.

Choosing one

Every row ymotion_find gives for a text animation says what it looks like and what it suits (about), its character (feel: calm, lively, punchy, playful, elegant or techy) and what moves (unit: block, line, word or character). A search matches all three, and letters, words and lines find a unit too, so ymotion_find {"catalogue":"presets","text":"elegant"} is one family. Here they are by character, and by whether each arrives, leaves or holds:

  • calm, in textFadeIn, slideUp, slideRight, riseByWord, karaoke
  • calm, out textFadeOut, slideUpOut, slideRightOut, fadeOutWords, sinkWords, fadeOutCharacters
  • calm, loop tilt, colorPulse, hover, orbit
  • lively, in zoomIn, popUp, spring, bounceIn, wordPop, scaleUpCharacters
  • lively, out springOut, popDown, popOutWords
  • lively, loop lean, hop, wobble, pulse
  • punchy, in slam, skewIn
  • punchy, out zoomOut, blowUp, skewOut
  • punchy, loop heartbeat
  • playful, in spinIn, waveIn, dropInByCharacter, rainingCharacters, spinInByCharacter, centerSpiral
  • playful, out spinOut, waveOut, twirlOff, fallAway
  • playful, loop wave, enlargeLetters, jump, randomBounce, jiggly, ripple, shiver
  • elegant, in zoomDown, trackIn, fadeUpCharacters, openFromCentre, outlineFill
  • elegant, out trackOut, horizontalClose, hollowOut
  • elegant, loop stretch, shimmer, twinkle, hollowSweep, accordion
  • techy, in typewriter, randomTypewriter, decoderFadeIn, flickerIn, glitchIn
  • techy, out backspace, randomErase, flickerOut, glitchOut, decoderOut
  • techy, loop glitch, flash, typewriterLoop, cipher

Text reads as designed when it keeps to one character and varies within it: a family of two to four, the biggest move kept for the line that matters, and one direction held for a stretch. An In and an Out of the same character pair naturally. Captions walks through it for a run of captions.

Where each one lands

A preset works inside a window: the whole layer, unless you pass window with a start and end in seconds from the layer's start.

  • An In starts at the window's start and settles over seconds later. Karaoke instead takes span, the share of the window it spends lighting the line.
  • An Out starts over seconds before the window's end and is gone by it.
  • A Loop runs through the window, fading itself in over the first quarter-second and out over the last. Pulse, Heartbeat, Breathe and Sway have no fade: their finite tracks move only inside the window and restore the flat values outside. Spin holds its final rotation after the end. A loop's timing knob is usually period, the seconds one cycle takes.

over takes at most 92% of the window, so on a short layer a gesture finishes sooner rather than running past the end.

Stacking and replacing

Each application adds its own animator roles and keys their paths, so an In, a Loop and an Out share a layer without erasing each other. Drop In By Character uses two independent roles for movement and arrival opacity. A window between the other two keeps the loop still while the text arrives and leaves — here on a five-second layer:

ymotion_edit {"label":"in, loop, out","ops":[{"verb":"applyPreset","args":{"layerId":"<layer id>","preset":"riseByWord"}},{"verb":"applyPreset","args":{"layerId":"<layer id>","preset":"wave","window":{"start":1,"end":4.4}}},{"verb":"applyPreset","args":{"layerId":"<layer id>","preset":"textFadeOut"}}]}

Applying the same preset twice adds another set of animator roles, as in After Effects; Pulse and Heartbeat add none, and replace their own keys instead. To change one already there, edit its keys — or undo it with ymotion_doc {"action":"undo"}, which steps back a whole ymotion_edit call, and apply it again with new knobs.

Presets that move the whole block key transform.scale and transform.rotation, replacing only keys inside their own span, so a Zoom In and a Zoom Out both survive; Pulse and Heartbeat key transform.scaleX and transform.scaleY, which multiply with those. A track that loops or wiggles — Pulse, Heartbeat, Breathe, Sway, Spin, Shake, Handheld, Float — replaces, and is replaced by, any preset on its path whole. Pulse, Heartbeat, Breathe, Sway and the camera wiggles move only in their finite window, restoring the flat values outside; Spin holds its final rotation afterwards. A plain keyed preset removes the old loop or wiggle on that path, so Zoom In after Breathe settles once; other paths keep their motion. Block Ins/Outs and the scale/turn layer moves centre the anchor on the content first. Static turn, skew and flip are compensated through the full matrix; existing keyed position/anchor or keyed scale, turn or skew keeps its chosen pivot. Plans that also write x/y start from the compensated rest.

What a preset leaves behind

Only ordinary data: animator roles named after the preset, their keyframes, for block moves some transform keys, and for a look its layer styles (Neon adds a glow too). ymotion_read {"what":"properties","layerId":"<layer id>","like":"Fade Up Words"} shows every value Fade Up Words wrote, and ymotion_read {"what":"keys","layerId":"<layer id>"} the keyframes, all yours to change. Delete the keys and the text stands at rest.

Hollow Sweep follows each run’s current stroke paint, or its current fill when there is no stroke, including later colour changes. Block moves instead record the layer’s resting scale and rotation when applied; existing keyed motion keeps its chosen pivot under the content-anchor guard.

See also