Components/TextMorph

TextMorph

Text that animates only the segments that changed.

VerifiedSince 0.6.0

Usage

Basic

Only the segments that actually changed move, and the container's size follows on the same curve.

Loading demo...

Number Place Value

Digits match by place value, not left to right: 1,204 → 1,318 rolls the hundreds and tens and leaves the thousands alone.

Loading demo...

Springs

spring takes a preset name or coefficients and sets both curve and duration, so durationMs and easing are ignored.

Loading demo...

Best Practices

  • Reach for it where the same quantity changes: counters, totals, percentages.
  • Pass cursorIndex for a number the user is typing, or inserting a digit before 20 reads as renumbering the column.
  • Use TxTextTransformer with mode="fade" where text must soft-wrap or ellipsise; here only \n breaks a line.
  • Keep high-frequency instances on one screen few; every character is an element with will-change.
  • Don't wrap it in TxAutoSizer; the engine animates the container's width and height itself.

API Reference

Props

PropTypeDefaultDescription
textstring | number-The value to render; numbers are formatted with locale and decimals first.
tagstringspanRoot element tag.
durationMsnumber400Morph duration in ms; ignored when spring is set.
easingstringcubic-bezier(0.19, 1, 0.22, 1)CSS timing function; ignored when spring is set.
spring'snappy' | 'smooth' | 'bouncy' | { stiffness?, damping?, mass? }-Spring physics; sets both the curve and the duration.
scalebooleantrueScales exiting segments as they leave.
numbersbooleantrueRolls numbers by place value; off falls back to the character morph.
decimalsnumber-Fraction digits; applies only when text is a number.
localestringenUsed for segmentation and number formatting.
cursorIndexnumber-Caret position; switches a single-number value to caret matching.
disabledbooleanfalseSkips the animation and writes the value straight in.
respectReducedMotionbooleantrueTreats reduced motion as disabled.
debugbooleanfalseOutlines the root and every segment, for debugging a morph.

Events

EventPayloadDescription
animation-start-A morph began; never fires for the first render.
animation-complete-The morph ran to its end; exactly one of this and animation-cancel fires per morph.
animation-cancel-The next update interrupted the morph.

Overview

  • The unit of motion is the character segment, not the string: survivors FLIP to their new position, arrivals fade in from the nearest anchor, and exits leave the flow and fade out.
  • The whole value lives in one visually hidden [tx-morph-sr] node and every segment is aria-hidden, so a screen reader announces the value once.
  • Selectable segments keep the original whitespace (NBSP, repeated spaces, tabs, line breaks, combining characters); exiting segments are not selectable, so a copy never mixes in the previous text.
  • Grouping commas travel with the magnitude; once the magnitude jumps by three places or more, no digit carries across and the number is replaced.
  • An update mid-morph carries the current velocity into the new curve, so rapid updates don't stall at the start of each curve.
  • Under reduced motion or disabled, the value is written straight to textContent and the internal segment record is cleared.

Technologies

  • The engine creates and owns the segment elements imperatively (the first-paint text exists only for SSR hydration), so the styles aren't scoped; tx-morph-* attribute names contain them.
  • The engine (also exported as TextMorphEngine / MorphController) is ported from lochie/torph (MIT), with its spring layer replaced by liquid/src/spring.ts, shared with TxLiquid.
  • Source: packages/tuffex/packages/components/src/text-morph/.
查看源码
packages/tuffex/packages/components/src/text-morph/index.ts