# React Image Cropper: Zuschnitt vor dem Upload, Zoom, Drehung

> Bau einen React Image Cropper mit Pinch-Zoom und Drehung, der vor dem Upload zuschneidet, ein WebP in Zielgröße liefert und Avatare mit Fortschritt hochlädt.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 19. Okt. 2026
- Kategorie: [Komponenten-Guides](https://wingo-ui.com/de/blog/category/components)
- Lesezeit: 9 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/react-image-cropper
- Englische Fassung: https://wingo-ui.com/blog/react-image-cropper

## Kurz gesagt

Ein React Image Cropper sollte im Browser zuschneiden, noch vor dem Upload: verschieben per Drag, zoomen per Pinch oder Mausrad, gerade richten per Drehung, und dann über ein Canvas einen verkleinerten WebP- oder JPEG-Blob exportieren, damit ein 12-Megapixel-Foto vom Smartphone nie in voller Größe hochgeladen wird. Für Avatare nimmst du einen runden Rahmen und exportierst PNG oder WebP mit transparenten Ecken. Der Image Cropper von Wingo UI erledigt das mit einem setApi-Handle und dem Helfer cropImage, und Avatar Upload macht daraus einen Ablauf aus Auswählen, Zuschneiden und Hochladen mit Fortschrittsring.

Du lässt Leute ein Profilfoto oder ein Bild für ein Inserat hochladen, und was ankommt, sind JPEGs mit 4.000 Pixeln, seitlich gedrehte Gesichter und Köpfe, die `object-fit: cover` abschneidet. Ein React Image Cropper löst das, bevor die Datei den Browser verlässt: Die Person wählt den Ausschnitt, du exportierst ein kleines Bild in genau der Größe, die du brauchst, und der Server speichert das, was die Seite auch zeigt. Dieser Guide zeigt, was ein Cropper können muss, wie sich die üblichen Libraries unterscheiden, wie du mit dem [Image Cropper](https://wingo-ui.com/components/image-cropper) von Wingo UI ein Bild vor dem Upload zuschneidest und wie der runde Avatar-Ablauf darauf aufbaut.

## Was muss ein React Image Cropper können?

Ein Cropper, der im Produktivbetrieb besteht, beherrscht sieben Dinge:

1. **Verschieben und Zoomen** per Maus-Drag, Mausrad, Ziehen mit einem Finger und Pinch mit zwei Fingern, wobei das Foto den Rahmen immer ausfüllt.
2. **Seitenverhältnisse**: ein festes für Cover und Avatare, Presets für Galerien und ein freier Zuschnitt mit Ecken, an denen du die Größe änderst.
3. **Drehung**: 90-Grad-Drehungen für seitlich liegende Fotos und ein feiner Slider, um einen Horizont gerade zu richten.
4. **Ein Export in fester Größe**: eine exakte Breite oder eine Obergrenze für die längste Seite, in WebP, JPEG oder PNG, als `Blob`, den du in `FormData` steckst.
5. **Ein runder Rahmen** für Avatare und Logos, mit transparenten Ecken im Export.
6. **Tastaturbedienung**: Pfeiltasten zum Verschieben, Tasten zum Zoomen und Drehen und Slider, deren Wert der Screenreader vorliest.
7. **Ein Smartphone-Layout**, in dem die Toolbar auf dem Bildschirm bleibt und jedes Bedienelement eine Touch-Fläche von 44px hat.

An Punkt 4 scheitern selbst gebaute Cropper meistens. Wer ein großes Foto mit einem einzigen `drawImage`-Aufruf direkt auf 512px verkleinert, kann gezackte Kanten bekommen, und ein Canvas über dem Größenlimit des Browsers kann fehlschlagen oder leer bleiben.

## Welche React Image Crop Library soll ich nehmen?

Diese Namen fallen, wenn du nach einer React Image Crop Library suchst (oder auf Deutsch nach „React Bild zuschneiden“). Die Fakten stammen aus dem README des jeweiligen Projekts auf GitHub, Stand Oktober 2026.

| Option | Lizenz | Was du bekommst | Zoom und Drehung | Runder Zuschnitt | Export |
|---|---|---|---|---|---|
| [react-easy-crop](https://github.com/ValentinH/react-easy-crop) | MIT | Ein fester Rahmen, das Foto bewegt sich darunter | `zoom` (`maxZoom` standardmäßig 3), Zoom per Mausrad, `rotation` | `cropShape="round"` | Pixelkoordinaten über `onCropComplete`; den Canvas-Schritt schreibst du selbst |
| [react-image-crop](https://github.com/dominictobias/react-image-crop) | ISC | Ein Rahmen, den du über einem festen Bild ziehst und in der Größe änderst, ohne Abhängigkeiten, unter 5KB gzip | Nicht in der Komponente; die Helfer im README nehmen optional Skalierung und Drehung | `circularCrop` | Crop-Koordinaten; das FAQ im README zeigt die Helfer `cropToCanvas` und `cropToImg` |
| [Wingo UI Image Cropper](https://wingo-ui.com/components/image-cropper) | Pro-Lizenz | react-easy-crop plus Toolbar: Presets für Seitenverhältnisse, Slider für Zoom und Drehung, Spiegeln, Ecken für den freien Zuschnitt | Pinch, Mausrad, Buttons, `maxZoom` 4, 90-Grad-Drehungen und ein feiner Slider von −45 bis 45 Grad | `cropShape="round"` mit maskiertem Export | `toBlob`, `toCanvas` und `cropImage` mit Größe, Typ, Qualität und Maske |

react-image-crop ist die bessere Wahl, wenn Nutzer einen Rahmen über ein Dokument oder einen Screenshot ziehen sollen und wenn die Bundle-Größe am meisten zählt. react-easy-crop passt zu Fotos, denn das Bild unter einem festen Rahmen zu verschieben fühlt sich auf dem Smartphone natürlicher an. Die Komponente von Wingo UI ist react-easy-crop mit fertiger UI und fertigem Export, als Quellcode, den du ändern kannst.

## Wie kann ich in React ein Bild vor dem Upload zuschneiden?

Setz den Cropper zwischen Dateiauswahl und Upload. Der Image Cropper nimmt eine `File` als `src` (er erzeugt die Object-URL und gibt sie wieder frei), und `setApi` gibt dir ein Handle mit `toBlob`. Wer in React ein Bild vor dem Upload zuschneiden will, braucht drei Teile: ein Datei-Input, den Cropper und den Upload-Aufruf.

```bash
npx wingo-ui@latest add image-cropper
```

```tsx
"use client";

import { useRef, useState, type ChangeEvent } from "react";
import { Button } from "@/components/ui/button";
import { ImageCropper, type ImageCropperApi } from "@/components/ui/image-cropper";

export function CoverPhotoField() {
  const [file, setFile] = useState<File | null>(null);
  const [api, setApi] = useState<ImageCropperApi | null>(null);
  const [saving, setSaving] = useState(false);
  const [error, setError] = useState("");
  const inputRef = useRef<HTMLInputElement>(null);

  const onPick = (event: ChangeEvent<HTMLInputElement>) => {
    const picked = event.target.files?.[0];
    if (picked) setFile(picked);
    // dieselbe datei lässt sich erneut auswählen
    event.target.value = "";
  };

  const save = async () => {
    if (!api) return;
    setSaving(true);
    setError("");
    try {
      const blob = await api.toBlob({ type: "image/webp", quality: 0.85, maxSize: 1600 });
      const body = new FormData();
      body.append("cover", blob, "cover.webp");
      const response = await fetch("/api/listings/cover", { method: "POST", body });
      if (!response.ok) throw new Error("Der Upload ist fehlgeschlagen");
    } catch (failure) {
      setError(failure instanceof Error ? failure.message : "Der Upload ist fehlgeschlagen");
    } finally {
      setSaving(false);
    }
  };

  return (
    <div className="flex max-w-xl flex-col gap-4">
      <input ref={inputRef} type="file" accept="image/jpeg,image/png,image/webp" className="sr-only" tabIndex={-1} onChange={onPick} />
      {file && <ImageCropper src={file} defaultAspect={16 / 9} setApi={setApi} />}
      <div className="flex gap-2">
        <Button variant="soft" onClick={() => inputRef.current?.click()}>
          Foto auswählen
        </Button>
        <Button loading={saving} disabled={!file} onClick={save}>
          Cover speichern
        </Button>
      </div>
      {error && <p role="alert" className="text-sm text-destructive-soft-foreground">{error}</p>}
    </div>
  );
}
```

`defaultAspect` legt das Startverhältnis fest und lässt die Presets (Free, 1:1, 4:3, 3:4, 16:9, 9:16) verfügbar; mit `aspectPresets={false}` sperrst du das Verhältnis. `onValueChange` feuert erst am Ende einer Interaktion, nie pro Frame, und liefert einen `CropValue`: den Bereich in Pixeln des Originalbilds, Zoom, Drehung, Seitenverhältnis und Spiegelung.

> Live-Demo (Image Cropper): Zieh das Foto, zoom per Pinch oder Mausrad, dreh es mit dem Drehungs-Slider und drück dann Crop, um Abmessungen und Dateigröße des Exports zu sehen. Probier sie unter [Image Cropper](https://wingo-ui.com/components/image-cropper) aus und installier sie mit `npx wingo-ui@latest add image-cropper`.

## Wie exportiere ich aus dem Zuschnitt ein WebP in fester Größe?

Gib `toBlob` Export-Optionen mit oder setz sie einmal über die Prop `export`. `maxSize` begrenzt die längste Seite und vergrößert nie, `width` und `height` setzen eine exakte Größe, `type` wählt `image/webp`, `image/jpeg` oder `image/png`, und `quality` (0 bis 1) gilt für WebP und JPEG. Ohne Optionen ist der Export ein JPEG mit 90% Qualität, begrenzt auf 2048px.

Drei Details des Exports solltest du kennen, bevor du deinen eigenen schreibst:

- **Stufenweises Verkleinern.** Der Ausschnitt wird zuerst in einem Vielfachen der Zielgröße gezeichnet, das eine Zweierpotenz ist (nie über der Auflösung des Originals), dann Schritt für Schritt halbiert, solange die nächste Hälfte noch mindestens so groß wie das Ziel ist, und erst dann in der Endgröße gezeichnet. Ein großes Smartphone-Foto, das du mit 512px exportierst, bleibt so scharf, statt Treppenstufen zu bekommen.
- **Eine Obergrenze für das Canvas.** Das Arbeits-Canvas wird nie größer als 16.777.216 Pixel (4096 mal 4096). Ein riesiges Foto wird deshalb verkleinert, und der Export bleibt nicht leer.
- **Keine Metadaten.** Die Datei wird aus einem Canvas neu kodiert und enthält deshalb keine EXIF-Daten, womit auch der GPS-Standort wegfällt, den ein Smartphone in Fotos schreibt. Aktuelle Browser wenden die EXIF-Ausrichtung an, wenn sie das Foto zeichnen ([`image-orientation`](https://developer.mozilla.org/en-US/docs/Web/CSS/image-orientation) steht standardmäßig auf `from-image`). Vorschau und Export stimmen also überein.

JPEG kennt keine Transparenz, also füllt der Export den Hintergrund weiß, solange du kein `background` übergibst. Kann ein Browser den gewünschten Typ nicht kodieren, fällt [canvas `toBlob`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) auf PNG zurück. Lies also `blob.type`, bevor du die Datei benennst.

## React Image Crop Circle: Wie schneide ich ein Bild rund zu?

Setz `cropShape="round"`. Der Rahmen wird zum Kreis, das Seitenverhältnis wird auf 1 festgelegt, die Presets verschwinden, und der Export maskiert den Kreis standardmäßig. Wähl PNG oder WebP, damit die Ecken transparent bleiben:

```tsx
"use client";

import { useState } from "react";
import { Button } from "@/components/ui/button";
import { cropImage, ImageCropper, type CropValue } from "@/components/ui/image-cropper";

export function LogoCrop({ file, onDone }: { file: File; onDone: (blob: Blob) => void }) {
  const [crop, setCrop] = useState<CropValue | null>(null);

  const save = async () => {
    if (!crop) return;
    onDone(await cropImage(file, crop, { type: "image/png", width: 512, height: 512, round: true }));
  };

  return (
    <div className="flex flex-col gap-4">
      <ImageCropper src={file} cropShape="round" rotatable={false} onValueChange={setCrop} />
      <Button onClick={save} disabled={!crop}>
        Logo speichern
      </Button>
    </div>
  );
}
```

`cropImage` führt denselben Export ohne die Komponente aus, aus der Originaldatei und einem gespeicherten `CropValue`. Dieses Muster nimmst du, wenn du später neu zuschneiden willst: Bewahr den Original-Upload und den Crop-Wert auf, übergib den Wert wieder als `defaultValue`, wenn die Person das Bild erneut bearbeitet, und erzeug jede Größe, die du brauchst, neu.

> Live-Demo (Image Cropper): Der runde Rahmen erzwingt einen Zuschnitt im Format 1:1. Verschieb das Foto im Kreis und drück dann Crop: Das PNG behält transparente Ecken auf dem Schachbrettmuster. Probier sie unter [Image Cropper](https://wingo-ui.com/components/image-cropper) aus und installier sie mit `npx wingo-ui@latest add image-cropper`.

## Wie baue ich einen React Avatar Upload mit Zuschnitt?

Nimm [Avatar Upload](https://wingo-ui.com/components/avatar-upload), das den Cropper in den kompletten Ablauf für Profilfotos einbaut. Tippst du auf den Avatar, öffnet sich ein Menü („Choose a photo“, „Take photo“ und „Remove photo“, wenn du `onRemove` übergibst), zugeschnitten wird in einem runden Rahmen in einem Dialog, und „Save“ exportiert ein quadratisches WebP mit 512px. Das neue Bild erscheint sofort, während sich ein Ring um den [Avatar](https://wingo-ui.com/components/avatar) mit dem Upload-Fortschritt füllt. Scheitert der Upload, kommt das alte Bild zurück, und die Komponente bietet „Try again“ an.

```bash
npx wingo-ui@latest add avatar-upload
```

```tsx
"use client";

import { useState } from "react";
import { AvatarUpload } from "@/components/ui/avatar-upload";

export function ProfilePhoto({ initial }: { initial: string | null }) {
  const [avatar, setAvatar] = useState<string | null>(initial);

  return (
    <AvatarUpload
      name="Emma Carter"
      value={avatar}
      onValueChange={setAvatar}
      outputSize={512}
      outputType="image/webp"
      onUpload={(blob, { signal, onProgress }) =>
        new Promise<string>((resolve, reject) => {
          const request = new XMLHttpRequest();
          // fetch meldet keinen upload-fortschritt, XMLHttpRequest schon
          request.upload.onprogress = (event) => {
            if (event.lengthComputable) onProgress(event.loaded / event.total);
          };
          request.onload = () =>
            request.status < 300
              ? resolve((JSON.parse(request.responseText) as { url: string }).url)
              : reject(new Error("Der Upload ist fehlgeschlagen"));
          request.onerror = () => reject(new Error("Die Verbindung ist abgebrochen"));
          request.onabort = () => reject(new DOMException("Aborted", "AbortError"));
          signal.addEventListener("abort", () => request.abort());
          const body = new FormData();
          body.append("avatar", blob, "avatar.webp");
          request.open("POST", "/api/profile/avatar");
          request.send(body);
        })
      }
      onRemove={() => fetch("/api/profile/avatar", { method: "DELETE" })}
    />
  );
}
```

Löst du das Promise mit der gespeicherten URL auf, wird sie zum neuen Wert. Ohne Aufrufe von `onProgress` dreht sich der Ring, statt sich zu füllen. Das `signal` meldet einen Abbruch, wenn die Komponente unmountet oder ein neuerer Upload startet. `variant="row"` ergänzt neben dem Avatar die Buttons „Change photo“ und „Remove“ für eine Einstellungsliste, und `variant="dropzone"` ist eine gestrichelte Kachel für Firmenlogos. `crop={false}` überspringt den Dialog und lädt das größte zentrierte Quadrat hoch. Über `cropperProps` stellst du den Zuschneide-Schritt ein, zum Beispiel mit `{ flippable: true }`.

> Live-Demo (Avatar Upload): Tipp auf den Avatar, wähl Choose a photo, schneid das Bild im runden Rahmen zu und drück Save: Der Ring füllt sich, während der Upload läuft. Probier sie unter [Avatar Upload](https://wingo-ui.com/components/avatar-upload) aus und installier sie mit `npx wingo-ui@latest add avatar-upload`.

## Wie funktioniert das Zuschneiden auf dem Smartphone und mit der Tastatur?

Auf dem Smartphone verschiebt ein Finger das Foto und ein Pinch zoomt. Die Arbeitsfläche ist auf 60% der Bildschirmhöhe begrenzt, damit die Toolbar sichtbar bleibt, die Toolbar stapelt sich, und die Presets für Seitenverhältnisse werden zu einer scrollbaren Chip-Leiste mit 44px großen Touch-Zielen. In Avatar Upload werden Quellen-Menü, Zuschneide-Schritt und Kamera unter 768px zu Bottom Sheets (`responsive`, standardmäßig an), nach derselben Regel wie im [Guide zum responsiven Dialog auf dem Smartphone](https://wingo-ui.com/de/blog/shadcn-responsive-dialog-on-mobile). „Take photo“ öffnet auf Smartphones die Frontkamera über `capture="user"` und am Desktop einen Dialog mit Live-Kamera.

Mit der Tastatur springst du per Tab zum Zuschneide-Rahmen: Pfeiltasten verschieben das Foto um 10px (Shift plus Pfeiltaste um 2px), `+` und `-` zoomen, `r` dreht um 90 Grad nach links, `f` spiegelt, wenn `flippable` an ist, und `0` setzt zurück. Screenreader lesen die Werte der Slider für Zoom und Drehung als 150% oder −12 Grad vor, und Wechsel des Seitenverhältnisses, Drehungen und Zurücksetzen werden angesagt. Alle Texte, auch die Ansagen, stecken in `labels`; was du dort übergibst, wird über `DEFAULT_IMAGE_CROPPER_LABELS` gelegt.

## Welche typischen Fehler gibt es bei einem Image Cropper?

- **Fremde Bilder ohne CORS.** Die Vorschau funktioniert, aber `toBlob` schlägt mit `labels.corsError` fehl, weil das Canvas „tainted“ ist. Schneid die `File` aus dem Input zu oder liefer das Bild mit CORS-Headern aus.
- **HEIC-Fotos vom iPhone.** Avatar Upload akzeptiert standardmäßig JPEG, PNG und WebP. Safari wandelt HEIC beim Auswählen in JPEG um, wenn JPEG in `accept` steht; in anderen Browsern fällt eine HEIC-Datei durch die Typprüfung, und die Person sieht `labels.wrongType`.
- **Das Original hochladen.** Schickst du die ausgewählte Datei und schneidest erst bei der Anzeige per CSS zu, lädt jeder Besucher das volle Foto herunter. Schick den Export.
- **Dem Client vertrauen.** Der Export im Browser ist eine Komfortfunktion. Prüf Typ, Größe und Abmessungen auf dem Server noch einmal.
- **Auf jeden Frame reagieren.** `onCropChange` feuert bei jedem Frame, solange sich der Ausschnitt bewegt. Speicher bei `onValueChange`, das einmal pro Interaktion feuert.

Für die Dateiauswahl selbst (Drag and Drop, Einfügen, Prüfung von Typ und Größe, eine Upload-Warteschlange) kombiniert der [Guide zum React File Upload](https://wingo-ui.com/de/blog/react-file-upload) den [File Upload](https://wingo-ui.com/components/file-upload) mit diesem Cropper, und das [Tutorial zur React-Einstellungsseite](https://wingo-ui.com/de/blog/react-settings-page) zeigt, wo eine Zeile für das Profilfoto auf einer kompletten Einstellungsseite sitzt.

## Wie baue ich es in mein Projekt ein?

Führ `npx wingo-ui@latest add image-cropper` oder `npx wingo-ui@latest add avatar-upload` aus. Die CLI kopiert den Quellcode nach `components/ui`, ergänzt die Komponenten und Helfer, die er importiert, und installiert die nötigen npm-Pakete (`react-easy-crop`, `motion` und `lucide-react` für den Cropper). Beide sind Pro-Komponenten ([Preise](https://wingo-ui.com/de/pricing)). Weitere Deep Dives zu einzelnen Komponenten findest du in der [Kategorie Komponenten-Guides](https://wingo-ui.com/de/blog/category/components), und der [Guide zu React-Formularkomponenten](https://wingo-ui.com/de/blog/react-form-components-guide) behandelt die Regeln für Label, Fehler und Touch, die jedes Feld in einem Profilformular teilt.

## Komponenten in diesem Artikel

- [Image Cropper](https://wingo-ui.com/components/image-cropper) (Pro): A React image cropper for photos before upload: aspect presets, pinch or wheel zoom, rotation, a thirds grid and a JPEG, PNG or WebP export. Installation: `npx wingo-ui@latest add image-cropper`
- [Avatar Upload](https://wingo-ui.com/components/avatar-upload) (Pro): A React avatar upload for profile pictures: pick or shoot a photo, crop it in a round frame and watch a ring fill while it uploads. Installation: `npx wingo-ui@latest add avatar-upload`

## Häufige Fragen

### Welche React Image Crop Library ist die beste?

react-easy-crop ist die übliche Wahl, wenn du Verschieben, Pinch-Zoom und Drehung brauchst, react-image-crop, wenn du einen kleinen Zuschneide-Rahmen ohne Abhängigkeiten willst, dessen Größe man an den Kanten ändert. Beide liefern Crop-Koordinaten und überlassen das fertige Bild dir oder einem Helfer; eine Quellcode-Komponente wie der Image Cropper von Wingo UI ergänzt react-easy-crop um Toolbar und Export.

### Wie schneide ich ein Bild in React rund zu?

Zeig einen runden Rahmen an (cropShape="round" in react-easy-crop und im Image Cropper von Wingo UI, circularCrop in react-image-crop) und maskier den Export mit einem runden Clip auf dem Canvas. Exportier PNG oder WebP, damit die Ecken transparent bleiben, denn JPEG hat keinen Alphakanal.

### Schneide ich Bilder besser im Client oder auf dem Server zu?

Schneid im Client zu, damit die Person das Ergebnis sieht und der Upload klein bleibt, und prüf Typ, Größe und Abmessungen auf dem Server noch einmal. Wenn du die Originaldatei und die Crop-Koordinaten aufbewahrst, kannst du später neu zuschneiden, ohne die Person erneut zu fragen.

### Warum scheitert der Export eines zugeschnittenen Bildes von einer fremden URL?

Ein Bild von einer anderen Origin ohne CORS-Header macht das Canvas „tainted“, und toBlob wirft dann einen SecurityError. Liefer das Bild mit Access-Control-Allow-Origin aus, leite es über deine eigene Domain oder schneid statt einer URL die Datei aus dem Input zu.

### Ist der Image Cropper von Wingo UI kostenlos?

Nein. Image Cropper und Avatar Upload sind Pro-Komponenten. Die Avatar-Komponente, in der Avatar Upload das Bild anzeigt, ist kostenlos.

---

Quelle: https://wingo-ui.com/de/blog/react-image-cropper
