@frontia/react-native-fast-text
v0.1.0-alpha.0
Published
High-performance native typewriter text reveal component for React Native (Fabric)
Readme
@frontia/react-native-fast-text
High-performance native typewriter/reveal text for React Native. The full string is committed to native once; a native frame scheduler reveals it grapheme by grapheme with zero per-frame JS work, zero re-renders and zero text re-layout.
Status: alpha. Published as
0.1.0-alpha.xunder thealphadist-tag. API may change before0.1.0. Verified on React Native 0.86 (New Architecture) only.
Why
The usual ways to type out a known string are expensive every frame:
| approach | per frame |
| --- | --- |
| React state + text.slice() | new string → full React commit → native setText → full text re-layout |
| Reanimated useAnimatedProps (animateable-text) | new string on the UI runtime → Spannable rebuild → setText → re-layout |
| fast-text | advance one integer, move a draw range, redraw |
Measured on a Pixel emulator (release, details in
docs/benchmarks.md): 72.9 % janky UI frames for the
React-state approach vs 0.48 % here; 0 React re-renders during a
1,000-grapheme reveal (vs 419); layout built once per text; completion
time within +0.34 % of the configured duration even under frame drops. The
native render is pixel-identical (0.0000 % diff) to RN <Text> for Korean,
emoji-heavy, RTL and multi-line samples.
This is a plain-text reveal component — not a streaming/Markdown renderer
and not a drop-in <Text> replacement.
Requirements
- React Native 0.86 (the only verified version so far)
- New Architecture (Fabric) — no legacy/Paper support
- No Expo Modules dependency. Expo apps need a development build /
expo prebuild(not Expo Go).
Installation
yarn add @frontia/react-native-fast-text@alpha
cd ios && pod installAutolinking registers everything; no manual setup.
Quick start
import { FastText, type FastTextRef } from '@frontia/react-native-fast-text';
function Dialogue({ line }: { line: string }) {
const ref = useRef<FastTextRef>(null);
return (
<>
<FastText
ref={ref}
text={line}
millisecondsPerGrapheme={14}
style={{ color: 'white', fontSize: 16, lineHeight: 26 }}
onComplete={() => console.log('done')}
/>
<Button title="skip" onPress={() => ref.current?.skip()} />
</>
);
}Changing text starts a new reveal (with autoPlay, the default). The
component reserves its final height from the first frame — put it
directly inside a ScrollView and the content size is correct while the
reveal is still running.
API
Props
| prop | type | default | notes |
| --- | --- | --- | --- |
| text | string | — | the full string; committed to native once per value |
| millisecondsPerGrapheme | number | 14 | 0 reveals instantly (still fires onComplete) |
| autoPlay | boolean | true | start when text commits |
| playing | boolean \| undefined | undefined | declarative play/pause; undefined = uncontrolled |
| onComplete | () => void | — | at most once per run; never from a replaced text |
| reduceMotion | 'system' \| 'always' \| 'never' | 'system' | active ⇒ instant reveal (event still fires) |
| style | TextStyle | — | supported subset below |
| numberOfLines | number | unlimited | |
| testID / accessibilityLabel | string | label defaults to the full text | |
Supported style keys: color, fontFamily, fontSize, fontWeight,
fontStyle, lineHeight, letterSpacing, textAlign,
includeFontPadding (Android). Layout styles (margin, width, …) apply to
the container. Anything else — notably padding and allowFontScaling —
is not supported in the alpha.
Ref
start(); // start when idle (autoPlay={false}), or resume when paused
restart(); // play the same text again from 0 (onComplete can re-fire)
pause();
resume();
skip(); // reveal everything now; completes the run (fires onComplete)
reset(); // back to nothing revealed, idle, no eventAll state transitions are defined and tested: text change mid-play, restart,
skip, pause/resume, background/foreground, millisecondsPerGrapheme = 0,
empty string, unmount, Reduce Motion, and text replaced right before
completion (stale completions are dropped via an internal generation
counter).
Grapheme correctness
The reveal advances per extended grapheme cluster using platform ICU
segmentation (plus defensive merges), so Hangul, CJK, combining marks,
skin-tone emoji, ZWJ families (👨👩👧👦), flags, variation selectors and CRLF
never split mid-cluster — and the duration is clusters × ms, not
UTF-16 units × ms. Verified by 13 on-device segmentation tests.
Accessibility
- The internal transparent layout-oracle
<Text>is hidden from the accessibility tree; the native view is the accessible element with the full string as its default label (the reveal is visual only). reduceMotion="system"(default) follows the platform Reduce Motion setting and reveals instantly when it is on.
How it works
A transparent RN <Text> inside the wrapper acts as a layout oracle (Yoga
sees the final size immediately). The absolutely-positioned native view
builds its platform text layout once per text/style/width change —
StaticLayout on Android, TextKit on iOS — and a Choreographer /
CADisplayLink scheduler derives the revealed cluster count from monotonic
elapsed time, changing only a non-metric-affecting draw range per frame.
Late frames catch up by elapsed time, so completion never drifts.
Details: docs/architecture.md · docs/benchmarks.md · docs/frontia-migration.md
Known limitations (alpha)
allowFontScalingunsupported (density-independent px only).- iOS mixed-bidi lines may momentarily reveal a few extra glyphs during the animation (contiguous glyph-range drawing); Android is exact.
- Backgrounding does not pause the clock — on return the reveal catches up (possibly completing instantly).
- 120 Hz behavior not yet measured.
- Only RN 0.86 is verified; other versions are unsupported until tested.
Example app
example/ contains three screens: demo (playground incl. an oracle
parity toggle), bench (react-state vs animateable-text vs fast-text
benchmark suite with native instrumentation counters) and frontia
(a replica of a production dialogue layer — gesture zones, long-press
scrubbing and nested scrolling — with an automated state-transition suite).
yarn
yarn example start
yarn example android # or: iosLicense
MIT © Frontia
