Zum Hauptinhalt springen

Fotos in einer Capacitor App aufnehmen und bearbeiten

Fotos in einer Capacitor App aufnehmen und bearbeiten mit dem Camera 8 API: Capture, Galerie-Auswahl, In-App-Crop, Live-Preview, Komprimierung, EXIF und Uploads.

Ein Foto mit der Kamera in einer Capacitor-App aufnehmen und bearbeiten

To take and edit photos in a Capacitor app, use @capacitor/camera 8.1 oder höher: Camera.takePhoto() öffnet die Kamera, editable: 'in-app' fügt Zuschneiden und Rotieren hinzu und chooseFromGallery() wählt vorhandene Fotos über den System-Picker aus. Für einen eigenen Kamera-Screen mit eigenen Steuerelementen nutze @capgo/camera-preview. Dieser Guide behandelt Aufnahme, Bearbeitung, Berechtigungen, Kompression, Metadaten, Speichern und Upload – mit code für Capacitor 8.

Wähle das richtige Tool

Benötigt Plugin Warum
Foto mit der System-Kamera-UI aufnehmen @capacitor/camera takePhoto Keine eigene UI zu pflegen, funktioniert überall
Nutzer können zuschneiden oder rotieren @capacitor/camera editable, editPhoto, editURIPhoto Integrierter In-App-Editor für iOS und Android
Ein oder mehrere Fotos oder Videos auswählen @capacitor/camera chooseFromGallery Nutzt den Android Photo Picker und PHPicker, ohne breite Berechtigungen
Eigener Kamera-Bildschirm (Overlays, Ausweisrahmen, Gesichtsführung) @capgo/camera-preview Natives Preview, das du per HTML und JS steuerst
Gesamte Bibliothek in deinem eigenen Grid durchsuchen @capgo/capacitor-photo-library Bibliothek mit Paginierung und Thumbnails
Verkleinern, neu skalieren oder HEIC konvertieren @capgo/capacitor-file-compressor Nativer Encoder, entfernt EXIF
Große Fotos zuverlässig hochladen @capgo/capacitor-uploader Natives Hintergrund-Upload mit Fortschritt

Camera-Plugin installieren und konfigurieren

bun add @capacitor/camera
bunx cap sync

Stelle sicher, dass du Version 8.1 oder neuer hast. Die Methoden in diesem Guide (takePhoto, chooseFromGallery, editPhoto, editURIPhoto) wurden in 8.1 hinzugefügt, und CameraErrorCode in 8.2. Die älteren getPhoto und pickImages sind veraltet.

iOS

Füge diese Keys hinzu: ios/App/App/Info.plist. Apple lehnt Builds mit vagen Strings ab, also erkläre den echten Grund:

<key>NSCameraUsageDescription</key>
<string>Take a photo for your profile or attach it to a report.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Choose existing photos to attach.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Save photos you take to your library.</string>

Android

Für das Aufnehmen und die Auswahl aus der Galerie sind keine Berechtigungen erforderlich. Das Plugin startet die Systemkamera-Aktivität und verwendet auf Android-11+-Geräten, die dies unterstützen, den Android Photo Picker, mit Fallback auf ACTION_OPEN_DOCUMENT andernfalls. Um den zurückportierten Picker auf älteren Geräten mit Google Play-Diensten zu erhalten, füge dies innerhalb von <application> in einem AndroidManifest.xmlhinzu. Das Snippet verwendet den tools Namespace, füge also auch xmlns:tools="http://schemas.android.com/tools" zum Wurzel- <manifest> Element hinzu, falls es noch nicht vorhanden ist (ein neues Capacitor-Projekt deklariert nur xmlns:android):

<service android:name="com.google.android.gms.metadata.ModuleDependencies"
    android:enabled="false"
    android:exported="false"
    tools:ignore="MissingClass">
    <intent-filter>
        <action android:name="com.google.android.gms.metadata.MODULE_DEPENDENCIES" />
    </intent-filter>
    <meta-data android:name="photopicker_activity:0:required" android:value="" />
</service>

Nur saveToGallery: true benötigt Speicherberechtigungen, und nur auf älteren Android-Versionen:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />

Vermeide das Hinzufügen von READ_MEDIA_IMAGES außer der Kernzweck deiner App ist eine Galerie oder ein Editor. Die Richtlinie von Google Play zu Foto- und Video-Berechtigungen verlangt, dass Apps mit gelegentlichem Foto-Bedarf den Picker verwenden, und Builds, die die Berechtigung ohne Begründung deklarieren, werden abgelehnt.

Foto aufnehmen

import { Camera, CameraDirection, CameraErrorCode, EncodingType } from '@capacitor/camera';

export async function takePicture() {
  try {
    const photo = await Camera.takePhoto({
      quality: 85,
      targetWidth: 2048,
      targetHeight: 2048,
      correctOrientation: true,
      encodingType: EncodingType.JPEG,
      cameraDirection: CameraDirection.Rear, // iOS and web
      includeMetadata: true,
    });

    // webPath works directly in <img src>
    document.querySelector<HTMLImageElement>('#preview')!.src = photo.webPath!;
    return photo;
  } catch (e: any) {
    if (e.code === CameraErrorCode.TakePhotoCancelled) return null;
    if (e.code === CameraErrorCode.CameraPermissionDenied) {
      // show a screen that explains how to enable the camera in Settings
      return null;
    }
    throw e;
  }
}

Zurückgegeben wird ein MediaResult:

  • uri: native Datei-URI des vollständigen Bildes (nicht im Web). Übergeben Sie sie an Filesystem, einen Uploader oder editURIPhoto.
  • webPath: eine URL, die die WebView laden kann. Verwenden Sie sie für Vorschauen statt base64.
  • thumbnail: ein base64-Thumbnail auf nativ, das vollständige Bild im Web.
  • metadata: Größe, Format, Auflösung, Erstellungsdatum und EXIF, wenn includeMetadata true ist.
  • saved: ob saveToGallery erfolgreich war.

targetWidth und targetHeight müssen zusammen gesetzt werden. Sie sind der günstigste Weg, um das Hochladen von 12-MP-Fotos zu vermeiden, wenn Sie nur ein 2048-Pixel-Bild benötigen.

Foto bearbeiten: zuschneiden und drehen

Direkt nach dem Capture bearbeiten

Setzen Sie editable und der Nutzer landet im Editor, bevor das Promise resolved wird:

const photo = await Camera.takePhoto({
  quality: 85,
  editable: 'in-app', // 'in-app' | 'external' | 'no'
});

'external' übergibt das Bild an einen anderen installierten Editor auf Android (z. B. Google Fotos) und fällt auf den In-App-Editor zurück. iOS hat keine externe Bearbeitung und verwendet immer 'in-app'.

Vorhandenes Foto bearbeiten

Um ein ausgewähltes Foto oder ein zuvor gespeichertes zu bearbeiten, übergebe seine URI:

const edited = await Camera.editURIPhoto({
  uri: photo.uri!,
  saveToGallery: false,
  includeMetadata: false,
});

Wenn du nur base64 hast (z. B. von einem Canvas oder einer Server-Antwort), nutze editPhoto. Die Eingabe muss rohes base64 ohne data: Präfix sein:

const { outputImage } = await Camera.editPhoto({ inputImage: base64 });
img.src = `data:image/jpeg;base64,${outputImage}`;

Keine der beiden Methoden ist im Web verfügbar. Wenn du Filter, Sticker, Text oder freies Zeichnen brauchst, reicht der eingebaute Editor nicht aus. Rendere das Bild in einem <canvas> und wende die Bearbeitungen dort an, oder nutze einen JavaScript-Cropper wie Cropper.js für feste Seitenverhältnisse wie Avatare. Ein Canvas-Export (canvas.toBlob(cb, 'image/jpeg', 0.85)) liefert dir einen Blob, den du direkt hochladen kannst.

import { Camera, MediaTypeSelection } from '@capacitor/camera';

const { results } = await Camera.chooseFromGallery({
  mediaType: MediaTypeSelection.Photo,
  allowMultipleSelection: true,
  limit: 5,            // Android 13+ and iOS
  quality: 85,
  targetWidth: 2048,
  targetHeight: 2048,
});

const urls = results.map((r) => r.webPath);

Für ein einzelnes Foto allowMultipleSelection: false sowie editable: 'in-app' bietet dir Pick-then-Crop in einem einzigen Aufruf. Setze mediaType: MediaTypeSelection.All um auch Videos zu akzeptieren.

Auf iOS können Benutzer eingeschränkten Zugriff gewähren. pickLimitedLibraryPhotos() ermöglicht es ihnen, die Auswahl zu ändern, und getLimitedLibraryPhotos() gibt zurück, was du sehen kannst. Mit dem System-Picker brauchst du beides selten, da ausgewählte Elemente immer lesbar sind.

Wenn deine App die Bibliothek des Benutzers in deiner eigenen UI anzeigt (z. B. eine Fotodruck-App oder eine Backup-App), verwende @capgo/capacitor-photo-library:

import { PhotoLibrary } from '@capgo/capacitor-photo-library';

const { state } = await PhotoLibrary.requestAuthorization();
if (state === 'authorized' || state === 'limited') {
  const { assets, hasMore } = await PhotoLibrary.getLibrary({
    limit: 60,
    offset: 0,
    thumbnailWidth: 256,
    thumbnailHeight: 256,
  });
  // assets[i].thumbnail?.webPath for the grid, getPhotoUrl({ id }) for full size
}

Dasselbe Plugin pickMedia() öffnet den System-Picker und kopiert die Auswahl in den Cache, falls du ein Plugin für beide Workflows möchtest. Denke an die Google-Play-Richtlinie oben, bevor du auf Android vollen Bibliothekszugriff anforderst.

Eigene Kameraansicht erstellen

Die Systemkamera-UI kann keinen Ausweisrahmen, eine Dokumentenkontur oder Ihre Marke anzeigen. @capgo/camera-preview rendert den nativen Kamerastrom und ermöglicht es, die UI in HTML zu zeichnen:

bun add @capgo/camera-preview
bunx cap sync
import { CameraPreview } from '@capgo/camera-preview';

await CameraPreview.start({
  position: 'rear',
  toBack: true,          // preview behind the WebView
  aspectRatio: '4:3',
  storeToFile: true,     // capture() returns a file path instead of base64
  disableAudio: true,
});

await CameraPreview.setFlashMode({ flashMode: 'auto' });
await CameraPreview.setZoom({ level: 2 });

const { value: filePath, exif } = await CameraPreview.capture({ quality: 85 });

await CameraPreview.stop();

Mit toBack: true, die Vorschau befindet sich hinter dem WebView, daher müssen die Seite und alle übergeordneten Elemente während der Kameraaktivität einen transparenten Hintergrund haben. Ein häufiges Muster ist ein camera-active Klasse auf body die setzt background: transparent und den Rest der App ausblendet. Füge NSCameraUsageDescription unter iOS hinzu; unter Android fordert das Plugin die CAMERA Laufzeitberechtigung über requestPermissions(). Rufe immer stop() auf, wenn der Screen geschlossen wird, um die Kamera freizugeben.

Anzeigen, komprimieren und konvertieren

Nutzt webPath (oder Capacitor.convertFileSrc(path) (für Pfade von anderen Plugins), um Bilder anzuzeigen. Vermeide Base64 in <img> Tags für Fotos in voller Größe: Ein 4 MB JPEG wird zu einem 5,3 MB String im JS-Speicher und geht über die Bridge.

Bild vor dem Upload verkleinern:

import { FileCompressor } from '@capgo/capacitor-file-compressor';

const { path } = await FileCompressor.compressImage({
  path: photo.uri!,
  width: 1600,          // keeps aspect ratio
  quality: 0.75,
  mimeType: 'image/jpeg',
});

Der Kompressor akzeptiert HEIC-Eingaben von iPhones, sodass derselbe Aufruf HEIC in JPEG konvertiert, für Backends, die HEIC nicht lesen können. Das Ergebnis wird in ein temporäres Verzeichnis geschrieben, daher mit @capacitor/filesystem verschieben, falls es behalten werden soll.

EXIF-Metadaten und Standort-Privatsphäre

Mit includeMetadata: true, metadata.exif enthält den EXIF-Block des Fotos. Fotos aus der Kamerarolle können GPS-Koordinaten enthalten. Zwei Regeln halten Sie aus der Schusslinie:

  1. Standortdaten vor dem Upload von Fotos entfernen, die öffentlich werden (Profilbilder, Marktplatz-Einträge). @capgo/capacitor-file-compressor entfernt EXIF bei der Kompression auf allen Plattformen.
  2. Wenn du Standortdaten brauchst (Feldberichte, Inspektionen), lies sie vor der Kompression aus den EXIF-Daten aus und sende sie als separates Feld. Gib sie dann im App-Store-Datenschutzlabel und im Play-Data-Safety-Formular an. Siehe unseren Privacy-Manifest-Guide für die iOS-Seite.

saveToGallery: true auf takePhoto oder editURIPhoto schreibt das Bild in die Fotogalerie und setzt saved auf das Ergebnis. Um ein Foto in deiner App privat zu halten, kopiere es in den App-Speicher:

import { Filesystem, Directory } from '@capacitor/filesystem';

await Filesystem.copy({
  from: photo.uri!,
  to: `photos/${Date.now()}.jpg`,
  toDirectory: Directory.Data,
});

Dateien im Cache-Verzeichnis können vom Betriebssystem jederzeit gelöscht werden; speichere also keine dauerhaften Referenzen darauf.

Foto hochladen

Bei kleinen Fotos fetch mit FormData reicht aus:

const blob = await fetch(photo.webPath!).then((r) => r.blob());
const form = new FormData();
form.append('file', blob, 'photo.jpg');
await fetch('https://api.example.com/photos', { method: 'POST', body: form });

Bei großen Dateien oder instabilen Netzwerken nutze einen nativen Uploader, der auch im Hintergrund weiterläuft:

import { Uploader } from '@capgo/capacitor-uploader';

const { id } = await Uploader.startUpload({
  filePath: path,
  serverUrl: 'https://api.example.com/photos',
  method: 'POST',
  mimeType: 'image/jpeg',
});

await Uploader.addListener('events', (event) => {
  console.log(event.name, event.payload);
});

Beispiel: Profilbild-Workflow

  1. Zeige ein Action Sheet mit „Foto aufnehmen“ und „Aus Galerie auswählen“ an.
  2. Rufe takePhoto oder chooseFromGallery mit editable: 'in-app', targetWidth: 1024, targetHeight: 1024.
  3. Beschneide auf einem Canvas zu einem Quadrat, wenn du ein garantiertes 1:1-Verhältnis brauchst.
  4. Komprimiere mit quality: 0.8 und entferne EXIF.
  5. Uploaden und dann die Server-URL anzeigen, nicht die lokale Datei, damit der Avatar auch nach Neuinstallationen erhalten bleibt.

Fehlerbehebung

Die App stürzt auf iOS ab, wenn die Kamera geöffnet wird: Ein Usage-Description-Key fehlt in Info.plist.

Das Foto ist auf Android gedreht: setzen correctOrientation: true (Standard) und vermeide das Lesen roher Pixel, ohne die EXIF-Orientierung anzuwenden.

Die App startet nach dem Fotografieren auf Android-Geräten mit wenig Speicher neu: Android kann deine Activity beenden, während die Kamera geöffnet ist. Höre auf appRestoredResult von @capacitor/app um das Ergebnis nach dem Neustart wiederherzustellen.

webPath wird nicht geladen: du hast das webPath und nach einem Neustart wiederverwendet. Persistiere die uri oder einen Filesystem-Pfad und konvertiere ihn erneut mit Capacitor.convertFileSrc.

Kameravorschau zeigt einen schwarzen Bildschirm: Ein Element über dem WebView hat noch eine Hintergrundfarbe, oder start() wurde aufgerufen, bevor die Berechtigung erteilt wurde.

Play Console lehnt den Build wegen Foto-Berechtigungen ab: Entferne READ_MEDIA_IMAGES und READ_MEDIA_VIDEO außer du hast eine Erklärung eingereicht, dass deine App breiten Zugriff benötigt.

Fixes veröffentlichen, ohne Store-Release

Bildgrößen, Komprimierungsstufen, Zuschneideverhältnisse und Upload-Endpunkte liegen alle in deinem Web-code. Mit Capgo Live Updates Sie können sie für jeden Benutzer ändern, ohne auf die Prüfung warten zu müssen, während native Änderungen wie neue Plugins einen regulären Build durchlaufen. Wenn Sie iOS-Builds ohne Mac benötigen, Capgo Build übernimmt das Signieren und Hochladen mit Xcode 26, was App Store Connect seit April 2026 erfordert.

Live-Updates für Capacitor-Apps

Wenn ein Bug im Web-Layer live ist, liefere den Fix über Capgo aus, statt Tage auf die App-Store-Freigabe zu warten. Nutzer bekommen das Update im Hintergrund, während native Änderungen im normalen Review-Prozess bleiben.

Neues aus unserem Blog

Capgo gibt dir die besten Insights, um eine wirklich professionelle Mobile App zu erstellen.