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, andoffsetslides it along. Withunitsofpercent, 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":2is the second word however long the words are.basedOn(characters) —characters,charactersExcludingSpaces,wordsorlines. Withcharacters, a space takes a beat of its own in a typewriter.shape(square) —squareswitches at the boundary;rampUp,rampDown,triangle,roundandsmoothfade across the range, so a sweep reads as one gesture.smoothness(1),squareonly — 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.randomizewith aseed— 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,maxordifference. The combination starts from nothing, so a lonesubtractgives 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) andmode(add), as on a range selector.minandmax(−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; aminof 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) andseed(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; positiveyis down.scale— 1 is unchanged.rotate— degrees.opacity— 1 is unchanged.fillOpacityandstrokeOpacityfade 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.charOffsetwraps 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.charValueinstead 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
setAnimatortakes an index and a patch, keeping unnamed nestedpropertiesfields. A namedselectorslist replaces whole: valid keys at retained indices stay, and keys for absent rows are removed, so a new selector does not inherit them.removeSelectorAtremoves a selector and carries later keys down one.addSelectoradds a selector to an animator;removeSelectorAttakes the animator's index and the selector's position,at.removeAnimatorAtandmoveAnimatorremove 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
overseconds later. Karaoke instead takesspan, the share of the window it spends lighting the line. - An Out starts
overseconds 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
- Type and titles for the type itself.
- Keyframes and easing for keys, eases, loops and
putTracks. - Captions for timing text to speech.
- Sound effects for motion for a sound to go with each preset, like typing under a typewriter.
- Recipes for common call shapes.