Components/TextTransformer

TextTransformer

Short-text transition helper: morphs per character through the text-morph engine by default (numbers roll by place value), and keeps the original whole-string blur crossfade as a mode, with a scoped text slot.

VerifiedSince 0.3.4

Not recommended for high-frequency real-time updates (e.g., streaming text, per-frame state changes). Better for occasional state/title/chapter/short-text changes.

Usage

TextTransformer

Loading demo...

Best Practices

  • Use it for labels, titles, badges, and occasional state changes; avoid streaming tokens or per-frame counters.
  • Reach for TxTextMorph directly when you want the morph without the fade compatibility layer — it exposes the full surface: springs, the place-value switch, locale and cursorIndex.
  • Keep durationMs aligned with the surrounding TxAutoSizer transaction when both are used together; the morph engine animates its own width and height, so an outer sizer is usually redundant.
  • Prefer wrap=false inside compact buttons/badges to avoid vertical jumps; enable wrap only for paragraph or chapter transitions (which fall back to fade).
  • Keep slot content inline and lightweight. Heavy components inside the slot are duplicated while the previous layer is visible.
  • If color changes with text, set color on the transformer root; under fade the outgoing layer keeps the old computed color for a cleaner crossfade.

API Reference

TxTextTransformer

Props

PropTypeDefaultDescription
textstring | number-Value rendered in the current layer and announced through the polite live region.
mode'morph' | 'fade'morphmorph hands the value to the text-morph engine (per-character diff, place-value digit rolls); fade is the original whole-string blur crossfade. The default slot and wrap both force fade.
durationMsnumber240Transition duration in milliseconds; under fade it also controls when the previous layer is removed.
blurPxnumber8Blur distance applied to the outgoing and incoming layers during the transition. fade only — the morph engine has no blur stage.
tagstringspanRoot HTML tag used for the transformer wrapper.
wrapbooleanfalseAllows multi-line text by switching layer whitespace from ellipsis mode to pre-line. Forces fade.

Slots

SlotPropsDescription
default{ text: string }Optional renderer for both current and previous layers. Use it to wrap the text in custom inline markup while keeping the transition logic.

Events

No Vue events are emitted. Drive state from the parent via the text prop.

Morph and Fade

mode defaults to morph: the value goes to the text-morph engine, shared words hold their position, only the changed characters move, and digits roll by place value. fade is the original whole-string blur crossfade, and the only mode blurPx applies to.

Two situations override mode and force fade: the default slot being in play (a slot renders arbitrary nodes, and the engine has no plain-text segments to morph), and wrap being on (the engine lays its segments out on one nowrap line and animates the container's width, which a value reflowing inside a constrained box cannot be measured against).

Morph vs fade

Loading demo...

Used with AutoSizer

When you want width/height to follow text changes smoothly, wrap TxTextTransformer in TxAutoSizer and trigger a transaction via autoSizerRef.action(() => ...).

If you do not want text to wrap during size animations (vertical/jumping lines), keep wrap=false; overflow will be clipped (overflow: hidden).

AutoSizer + TextTransformer

Loading demo...

Long Text / Chapter Switch

Long text chapter

Loading demo...

Title + Subtitle

Title + subtitle

Loading demo...

Status/Badge Text

Status text

Loading demo...

Overview

  • text is normalized with String(...); numbers are displayed as their string form.
  • Under morph the value goes to the engine: the whole value sits in a visually-hidden [tx-morph-sr] node and every segment is aria-hidden. The root's overflow opens back up to visible, or exiting segments would be cut in half — which is why morph does not ellipsise.
  • The root uses aria-live="polite"; under fade the previous layer is aria-hidden so screen readers announce only the current text.
  • Under fade, when text changes the component snapshots the previous DOM color, then renders both layers:
    • the new layer first lands in its setup state (transparent and blurred) with no transition;
    • the component forces one style commit, then starts the animation on the next animation frame;
    • the previous layer is removed after durationMs + 34ms. Without that commit, the setup and animating states would land in the same style recalc and the new text would appear at once instead of fading in.
  • Under prefers-reduced-motion: reduce, neither layer transitions; the new text replaces the old at once.
  • A newer text change cancels the previous timer/frame through an internal sequence guard, so stale transitions cannot remove the active layer.
  • wrap=false keeps one-line ellipsis behavior; wrap=true switches layers to white-space: pre-line for multiline or newline-delimited content.
  • The scoped slot is invoked for both current and previous text, so slot output should be deterministic from the provided text value.

Technologies

  • Live-region note: The root always uses aria-live="polite". Avoid placing multiple high-churn instances in the same dense region, or screen readers may announce more changes than users can consume.
  • Transition note: The outgoing layer is duplicated until durationMs + 34ms; slot content is rendered for both layers during that window. Keep slot renderers pure and cheap.
  • Fade-in fix (2026-09-24): Before this fix, the setup and animating states were computed in the same style recalc, so under fade the new text never faded in; only the old text blurred out. The setup state now lands without a transition, and a style commit is forced before the animation frame is requested. TxModeChip's label crossfade, which depends on this fade-in, is what surfaced it.
  • Verified coverage: text-transformer.test.ts checks:
    • the default morph path, and the slot and wrap fallbacks to fade;
    • on the fade side: live-region semantics, custom root tag, CSS variable mapping for duration/blur, wrap class, and numeric text normalization;
    • previous/current layers, preserved previous color, aria-hidden on the outgoing layer, timer cleanup, and scoped slot props;
    • the forced setup-state commit, the setup state landing without a tween, and the reduced-motion escape.
  • Component source: packages/tuffex/packages/components/src/text-transformer/src/TxTextTransformer.vue.
  • Types: packages/tuffex/packages/components/src/text-transformer/src/types.ts exports TextTransformerProps.
  • Export alias: packages/tuffex/packages/components/src/text-transformer/index.ts exports TextTransformer, TxTextTransformer, TextTransformerProps, and TxTextTransformerInstance.
  • Coverage: packages/tuffex/packages/components/src/text-transformer/__tests__/text-transformer.test.ts verifies both modes, both forced fallbacks, live-region defaults, transitions, CSS variables, slots, and cleanup.
  • Engine: morph renders TxTextMorph; see TextMorph for the engine itself.
查看源码
packages/tuffex/packages/components/src/text-transformer/index.ts