Kurz gesagt
Pretext.js ist eine TypeScript-Bibliothek ohne Abhängigkeiten, die mehrzeiligen Text rein arithmetisch statt mit DOM-Operationen misst und positioniert. Damit eliminiert sie erzwungene synchrone Reflows, liefert ca. 500x schnellere Textmessung als getBoundingClientRect() und unterstützt jedes wichtige Schriftsystem weltweit. Für virtuelle Scroller, Chat-UIs oder Datengrids löst diese Bibliothek ein Problem, das Browser seit Jahrzehnten ignorieren.
Probiere Apidog noch heute aus
Einführung
Jedes Mal, wenn Ihr JavaScript getBoundingClientRect() aufruft oder offsetHeight liest, stoppt der Browser alles: Ausstehende Stiländerungen werden geleert, das Layout neu berechnet und ein vollständiger Rendering-Durchlauf erzwungen. Das ist der berüchtigte synchrone Reflow – die teuerste Operation, die ein Browser ausführen kann.
Multiplizieren Sie das mit 1.000 Chatblasen in einer virtuellen Liste oder 10.000 Zeilen in einem Datengrid: Das Ergebnis sind verlorene Frames, Ruckeln und User, die denken, die App ist kaputt.
💡 Apidog-Teams, die API-gesteuerte Frontends entwickeln, kennen diesen Schmerz: Antwortdaten in dynamische UIs zu streamen und dabei alles performant zu halten, ist eine stetige Herausforderung, wenn die Layout-Engine blockiert.
Cheng Lou (React-Motion, ReasonML), entwickelte Pretext.js, um dieses Problem fundamental zu lösen. Seit Release (März 2026) schoss das Projekt auf GitHub auf über 14.000 Sterne und wurde heiß auf Hacker News diskutiert.
In diesem Beitrag erfährst du, wie Pretext.js funktioniert, wann und wie du es einsetzt – praxisorientiert, mit Code und konkreten Anwendungsfällen.
Was ist Pretext.js?
Pretext.js ist eine reine JavaScript/TypeScript-Textlayout-Engine. Sie misst und positioniert mehrzeiligen Text ausschließlich durch Arithmetik – ohne getBoundingClientRect(), offsetHeight, Reflow oder DOM-Thrashing.
Statt den Browser zu fragen „Wie hoch ist dieser Text?“, berechnet Pretext.js die Antwort mathematisch – mit Schriftmetriken aus der Canvas-API.
API-Überblick:
import { prepare, layout } from '@chenglou/pretext';
// Schritt 1: Text vorbereiten (einmalig, cachebar)
const handle = prepare('Hello, pretext.js', '16px "Inter"');
// Schritt 2: Layout bei beliebiger Breite (reine Arithmetik, Mikrosekunden)
const { height, lineCount } = layout(handle, 400, 24);
Das ist alles. Zwei Funktionen: prepare() misst Textsegmente (per Canvas, einmalig), layout() berechnet das Layout (rein arithmetisch, blitzschnell). Alles nach dem ersten prepare() läuft ohne jeglichen DOM-Kontakt.
Warum ist das für API-intensive Anwendungen relevant?
Wenn du Apps baust, die z.B. Streaming-API-Antworten verarbeiten (KI-Assistenten, Dashboards, kollaborative Editoren), musst du die Höhe von Text kennen, bevor du ihn renderst. Sonst springt dein virtueller Scroller, Chat-UIs ruckeln – und das merkt jeder User.
Mit Pretext.js bekommst du die Höhe in Mikrosekunden, nicht in Millisekunden.
Das Problem, das Pretext.js löst
Erzwungener synchroner Reflow: Das eigentliche Bottleneck
Beispiel:
const elements = document.querySelectorAll('.text-block');
elements.forEach(el => {
const height = el.getBoundingClientRect().height; // REFLOW!
// Positionierung ...
});
Jeder getBoundingClientRect()-Aufruf löst Folgendes aus:
- JS-Execution pausiert
- Stile werden berechnet
- Layout für das gesamte Dokument (oder Subtree) neu berechnet
- Wert wird zurückgegeben
In einer Schleife über 1.000 Elemente verursacht das 1.000 Layout-Neuberechnungen. Kosten: ca. 94 ms (6 verlorene Frames bei 60 fps).
Virtuelles Scrollen: Das klassische Performance-Problem
Virtuelle Scroller (wie react-window, tanstack-virtual) müssen die Höhe jedes Elements kennen. Feste Höhen sind trivial, aber variable Textinhalte? Nightmare.
Meist werden Off-Screen-Elemente gerendert, gemessen und dann positioniert – das untergräbt den Sinn des virtuellen Scrollings und kostet Performance. Pretext.js eliminiert diesen Workaround: Du berechnest die Höhe exakt, bevor ein DOM-Knoten existiert.
Benchmarks
Pretext.js liefert:
| Ansatz | 1.000 Textblöcke | 500 Textblöcke |
|---|---|---|
DOM (getBoundingClientRect) |
~94ms (6 Frames) | ~47ms |
Pretext.js (layout()) |
~2ms | ~0,09ms |
| Geschwindigkeitsunterschied | ~47x schneller | ~500x schneller |
Gerade bei vielen Elementen ist der Unterschied dramatisch.
Wie funktioniert Pretext.js intern?
Die Bibliothek arbeitet in drei Phasen:
1. Textsegmentierung
prepare() normalisiert den Text, behandelt Leerzeichen, setzt Zeilenumbruchregeln (Unicode UAX #14) um und segmentiert in umbrechbare Einheiten.
Unterstützte Fälle:
- CJK-Zeichen: Zeichenbasierte Umbrüche
- Arabisch, Hebräisch: Bidirektionalität
- Thailändisch: Wörterbuchbasierte Segmentierung
- Hindi/Devanagari: Ligaturen
- Emoji: Korrekte ZWJ-/Sequenz-Behandlung
-
Weiche Trennstriche:
­wird erkannt
2. Canvas-Messung
Jedes Segment wird über Canvas measureText() gemessen (kein Reflow):
const ctx = offscreenCanvas.getContext('2d');
ctx.font = '16px "Inter"';
const metrics = ctx.measureText('Hello');
const width = metrics.width;
Die Ergebnisse werden gecached. Gleicher Text + gleiche Schriftart = kein erneuter Browserzugriff.
3. Reines arithmetisches Layout
layout() nutzt die gecachten Breiten und Containerbreite, bricht Zeilen mit einem Greedy-Algorithmus um. Kein DOM, keine Canvas-Operation – nur Addition und Vergleich.
const handle = prepare(longArticleText, '16px "Inter"');
const mobile = layout(handle, 375, 24); // { height: 2400, lineCount: 100 }
const tablet = layout(handle, 768, 24); // { height: 1200, lineCount: 50 }
const desktop = layout(handle, 1200, 24); // { height: 720, lineCount: 30 }
Ein Handle, beliebig viele Layouts für verschiedene Breiten – ideal für Responsive Designs.
Praktische Anwendungsfälle
1. Virtuelles Scrollen mit variabler Texthöhe
So integrierst du Pretext.js in einen virtuellen Scroller:
import { prepare, layout } from '@chenglou/pretext';
interface TextItem {
id: string;
content: string;
}
function computeHeights(items: TextItem[], containerWidth: number) {
return items.map(item => {
const handle = prepare(item.content, '14px "Inter"');
const { height } = layout(handle, containerWidth, 20);
return { id: item.id, height: height + 32 }; // +32 für Padding
});
}
// 10.000 Items in ~4ms messen
const heights = computeHeights(chatMessages, 600);
Keine Off-Screen-Elemente, keine Schätzungen, keine Sprünge beim Scrollen.
2. KI-Chat-Oberflächen (Streaming)
Bei AI-Chats wird Text Token für Token gestreamt – jede Änderung kann Zeilenumbrüche verursachen. Mit Pretext.js misst du die Höhe nach jedem Token ohne DOM-Kontakt:
let streamedText = '';
const font = '15px "SF Pro"';
socket.on('token', (token: string) => {
streamedText += token;
const handle = prepare(streamedText, font);
const { height } = layout(handle, bubbleWidth, 22);
scroller.updateItemHeight(messageId, height + padding);
});
3. Datengrids mit Textspalten
Für automatische Spaltenbreitenmessung – kein DOM nötig:
function computeColumnWidth(values: string[], font: string, padding: number) {
let maxWidth = 0;
for (const value of values) {
const handle = prepare(value, font);
const { height } = layout(handle, Infinity, 20);
// TODO: maxWidth anhand gemessener Breite setzen
}
return maxWidth + padding;
}
(Füge hier die Berechnung für die Breite gemäß deiner Anforderungen hinzu.)
4. Mehrsprachige Feeds
Gemischte Sprachen? Gleiche API:
const posts = [
{ text: 'This library changed everything', lang: 'en' },
{ text: 'RTL text with correct bidirectional layout', lang: 'ar' },
{ text: 'CJK text gets proper character-level breaks', lang: 'zh' },
];
posts.forEach(post => {
const handle = prepare(post.text, '16px system-ui');
const { height } = layout(handle, 400, 24);
});
Teste dein Textlayout mit Apidog
Wenn du API-gestützte, textlastige UIs baust, reicht gutes Layout nicht – die API-Antworten müssen auch stimmen.
Apidog macht es einfach, Streaming-API-Antworten zu simulieren und zu testen, wie Pretext.js damit umgeht. Du kannst verschiedene Textlängen, Sprachen und Unicode-Grenzfälle testen und prüfen, ob dein Virtueller Scroller korrekt arbeitet.
Für KI-Chat-Produkte bietet Apidog:
- Simulation von Streaming-Antworten (Token für Token)
- Tests mit mehrsprachigen Payloads
- Schemas validieren (richtige Textfelder)
- Automatisierte Testsuiten für Text-Rendering-Grenzfälle
Eine Textlayout-Bibliothek ist nur so gut wie die Daten, die sie bekommt. Schlechte API-Antworten = schlechtes Layout, egal wie schnell die Engine ist.
Bekannte Einschränkungen und Kritikpunkte
Rendering-Genauigkeit: Grenzfälle
- Ungewöhnliche Fonts/Kerning-Paare
- Mixed Font Sizes innerhalb eines Blocks
- Subpixel-Unterschiede (Canvas vs. DOM)
- Browserspezifische Eigenheiten
Für virtuelles Scrollen meist unwichtig, für Print-Layouts relevant.
Canvas-Messung ist nicht kostenlos
prepare() nutzt die Canvas-Engine. Wenn du tausende neue Handles pro Frame erstellst, wird das ggf. zum Bottleneck. Nutze Caching!
Keine CSS-Eigenschaften
Nicht unterstützt:
letter-spacingword-spacingtext-indenttext-transformfont-feature-settingsfont-variant
Wenn dein Layout auf diesen Eigenschaften basiert, musst du nachjustieren.
Kein Rendering
Pretext.js misst nur – für das eigentliche Rendering brauchst du weiterhin DOM/Canvas/SVG.
Pretext.js vs. klassische Ansätze
| Funktion | Pretext.js | DOM-Messung | Geschätzte Höhen |
|---|---|---|---|
| Geschwindigkeit (1K) | ~2ms | ~94ms | ~0ms |
| Genauigkeit | Hoch (Canvas) | Perfekt | Niedrig |
| DOM-Abhängigkeit | Nur prepare()
|
Vollständig | Keine |
| Reflow | Null | 1 pro Messung | Null |
| Mehrsprachigkeit | Volle Unicode | Browser | Schlecht |
| CSS-Eigenschaften | Nur Font | Vollständig | Keine |
| Speicherverbrauch | Segment-Cache | DOM-Knoten | Minimal |
| Responsive Layouts | 1 Handle, viele Layouts | Pro Breite neu | Pro Breite neu |
Für höchste Genauigkeit & CSS-Support: DOM. Für Geschwindigkeit und Virtualisierung: Pretext.js.
Erste Schritte
Installation
npm install @chenglou/pretext
# oder
pnpm add @chenglou/pretext
# oder
bun add @chenglou/pretext
Grundlegende Verwendung
import { prepare, layout } from '@chenglou/pretext';
const handle = prepare(
'Pretext.js computes text layout without touching the DOM.',
'16px "Inter"'
);
const result = layout(handle, 600, 24);
console.log(result.height); // z.B. 48 (2 Zeilen x 24px)
console.log(result.lineCount); // z.B. 2
Integration mit React
import { prepare, layout } from '@chenglou/pretext';
import { useVirtualizer } from '@tanstack/react-virtual';
import { useMemo, useRef } from 'react';
function VirtualChat({ messages }: { messages: string[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const containerWidth = 600;
const font = '14px "Inter"';
const lineHeight = 20;
const heights = useMemo(() => {
return messages.map(msg => {
const handle = prepare(msg, font);
const { height } = layout(handle, containerWidth, lineHeight);
return height + 24; // Padding
});
}, [messages]);
const virtualizer = useVirtualizer({
count: messages.length,
getScrollElement: () => parentRef.current,
estimateSize: (index) => heights[index],
});
return (
<div ref={parentRef} style={{ height: '100vh', overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.key}
style={{
position: 'absolute',
top: virtualRow.start,
width: containerWidth,
}}
>
{messages[virtualRow.index]}
</div>
))}
</div>
</div>
);
}
So erhältst du einen performanten virtuellen Chat mit exakt berechneten Elementhöhen vor dem Rendern.
Interaktiver Playground
Teste dein Layout live unter pretextjs.dev/playground.
Wann solltest du Pretext.js nicht verwenden?
- Statische Seiten: CSS reicht.
- Pixelgenaue Drucklayouts: DOM bleibt Referenz.
- CSS-lastige Textstile: Nicht unterstützt.
- Serverseitiges Rendering: Canvas-API ist clientseitig.
- Kleine, statische Listen: Optimierung lohnt nicht.
FAQ
Ist Pretext.js produktionsreif?
Frisch, aber erprobt. Große Testsuite, stabile API – aber: Fixiere die Version und prüfe mit deinen Schriften/Inhalten.
Funktioniert Pretext.js mit React, Vue, Svelte?
Ja, Framework-agnostisch. Zwei Funktionen, überall nutzbar.
Wie verhält es sich mit Webfonts?
prepare() misst mit der geladenen Schriftart. Achte darauf, dass Fonts geladen sind (document.fonts.ready).
Canvas- oder SVG-Rendering?
Ja, Layout-Ergebnisse sind rendering-agnostisch.
RTL-Support?
Ja, mit voller Bidirektionalität.
Wie groß ist das Bundle?
~15KB minimiert, keine externen Abhängigkeiten.
Wie genau ist das Ergebnis?
Meist innerhalb 1-2 Pixel des DOM. CSS-Eigenschaften wie letter-spacing werden nicht berücksichtigt.
Stilisierten Text messen?
Ein prepare() pro Fontspec. Für gemischte Stile: Text selbst segmentieren und mehrere Handles erzeugen.
Fazit
Pretext.js löst endlich performante, genaue Textmessung ohne DOM-Reflow. Für virtuelle Listen, Chat-UIs und Datagrids ersetzt die Library komplexe Workarounds durch zwei einfache Funktionsaufrufe.
Sie ist kein Allheilmittel: CSS-Eigenschaften fehlen, Subpixel-Deltas gibt es, SSR ist noch nicht möglich. Aber für performante Virtualisierung ist aktuell nichts schneller oder flexibler.
Bereit, performante, textlastige UIs zu bauen? Teste deine API-Endpunkte mit Apidog und integriere Pretext.js in deine Rendering-Pipeline.


Top comments (0)