En bref
Pretext.js est une bibliothèque TypeScript sans dépendance qui permet de mesurer et de positionner du texte multiligne grâce à des calculs arithmétiques purs, sans aucune opération DOM. Elle évite les reflows synchrones forcés, accélère la mesure de texte (jusqu'à 500 fois plus rapide que getBoundingClientRect()), et prend en charge tous les principaux systèmes d’écriture. Pour des défilements virtuels, interfaces de chat ou grilles de données, Pretext.js résout un problème ignoré par les navigateurs depuis 30 ans.
Essayez Apidog dès aujourd'hui
Introduction
À chaque fois que votre JavaScript utilise getBoundingClientRect() ou lit offsetHeight, le navigateur interrompt tout, applique les styles en attente et force un rendu complet (reflow synchrone forcé). C'est l'opération la plus coûteuse côté navigateur.
Lorsque cela concerne 1 000 bulles de chat dans une liste virtuelle ou 10 000 lignes dans une grille, vous obtenez des pertes d’images, des saccades et des utilisateurs frustrés.
💡 Les équipes Apidog qui développent des frontends pilotés par API connaissent bien cette problématique : diffuser des données dynamiques tout en gardant une interface fluide est un défi constant si le moteur de layout du navigateur vous freine.
Cheng Lou, créateur de react-motion et contributeur clé de React et ReasonML, a conçu Pretext.js pour répondre à ce problème. Lancée en mars 2026, la bibliothèque a rapidement atteint 14 000+ étoiles sur GitHub et déclenché de gros débats sur Hacker News.
Cet article vous montre comment Pretext.js fonctionne, quand l’utiliser, ses limites, et comment l’intégrer à vos projets.
Qu'est-ce que Pretext.js ?
Pretext.js est un moteur de mise en page de texte pur JavaScript/TypeScript. Il mesure et positionne le texte multiligne sans toucher au DOM, en utilisant uniquement les métriques de police de l’API Canvas.
API minimale :
import { prepare, layout } from '@chenglou/pretext';
// 1. Préparez le texte (une fois, cachable)
const handle = prepare('Hello, pretext.js', '16px "Inter"');
// 2. Mesurez la hauteur à une largeur donnée (arithmétique pure)
const { height, lineCount } = layout(handle, 400, 24);
prepare() mesure et segmente le texte (utilise Canvas une seule fois), layout() calcule la répartition sur lignes et la hauteur, sans aucun accès DOM.
Pourquoi c'est crucial pour les apps API-intensive
Streaming d’IA, dashboards temps réel, éditeurs collaboratifs : vous devez connaître la hauteur du texte entrant avant de le rendre, sinon le virtuel scroll saccade et la UX souffre. Pretext.js fournit cette valeur en microsecondes.
Le problème résolu par Pretext.js
Le reflow synchrone forcé
Code classique :
const elements = document.querySelectorAll('.text-block');
elements.forEach(el => {
const height = el.getBoundingClientRect().height; // REFLOW!
// Utilisez la hauteur...
});
Chaque appel à getBoundingClientRect() force :
- Pause JS
- Vidage des styles en attente
- Recalcul layout sur tout le document
- Renvoi de la valeur
Dans une boucle de 1 000 éléments, c’est 1 000 recalculs : ~94ms, soit 6 frames perdues.
Le défi du défilement virtuel
Pour du contenu à hauteur variable, les solutions classiques :
- Rendu off-screen pour mesurer (anti-virtuel !)
- Estimation de hauteur puis correction (sauts visuels)
- Hauteur fixe (UX limitée)
Pretext.js permet de calculer la hauteur exacte avant la création du moindre nœud DOM.
Benchmarks
| Approche | 1 000 blocs de texte | 500 blocs de texte |
|---|---|---|
DOM (getBoundingClientRect) |
~94ms (6 frames) | ~47ms |
Pretext.js (layout()) |
~2ms | ~0.09ms |
| Différence de vitesse | ~47x plus rapide | ~500x plus rapide |
La vitesse s’améliore sur des petits lots car la surcharge DOM reste fixe, alors que Pretext scale linéairement.
Comment Pretext.js fonctionne
Trois phases :
1. Segmentation du texte
prepare() normalise le texte, applique les règles Unicode (UAX #14), segmente en unités sécables. Support multilingue complet :
- CJK (caractère = point de coupure)
- Arabe/hébreu (RTL, bidirectionnel)
- Thaï (segmentation par dictionnaire)
- Hindi/devanagari, emojis,
2. Mesure Canvas
Chaque segment est passé à measureText() (Canvas), sans reflow DOM.
const ctx = offscreenCanvas.getContext('2d');
ctx.font = '16px "Inter"';
const metrics = ctx.measureText('Hello'); // Pas de reflow !
const width = metrics.width;
Les mesures sont mises en cache (texte + police).
3. Mise en page arithmétique pure
layout() assemble les segments sur lignes selon la largeur de conteneur :
- Additionne les largeurs jusqu’à dépassement
- Saute à la ligne
- Répète jusqu’à tout placer
- Multiplie le nombre de lignes par la hauteur de ligne
Aucun DOM, aucun Canvas, juste de l’arithmétique.
Modèle de poignée réutilisable
Un seul prepare() pour toutes les largeurs :
const handle = prepare(longArticleText, '16px "Inter"');
const mobile = layout(handle, 375, 24);
const tablet = layout(handle, 768, 24);
const desktop = layout(handle, 1200, 24);
Idéal pour les layouts responsives.
Cas d'utilisation pratiques
1. Défilement virtuel avec texte à hauteur variable
Intégration directe :
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 pour le padding
});
}
// 10 000 éléments mesurés en ~4ms
const heights = computeHeights(chatMessages, 600);
Pas de rendu off-screen ni estimation de hauteur.
2. Interfaces de chat IA
Streaming token par token :
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);
// Met à jour la position du scroller virtuel sans toucher au DOM
scroller.updateItemHeight(messageId, height + padding);
});
3. Grilles de données (colonnes texte auto-dimensionnées)
function computeColumnWidth(values: string[], font: string, padding: number) {
let maxWidth = 0;
for (const value of values) {
const handle = prepare(value, font);
// Layout avec largeur infinie = largeur naturelle
const { height } = layout(handle, Infinity, 20);
// Utiliser la largeur du segment pour le sizing exact
maxWidth = Math.max(maxWidth, /* largeur calculée */);
}
return maxWidth + padding;
}
4. Flux de contenu multilingue
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);
});
Tester votre mise en page de texte avec Apidog
Pour des interfaces riches en texte pilotées par API, validez vos données et vos rendus avant de déployer.
Apidog facilite la simulation de réponses API en streaming et le test de vos intégrations Pretext.js : scénarios multilingues, longueurs variables, cas limites Unicode, etc.
Pour les équipes chat IA, l’environnement Apidog permet de :
- Simuler des réponses en streaming (texte chunké simulant une sortie LLM)
- Tester des payloads multilingues
- Valider les schémas de réponse
- Automatiser des suites de tests pour les cas limites de rendu texte
Des réponses API de qualité sont indispensables pour un rendu correct, quelle que soit la performance de votre moteur de mesure.
Limitations et critiques connues
Cas limites de précision de rendu
Des écarts peuvent apparaître sur :
- Polices avec crénage inhabituel
- Textes avec tailles de police mixtes
- Différences sous-pixel Canvas vs DOM
- Particularités de shaping selon navigateur
Pour le scroll virtuel, ces écarts sont négligeables ; pour un rendu typographique pixel-perfect, attention.
La mesure Canvas a un coût
prepare() sollicite Canvas. Si vous créez des milliers de poignées uniques par frame, cela peut devenir un bottleneck. Privilégiez le caching ou le batch.
Pas de support des propriétés CSS avancées
Seule la police est prise en compte. Pas de gestion native de :
letter-spacingword-spacingtext-indenttext-transformfont-feature-settingsfont-variant
Si votre mise en page dépend de ces propriétés, la hauteur calculée pourra diverger de celle du navigateur.
Ce n'est pas un moteur de rendu
Pretext.js mesure, il ne rend pas. Vous avez toujours besoin du DOM ou d'un rendu Canvas/SVG pour afficher le texte.
Pretext.js vs. approches traditionnelles
| Caractéristique | Pretext.js | Mesure DOM | Hauteurs estimées |
|---|---|---|---|
| Vitesse (1K éléments) | ~2ms | ~94ms | ~0ms |
| Précision | Élevée (Canvas) | Parfaite (DOM) | Faible |
| Dépendance DOM | Aucune après prepare()
|
Complète | Aucune |
| Déclencheurs de reflow | 0 | 1 par mesure | 0 |
| Multilingue | Unicode complet | Complet (natif) | Faible |
| Support propriétés CSS | Limité | Complet | Aucun |
| Surcharge mémoire | Segments en cache | Nœuds DOM | Minimale |
| Layouts réactifs | 1 prepare(), N layout()
|
Re-mesure à chaque largeur | Ré-estimation |
Pour la précision pixel, le DOM reste la référence. Pour la performance sur des milliers d’éléments, Pretext.js domine.
Démarrer
Installation
npm install @chenglou/pretext
# ou
pnpm add @chenglou/pretext
# ou
bun add @chenglou/pretext
Utilisation de base
import { prepare, layout } from '@chenglou/pretext';
// Mesurer un paragraphe
const handle = prepare(
'Pretext.js computes text layout without touching the DOM.',
'16px "Inter"'
);
// Obtenir la hauteur à une largeur donnée
const result = layout(handle, 600, 24);
console.log(result.height); // ex: 48 (2 lignes x 24px)
console.log(result.lineCount); // ex: 2
Intégration avec 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>
);
}
Ce pattern permet d’obtenir un chat virtuel avec des hauteurs précises avant tout rendu DOM – plus de sauts, plus de reflow.
Aire de jeux interactive
Testez en temps réel sur pretextjs.dev/playground : collez du texte, ajustez la police et la largeur, observez le layout.
Quand NE PAS utiliser Pretext.js
- Pages statiques : CSS gère très bien la mise en page pour des contenus fixes.
- Mise en page d’impression pixel-perfect : Les différences sous-pixel importent, préférez le DOM.
- Style CSS complexe : Si vos hauteurs dépendent de propriétés CSS avancées, attendez-vous à des divergences.
- Rendu côté serveur : Pretext.js nécessite Canvas (non disponible nativement en Node.js).
- Petites listes statiques : Pour 50 items, la mesure DOM est suffisante et ne vaut pas une dépendance supplémentaire.
FAQ
Pretext.js est-il prêt pour la production ?
Lancée en 2026, déjà 14 000+ étoiles et utilisée en production chez Midjourney. Suite de tests solide, mais testez avec vos propres polices et contenus.
Fonctionne-t-il avec React/Vue/Svelte ?
Oui, Pretext.js est framework-agnostique. Utilisez prepare() et layout() où vous voulez.
Comment gère-t-il les polices web ?
prepare() mesure avec la police chargée au moment de l’appel. Si la police web n’est pas prête, les mesures seront fausses. Vérifiez le chargement avec document.fonts.ready.
Utilisable pour Canvas/SVG ?
Oui. Utilisez les hauteurs/sauts calculés pour positionner du texte sur Canvas, SVG, WebGL ou DOM.
Prise en charge des langues RTL ?
Oui, arabe, hébreu, direction mixte, etc.
Poids du bundle ?
15 Ko minifié, zéro dépendance, utilise seulement Canvas et Intl.Segmenter si dispo.
Précision vs mesure DOM ?
1 à 2 pixels d’écart typique. Si vous utilisez letter-spacing ou word-spacing, attendez-vous à plus de différence.
Mesure de texte stylisé (gras, italique, tailles mixtes) ?
Une seule police par prepare(). Pour du texte à styles mixtes, segmentez le texte par style et créez plusieurs poignées.
Conclusion
Pretext.js permet une mesure rapide et précise du texte sans reflow DOM. Idéale pour le virtuel scroll, les chats, et les grilles de données, la bibliothèque remplace des hacks complexes par deux appels de fonction.
Ce n’est pas une solution miracle : pas de support CSS avancé, quelques écarts sous-pixel, pas de support SSR natif. Mais pour le pré-calcul de hauteurs texte dans les listes virtualisées, rien de comparable.
Prêt à accélérer vos interfaces textuelles ? Commencez par tester vos endpoints API avec Apidog pour garantir la solidité de vos données, puis intégrez Pretext.js dans votre pipeline de rendu.


Top comments (0)