ProseImg
Ein Bild: Theme-bewusst, responsiv und zoombar.
Jedes Bild in einer Markdown-Seite.
Jedes Beispiel unten ist ein Codeblock. Die Bild-Fixtures sind dasselbe Markup, tatsächlich gezeichnet — ein dunkler Zwilling, ein Original schmaler als die Spalte, eine Vektorgrafik, eine Animation und ein Bild, das sich nicht zoomen lässt.
Was sie hinzufügt
Zwei Varianten. Ein Screenshot eines dunklen Editors auf einer weißen Seite — oder umgekehrt — ist das häufigste hässliche Ding in einer Dokumentation, und CSS kann es nicht beheben, weil die Pixel falsch sind, nicht der Rahmen. Eine Seite darf eine zweite Datei benennen:
{dark="/shots/sidebar-dark.png"}
Zwei Anfragen, nicht eine Datei. Das Bild in der Seite und das Bild im
Zoom-Dialog fragen unterschiedliche Breiten an, sodass ein Telefon das
2400-px-Original nicht mehr herunterlädt, um es in eine 48rem-Spalte zu zeichnen.
Beides sind srcsets, die @nuxt/image erzeugt — keine Datei, die jemand
vorbereiten müsste.
Klick zum Zoomen, sofern das Bild nicht darum bittet, es zu lassen:
{zoom="false"}
Ein zoombares Bild trägt einen kleinen Zoom-Hinweis in seiner Ecke, dauerhaft
statt beim Hover — ein Cursor ist auf einem Touch-Gerät nichts und in einem
Screenshot der Seite unsichtbar. Das Bild ist eine Schaltfläche: Tab erreicht
sie mit sichtbarem Ring, Enter und Leertaste öffnen den Dialog, Escape
schließt ihn und gibt den Fokus zurück. Mit zoom="false" gibt es keinen
Hinweis, keinen Ring und nichts, wohin getabbt werden könnte.
Ein Hintergrund für ein transparentes Bild. Ein Diagramm, dessen Linien für
eine helle Fläche gezeichnet sind, verschwindet auf einer dunklen Seite. Nenne
eine Farbe pro Theme — jede CSS-Farbe, auch var(--card):
{background="#fafafa" dark-background="#171717"}
Farben statt Klassen, mit Absicht: Das Stylesheet des Layers wird aus seinen
eigenen Komponenten gebaut, nie aus Markdown — eine dark:bg-…-Klasse in einer
Seite hätte also kein CSS dahinter.
Die Ansicht nimmt den ganzen Bildschirm über einer weichgezeichneten Seite ein.
Ihre Bedienelemente — verkleinern, die Zoomstufe (die ihn zurücksetzt),
vergrößern, Original in neuem Tab öffnen, schließen — schweben in einer Zeile
über dem Bild, der Alt-Text steht darunter, sodass keins von beiden das Bild
verdeckt. Das Mausrad zoomt um den Cursor, eine Pinch-Geste um die Finger; ein
Klick aufs Bild zoomt an dieser Stelle auf 200 %, ein zweiter zurück; +, -
und 0 tun dasselbe über die Tastatur. Ein gezoomtes Bild wird zum Verschieben
gezogen und lädt die Originaldatei, damit es scharf bleibt. Ein Klick daneben
schließt die Ansicht.
API
Props
| Prop | Typ | Standard | Anmerkungen |
|---|---|---|---|
src | string | — | Wie geschrieben |
alt | string | — | Wie geschrieben |
dark | string | — | Eine zweite Datei, unter dem dunklen Theme ausgeliefert |
zoom | boolean | true | false lässt das Bild inert |
width | number | — | Optional; siehe Maße unten |
height | number | — | Optional; siehe Maße unten |
sizes | string | — | Überschreibt die Richtlinie in der Seite; siehe Größen |
background | string | — | Eine CSS-Farbe hinter einem transparenten Bild, in der Seite und in der Ansicht |
dark-background | string | — | Dasselbe unter dem dunklen Theme; fällt auf background zurück |
Größen
Das Theme weiß eine Sache, die @nuxt/image nicht wissen kann: wie breit der
Kasten ist, in den es ein Bild zeichnet. Mehr konfiguriert es nicht.
- In der Seite —
sm:95vw md:768px. Unterhalb vonmdist der Artikel der Viewport abzüglich seiner Ränder; abmdist er aufmax-w-3xlgedeckelt, unabhängig vom Bildschirm — die angefragte Breite hört also auf zu wachsen, statt auf einem 4K-Display weiterzuklettern. - Im Dialog —
sm:96vw md:96vw lg:96vw xl:1264px, dem Viewport folgend bis zur eigenen Deckelung des Dialogs beimin(96vw, 80rem).
Beide benennen nur die Breakpoints, die @nuxt/image ohnehin mitbringt — ein
Konsument, der image.screens neu definiert, behält seine Definition. Die Ebene
deklariert keine eigenen screens.
Ein Bild, das nicht so breit ist wie die Textspalte — ein Logo, ein Badge, ein Diagramm am Rand — sagt das:
{sizes="sm:50vw md:200px"}
sizes ist die eigene Syntax von @nuxt/image und erreicht nur das Bild in der
Seite. Der Dialog behält seine Richtlinie: eine, die die Textspalte beschreibt,
kann keinen Dialog beschreiben.
Maße
width und height sind optional und bleiben es — die meisten Seiten lassen
sie weg, und sie zu verlangen würde jede bereits geschriebene Seite brechen.
Gib sie trotzdem an, wo du kannst. Sie sind es, die den Browser den Kasten reservieren lassen, bevor die Datei eintrifft; ohne sie hat das Bild bis zum Laden kein Seitenverhältnis, und der Text darunter springt, sobald es da ist. Die Komponente bewahrt das Verhältnis des Paares, das sie bekommt, und erfindet nichts, wenn sie keines bekommt: es gibt kein Standard-Seitenverhältnis und keinen Zuschnitt, denn ein falsches Verhältnis ist schlimmer als ein Sprung.
Ein Bild, das schmaler ist als die Spalte, bleibt in seiner eigenen Größe, statt gedehnt zu werden.
Formate
SVG und animiertes GIF werden unverändert durchgereicht, in der Seite wie im
Dialog, ganz ohne srcset. Ein Vektor hat keine Pixel zum Neuberechnen — einen
608 px breiten anzufragen rastert eine Datei, die in jeder Größe scharf war —
und ein GIF durch eine Rasterpipeline kommt als sein erstes Einzelbild zurück.
Das ist, was die Komponente tut, keine Aussage über Provider: manche reichen diese Formate durch, manche nicht, und eine Dokumentations-Ebene kann nicht wissen, welchen ein Konsument konfiguriert hat. Responsive Quellen gelten den Rasterformaten, für die der Zoom existiert — PNG, JPEG, WebP, AVIF.
Provider
Die Ebene setzt keinen image-Schlüssel. Welcher Dienst ein Bild
transformiert, gehört dem Konsumenten, so wie sources: ein von der Ebene
gewählter Provider ist ein Provider, den jeder Konsument wieder rückgängig
machen muss, und er entscheidet über ein Konto und eine Rechnung ebenso wie über
eine URL. Konfiguriere
@nuxt/image wie in jeder
Nuxt-App; die Komponente übergibt ihm nur zwei sizes-Zeichenketten.
Sich selbst überlassen, wählt @nuxt/image selbst, und die drei Fälle, denen
eine Dokumentationsseite begegnet, sind:
- Ein Node-Server.
ipxtransformiert auf Anfrage unter/_ipx/…. Es brauchtsharp, eine native Bindung, die die Node-Ausgabe ohnehin mitführt. - Eine generierte Seite (
nuxt generate).ipxStaticschreibt jede Variante zur Bauzeit. Danach läuft nichts mehr, und die Ausgabe wächst um die Varianten — der Handel, den eine vollständig statische Seite ohnehin eingeht. - Eine Edge-Laufzeit — Cloudflare Workers und alles andere, das weder Node
noch statisch ist. Kein Transformer läuft, und Bilder werden so ausgeliefert,
wie sie eingecheckt wurden. Eine prerenderte Seite auf einer solchen Laufzeit
darf nicht auf
ipxkonfiguriert werden: es ist eine native Bindung und kann dort überhaupt nicht laufen. Greif zu einem CDN-Provider, wenn die Transformationen gewünscht sind.
Die Varianten des Dialogs entstehen auch bei einem statischen Build, obwohl der Dialog beim Rendern der Seite geschlossen ist — die Komponente meldet sie selbst beim Prerender-Crawler an. Heruntergeladen wird nichts, bis der Dialog aufgeht.
Ein Bild mit zoom="false" bekommt keine davon. Es gibt keinen Dialog, der
aufgehen könnte, also wäre jede dialoggroße Datei dafür Output, den niemand je
anfordern kann.
Anmerkungen
Die dunkle Variante wird ausdrücklich benannt, statt per Konvention geraten.
-dark aus einem Dateinamen abzuleiten heißt, dass eine fehlende Datei zu einem
kaputten Bild wird — und der Autor erfährt es nie.
Ein Bild aus einer Quelle, die aus einem anderen Repository geklont wurde, hat
keine eigene Route: Content lädt den Baum nach .data/content/, und nichts
kopiert seine Assets nach public/. Die Pfade in diesen Beispielen sind absolut
in das eigene public/ der konsumierenden Seite — dort muss ein
Dokumentationsbild heute liegen.