Zum Inhalt springen
duxt

ProseImg

Ein Bild: Theme-bewusst, responsiv und zoombar.

Jedes Bild in einer Markdown-Seite.

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:

![Die Seitenleiste](/shots/sidebar.png){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:

![Ein Diagramm](/diagram.svg){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):

![Ein Diagramm](/diagram.svg){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

PropTypStandardAnmerkungen
srcstring—Wie geschrieben
altstring—Wie geschrieben
darkstring—Eine zweite Datei, unter dem dunklen Theme ausgeliefert
zoombooleantruefalse lässt das Bild inert
widthnumber—Optional; siehe Maße unten
heightnumber—Optional; siehe Maße unten
sizesstring—Überschreibt die Richtlinie in der Seite; siehe Größen
backgroundstring—Eine CSS-Farbe hinter einem transparenten Bild, in der Seite und in der Ansicht
dark-backgroundstring—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 von md ist der Artikel der Viewport abzüglich seiner Ränder; ab md ist er auf max-w-3xl gedeckelt, 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 bei min(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:

![Das Badge](/badge.png){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. ipx transformiert auf Anfrage unter /_ipx/…. Es braucht sharp, eine native Bindung, die die Node-Ausgabe ohnehin mitführt.
  • Eine generierte Seite (nuxt generate). ipxStatic schreibt 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 ipx konfiguriert 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.

War diese Seite hilfreich?