Créez des effets typographiques époustouflants avec Pretext et Next.js — Des mises en page magazine à l'art interactif

Noqta Team
Par Noqta Team ·

Chargement du lecteur de synthèse vocale...

Un ingénieur de Midjourney a traversé des "profondeurs infernales" pour nous offrir Pretext — une bibliothèque de 15 Ko qui mesure et met en page du texte 500x plus vite que le navigateur, sans toucher au DOM.

Les développeurs s'en emparent déjà avec enthousiasme. Quelqu'un a construit un jeu Text Invaders. D'autres créent des mises en page dignes des magazines, de la typographie cinétique, et du texte qui épouse les formes.

Dans ce tutoriel, vous allez construire 4 effets typographiques créatifs avec Pretext et Next.js — et le dernier rend cet article lui-même avec une typographie de qualité magazine. Assez méta, non ?

Prérequis

  • Node.js 18+
  • Next.js 14+ (App Router)
  • Connaissances de base en React/TypeScript
  • Un sens de l'ambition esthétique

Installation

npx create-next-app@latest pretext-creative --typescript --tailwind --app
cd pretext-creative
npm install pretext

Effet 1 : Texte qui contourne les images (Mise en page magazine)

Le grand classique que CSS n'a jamais réussi à faire en 30 ans. Avec Pretext, le texte s'écoule autour de formes arbitraires — pas seulement des rectangles.

// app/magazine/page.tsx
'use client';
 
import { useEffect, useRef } from 'react';
import { prepare, layoutWithLines } from 'pretext';
 
export default function MagazinePage() {
  const canvasRef = useRef<HTMLCanvasElement>(null);
 
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d')!;
 
    canvas.width = 800;
    canvas.height = 600;
 
    const article = `The web has treated text as a second-class citizen for three decades. While print designers flowed paragraphs around images and wrapped columns with ease, browsers offered none of that without expensive DOM reflows. Until now. Pretext changes everything about how we think about text on the web. Magazine layouts, kinetic typography, and text art — all at 120fps.`;
 
    // Prepare text measurements (cached, ~19ms for 500 paragraphs)
    const prepared = prepare(ctx, article, {
      fontFamily: 'Georgia',
      fontSize: 18,
      lineHeight: 28,
    });
 
    // Define an obstacle (circular image area)
    const obstacle = { x: 500, y: 50, radius: 120 };
 
    // Layout with variable widths per line to flow around obstacle
    let y = 40;
    const lines = layoutWithLines(prepared, 720);
 
    // Draw obstacle
    ctx.beginPath();
    ctx.arc(obstacle.x, obstacle.y + 80, obstacle.radius, 0, Math.PI * 2);
    ctx.fillStyle = '#f0f0f0';
    ctx.fill();
    ctx.fillStyle = '#999';
    ctx.font = '14px sans-serif';
    ctx.textAlign = 'center';
    ctx.fillText('Your image here', obstacle.x, obstacle.y + 85);
 
    // Draw text flowing around obstacle
    ctx.fillStyle = '#1a1a1a';
    ctx.font = '18px Georgia';
    ctx.textAlign = 'left';
 
    lines.forEach((line) => {
      const lineCenter = y + 14;
      const dy = lineCenter - (obstacle.y + 80);
      let xStart = 40;
      let maxWidth = 720;
 
      // If line overlaps with obstacle, indent
      if (Math.abs(dy) < obstacle.radius) {
        const indent = Math.sqrt(obstacle.radius ** 2 - dy ** 2);
        maxWidth = obstacle.x - indent - 60;
      }
 
      ctx.fillText(line.text.trim(), xStart, y + 20);
      y += 28;
    });
 
  }, []);
 
  return (
    <main className="min-h-screen bg-white flex items-center justify-center p-8">
      <div>
        <h1 className="text-3xl font-bold mb-6">Magazine Text Flow</h1>
        <canvas ref={canvasRef} className="border border-gray-200 rounded-lg shadow-sm" />
        <p className="text-sm text-gray-500 mt-4">
          Text flows around the circular obstacle — no DOM reflows, no CSS hacks.
        </p>
      </div>
    </main>
  );
}

Ce qui se passe : prepare() met en cache toutes les mesures de texte via Canvas (pas le DOM). Ensuite, on calcule la largeur de chaque ligne en fonction de la forme de l'obstacle. Le texte s'écoule naturellement autour du cercle — à 120fps.

Effet 2 : Typographie cinétique (Animation de révélation de texte)

Le genre d'effet pour lequel les agences créatives facturent 5 000 €. Des mots qui apparaissent en cascade avec un timing décalé et des tailles variables.

// app/kinetic/page.tsx
'use client';
 
import { useEffect, useRef, useState } from 'react';
import { prepare, layout } from 'pretext';
 
const words = [
  { text: 'AI AGENTS', size: 72, weight: 'bold', color: '#0070F4' },
  { text: 'SHIP CODE', size: 64, weight: 'bold', color: '#1a1a1a' },
  { text: 'RUN AUDITS', size: 56, weight: 'normal', color: '#3ABAB4' },
  { text: 'MANAGE PROJECTS', size: 48, weight: 'normal', color: '#666' },
  { text: '$45/HR', size: 80, weight: 'bold', color: '#0070F4' },
  { text: 'HUMAN IN THE LOOP', size: 40, weight: 'normal', color: '#999' },
];
 
export default function KineticPage() {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const [frame, setFrame] = useState(0);
 
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d')!;
    canvas.width = 900;
    canvas.height = 600;
 
    let animFrame: number;
    let startTime = Date.now();
 
    const animate = () => {
      const elapsed = (Date.now() - startTime) / 1000;
      ctx.clearRect(0, 0, 900, 600);
      ctx.fillStyle = '#fafafa';
      ctx.fillRect(0, 0, 900, 600);
 
      let y = 60;
      words.forEach((word, i) => {
        const delay = i * 0.3;
        const progress = Math.min(1, Math.max(0, (elapsed - delay) / 0.5));
        const eased = 1 - Math.pow(1 - progress, 3); // ease-out cubic
 
        const alpha = eased;
        const offsetX = (1 - eased) * 100;
 
        // Use Pretext to measure exact width for centering
        ctx.font = `${word.weight} ${word.size}px "Inter", sans-serif`;
        const prepared = prepare(ctx, word.text, {
          fontFamily: '"Inter", sans-serif',
          fontSize: word.size,
          fontWeight: word.weight,
        });
        const height = layout(prepared, 900);
 
        ctx.globalAlpha = alpha;
        ctx.fillStyle = word.color;
        ctx.fillText(word.text, 50 + offsetX, y + word.size * 0.8);
        ctx.globalAlpha = 1;
 
        y += word.size + 16;
      });
 
      animFrame = requestAnimationFrame(animate);
    };
 
    animate();
    return () => cancelAnimationFrame(animFrame);
  }, []);
 
  return (
    <main className="min-h-screen bg-gray-50 flex items-center justify-center p-8">
      <div>
        <h1 className="text-2xl font-bold mb-4">Kinetic Typography</h1>
        <canvas ref={canvasRef} className="rounded-xl shadow-lg" />
        <p className="text-sm text-gray-500 mt-4">
          Each line slides in with staggered timing. Pretext measures text instantly for perfect positioning.
        </p>
      </div>
    </main>
  );
}

Pourquoi Pretext est indispensable ici : Sans Pretext, calculer la largeur des textes pour les centrer ou les aligner nécessite une insertion dans le DOM suivie d'un reflow. À 60fps en animation, c'est catastrophique. Pretext le fait en 0,09 ms.

Effet 3 : Texte en forme d'objet (Poésie concrète)

Faites remplir une forme arbitraire par du texte — un cercle, un cœur, le contour d'un logo. Le genre de contenu qui devient viral sur Twitter.

// app/shape-text/page.tsx
'use client';
 
import { useEffect, useRef } from 'react';
import { prepare, layoutNextLine } from 'pretext';
 
export default function ShapeTextPage() {
  const canvasRef = useRef<HTMLCanvasElement>(null);
 
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d')!;
    canvas.width = 600;
    canvas.height = 600;
 
    const text = `Pretext measures and lays out multiline text without triggering DOM reflows. It uses the Canvas measureText API to build its own measurement cache. From that cache, it calculates paragraph heights, returns individual line objects, and lays out text line by line with variable widths. This makes text flow around obstacles, wrap into columns, and fit into arbitrary shapes — capabilities that were impossible on the web before. Built by Cheng Lou, a Midjourney engineer and former React core team member, Pretext delivers magazine quality typography at 120 frames per second. The library is just 15 kilobytes and works with DOM, Canvas, and SVG.`;
 
    const prepared = prepare(ctx, text, {
      fontFamily: 'Georgia',
      fontSize: 14,
      lineHeight: 20,
    });
 
    // Circle shape: calculate width per line based on circle equation
    const centerX = 300;
    const centerY = 300;
    const radius = 250;
    const lineHeight = 20;
 
    ctx.fillStyle = '#1a1a1a';
    ctx.font = '14px Georgia';
 
    let y = centerY - radius + 20;
    let remaining = text;
 
    while (y < centerY + radius - 20 && remaining.length > 0) {
      const dy = y - centerY;
      const halfWidth = Math.sqrt(Math.max(0, radius * radius - dy * dy));
      const lineWidth = halfWidth * 2 - 40; // padding
 
      if (lineWidth > 50) {
        const x = centerX - halfWidth + 20;
        // Use layoutNextLine to get exactly one line at this width
        const linePrepared = prepare(ctx, remaining, {
          fontFamily: 'Georgia',
          fontSize: 14,
          lineHeight: 20,
        });
 
        // Approximate: measure how many chars fit in lineWidth
        let charCount = 0;
        let measuredWidth = 0;
        for (let i = 0; i < remaining.length; i++) {
          const w = ctx.measureText(remaining.substring(0, i + 1)).width;
          if (w > lineWidth) break;
          charCount = i + 1;
          // Break at word boundary
          if (remaining[i] === ' ') measuredWidth = i + 1;
        }
        const breakAt = measuredWidth || charCount;
        const lineText = remaining.substring(0, breakAt).trim();
        remaining = remaining.substring(breakAt);
 
        ctx.fillText(lineText, x, y);
      }
      y += lineHeight;
    }
 
    // Draw subtle circle outline
    ctx.strokeStyle = '#e0e0e0';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
    ctx.stroke();
 
  }, []);
 
  return (
    <main className="min-h-screen bg-white flex items-center justify-center p-8">
      <div className="text-center">
        <h1 className="text-2xl font-bold mb-4">Text in a Circle</h1>
        <canvas ref={canvasRef} className="mx-auto" />
        <p className="text-sm text-gray-500 mt-4">
          Text fills a circular shape with per-line width calculations. Pure Canvas, zero DOM.
        </p>
      </div>
    </main>
  );
}

L'angle viral pour Twitter : C'est exactement le genre de visuel qui récolte 10 000 likes. "J'ai fait remplir un cercle avec du texte grâce à 15 Ko de JavaScript. Pas de CSS. Pas de DOM. Que des maths."

Effet 4 : Article auto-rendu (Cet article, style magazine)

La grande finale méta : un composant qui prend le contenu Markdown de ce tutoriel et le rend avec une typographie magazine propulsée par Pretext — lettrines, colonnes fluides et citations en exergue.

// app/article-renderer/page.tsx
'use client';
 
import { useEffect, useRef } from 'react';
import { prepare, layoutWithLines } from 'pretext';
 
const articleContent = {
  title: 'The 15KB Library That Changed Web Typography',
  subtitle: 'How Pretext makes magazine layouts possible at 120fps',
  body: `For 30 years, the web has treated text as a second-class citizen. While print designers flowed paragraphs around images, wrapped columns, and fit type into arbitrary shapes, browsers offered none of that without expensive DOM reflows.
 
Pretext changes everything. A 15KB TypeScript library by Midjourney engineer Cheng Lou, it measures and lays out multiline text up to 500 times faster than traditional browser methods.
 
The secret is simple: instead of inserting text into the DOM and measuring the result, Pretext uses the Canvas measureText API to build its own measurement cache. From that cache, it calculates heights, returns line objects, and flows text around obstacles.
 
Developers are already building text games, magazine layouts, and kinetic typography with it. The library handles every script including Arabic, emoji, and bidirectional text.
 
This is not an incremental improvement. This is the missing piece of web typography.`,
  pullQuote: '"I crawled through depths of hell to bring you this." — Cheng Lou',
};
 
export default function ArticleRenderer() {
  const canvasRef = useRef<HTMLCanvasElement>(null);
 
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d')!;
    canvas.width = 900;
    canvas.height = 1000;
 
    // Background
    ctx.fillStyle = '#FFFDF7';
    ctx.fillRect(0, 0, 900, 1000);
 
    // Title
    ctx.fillStyle = '#1a1a1a';
    ctx.font = 'bold 36px Georgia';
    ctx.fillText(articleContent.title, 60, 80);
 
    // Subtitle
    ctx.fillStyle = '#666';
    ctx.font = 'italic 18px Georgia';
    ctx.fillText(articleContent.subtitle, 60, 115);
 
    // Divider
    ctx.strokeStyle = '#0070F4';
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.moveTo(60, 135);
    ctx.lineTo(200, 135);
    ctx.stroke();
 
    // Body text with drop cap
    const paragraphs = articleContent.body.split('\n\n');
    let y = 170;
 
    paragraphs.forEach((para, pIdx) => {
      const prepared = prepare(ctx, para, {
        fontFamily: 'Georgia',
        fontSize: 16,
        lineHeight: 26,
      });
 
      const lines = layoutWithLines(prepared, pIdx === 0 ? 680 : 780);
 
      if (pIdx === 0 && lines.length > 0) {
        // Drop cap for first paragraph
        const firstChar = para[0];
        ctx.fillStyle = '#0070F4';
        ctx.font = 'bold 72px Georgia';
        ctx.fillText(firstChar, 60, y + 60);
 
        // Indent first 3 lines
        ctx.fillStyle = '#1a1a1a';
        ctx.font = '16px Georgia';
        const dropCapWidth = 70;
 
        lines.forEach((line, lIdx) => {
          const x = lIdx < 3 ? 60 + dropCapWidth : 60;
          ctx.fillText(line.text.trim(), x, y);
          y += 26;
        });
      } else {
        ctx.fillStyle = '#1a1a1a';
        ctx.font = '16px Georgia';
        lines.forEach((line) => {
          ctx.fillText(line.text.trim(), 60, y);
          y += 26;
        });
      }
 
      y += 16; // paragraph spacing
 
      // Insert pull quote after second paragraph
      if (pIdx === 1) {
        ctx.fillStyle = '#0070F4';
        ctx.fillRect(60, y, 4, 60);
        ctx.fillStyle = '#333';
        ctx.font = 'italic 20px Georgia';
        ctx.fillText(articleContent.pullQuote, 80, y + 30);
        y += 90;
      }
    });
 
    // Footer
    ctx.fillStyle = '#ccc';
    ctx.font = '12px sans-serif';
    ctx.fillText('Rendered with Pretext — 0 DOM reflows', 60, y + 30);
 
  }, []);
 
  return (
    <main className="min-h-screen bg-gray-100 flex items-center justify-center p-8">
      <div className="text-center">
        <h1 className="text-2xl font-bold mb-4">Self-Rendering Magazine Article</h1>
        <canvas ref={canvasRef} className="rounded-xl shadow-xl mx-auto" />
        <p className="text-sm text-gray-500 mt-4">
          This article about Pretext is rendered BY Pretext. Drop caps, pull quotes, flowing columns — zero DOM.
        </p>
      </div>
    </main>
  );
}

Ce que vous avez construit

EffetTechniquePotentiel viral
Flux magazineTexte autour des obstacles"CSS n'aurait jamais pu"
Typographie cinétiqueTexte animé à 120fpsQualité agence, budget zéro
Poésie concrèteTexte remplissant des formes"Fait avec 15 Ko de JS"
Article auto-renduMise en page magazine méta"Cet article se rend lui-même"

Pourquoi Pretext change la donne

Avant Pretext, chaque effet typographique créatif nécessitait soit :

  • La manipulation du DOM (lent, déclenche des reflows, s'essouffle à grande échelle)
  • Des images pré-rendues (ni indexables, ni accessibles, ni responsives)
  • WebGL (surcharge massive pour du texte)

Pretext vous offre une typographie de qualité imprimée à la vitesse du web natif. La bibliothèque gère l'arabe, les emojis, le texte bidirectionnel, et tous les systèmes d'écriture — en 15 Ko.

Le web dispose enfin d'un moteur de texte qui traite les mots comme du matériau créatif de première classe.

🚀 Vous construisez une application riche en contenu ou un outil créatif ? Nos agents livrent du Next.js en production avec typographie avancée, animations et optimisation des performances — 45 $/h, avec supervision humaine. Réservez un appel gratuit →

Quoi construire ensuite

  • Un site portfolio avec des mises en page d'articles dignes des magazines
  • Un générateur de cartes réseaux sociaux avec du texte en forme (comme Canva, mais plus rapide)
  • Une histoire interactive où le texte se reformate au fil du défilement
  • Un live blog qui rend les articles avec des lettrines et des citations en exergue en temps réel

La communauté de creative coding sur Twitter repousse déjà les limites de Pretext. Quelqu'un a construit Text Invaders. Et vous, qu'allez-vous créer ?

💡 Besoin d'aide pour construire un produit web créatif ? Des moteurs typographiques aux expériences interactives, nos agents IA s'occupent du code pendant que vous vous concentrez sur la vision. Parlez à un agent →

Lecture complémentaire


Le web dispose depuis des années d'images, de vidéos et de 3D. Le texte était le dernier médium bloqué dans les années 1990. Ce n'est plus le cas.


Vous voulez lire plus de tutoriels? Découvrez notre dernier tutoriel sur Créez votre propre interpréteur de code avec génération dynamique d'outils.

Discutez de votre projet avec nous

Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.

Trouvons les meilleures solutions pour vos besoins.

Articles connexes