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 odereditURIPhoto.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, wennincludeMetadatatrue ist.saved: obsaveToGalleryerfolgreich 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.
Fotos aus der Galerie auswählen
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.
Eigene Galerie-Gitter erstellen
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:
- Standortdaten vor dem Upload von Fotos entfernen, die öffentlich werden (Profilbilder, Marktplatz-Einträge).
@capgo/capacitor-file-compressorentfernt EXIF bei der Kompression auf allen Plattformen. - 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.
In Galerie oder App-Speicher speichern
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
- Zeige ein Action Sheet mit „Foto aufnehmen“ und „Aus Galerie auswählen“ an.
- Rufe
takePhotooderchooseFromGallerymiteditable: 'in-app',targetWidth: 1024,targetHeight: 1024. - Beschneide auf einem Canvas zu einem Quadrat, wenn du ein garantiertes 1:1-Verhältnis brauchst.
- Komprimiere mit
quality: 0.8und entferne EXIF. - 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.