Per scattare e modificare foto in un'app Capacitor, usa @capacitor/camera 8.1 o versioni successive: Camera.takePhoto() apre la fotocamera, editable: 'in-app' aggiunge un passaggio di ritaglio e rotazione, e chooseFromGallery() seleziona le foto esistenti tramite il picker di sistema. Per una schermata fotocamera personalizzata con i tuoi controlli, usa @capgo/camera-preview. Questa guida copre acquisizione, editing, permessi, compressione, metadati, salvataggio e upload, con code per Capacitor 8.
Scegli lo strumento giusto
| Ti servono | Plugin | Perché |
|---|---|---|
| Scatta una foto con l'interfaccia fotocamera di sistema | @capacitor/camera takePhoto |
Nessuna UI personalizzata da mantenere, funziona ovunque |
| Consenti all'utente di ritagliare o ruotare | @capacitor/camera editable, editPhoto, editURIPhoto |
Editor in-app integrato su iOS e Android |
| Seleziona una o più foto o video | @capacitor/camera chooseFromGallery |
Usa Android Photo Picker e PHPicker, senza permessi ampi |
| Schermata fotocamera personalizzata (overlay, cornice documento, guida volto) | @capgo/camera-preview |
Anteprima nativa gestibile via HTML e JS |
| Sfoglia l'intera libreria nella tua griglia | @capgo/capacitor-photo-library |
Accesso paginato alla libreria con miniature |
| Riduci, ridimensiona o converti HEIC | @capgo/capacitor-file-compressor |
Encoder nativo, rimuove i metadati EXIF |
| Upload affidabile di foto pesanti | @capgo/capacitor-uploader |
Upload nativo in background con progresso |
Installa e configura il plugin Camera
bun add @capacitor/camera
bunx cap sync
Assicurati di avere la versione 8.1 o superiore. I metodi in questa guida (takePhoto, chooseFromGallery, editPhoto, editURIPhoto) sono stati aggiunti nella 8.1, e CameraErrorCode nella 8.2. I vecchi getPhoto e pickImages sono deprecati.
iOS
Aggiungi queste chiavi a ios/App/App/Info.plist. Apple rifiuta i build con stringhe vaghe, quindi spiega il motivo reale:
<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
La cattura e la selezione dalla galleria non richiedono permessi. Il plugin avvia l'attività della fotocamera di sistema e utilizza Android Photo Picker sui dispositivi Android 11+ che lo supportano, con fallback su ACTION_OPEN_DOCUMENT altrimenti. Per ottenere il picker backportato su dispositivi più vecchi con Google Play services, aggiungi questo dentro <application> nella AndroidManifest.xml. Lo snippet usa lo tools namespace, quindi aggiungi anche xmlns:tools="http://schemas.android.com/tools" all'elemento radice <manifest> se non è già presente (un nuovo progetto Capacitor dichiara solo 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>
Solo le saveToGallery: true richiede permessi di archiviazione, e solo su vecchie versioni di Android:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
Evita di aggiungere READ_MEDIA_IMAGES a meno che lo scopo principale della tua app non sia una galleria o un editor. La policy di Google Play sui permessi per foto e video impone alle app che hanno bisogno di foto solo occasionalmente di usare il picker, e le build che dichiarano il permesso senza giustificazione vengono rifiutate.
Scatta una foto
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;
}
}
Quello che ottieni è un MediaResult:
uri: URI file nativo dell'immagine completa (non su web). Passalo a Filesystem, a un uploader o aeditURIPhoto.webPath: un URL che la WebView può caricare. Usalo per le anteprime invece di base64.thumbnail: una miniatura base64 su nativo, l'immagine completa su web.metadata: dimensione, formato, risoluzione, data di creazione ed EXIF quandoincludeMetadataè true.saved: sesaveToGalleryha avuto successo.
targetWidth e targetHeight devono essere impostati insieme. Sono il modo più economico per evitare di fare l'upload di foto da 12 MP quando serve solo un'immagine da 2048 pixel.
Modifica la foto: ritaglia e ruota
Modifica subito dopo lo scatto
Imposta editable e l'utente si ritrova in un editor prima che la promise si risolva:
const photo = await Camera.takePhoto({
quality: 85,
editable: 'in-app', // 'in-app' | 'external' | 'no'
});
'external' passa l'immagine a un altro editor installato su Android (ad esempio Google Photos) e, in alternativa, usa l'editor in-app. iOS non ha editing esterno e usa sempre 'in-app'.
Modifica una foto esistente
Per modificare una foto selezionata o una salvata in precedenza, passa il suo URI:
const edited = await Camera.editURIPhoto({
uri: photo.uri!,
saveToGallery: false,
includeMetadata: false,
});
Se hai solo base64 (ad esempio da un canvas o da una risposta del server), usa editPhoto. L'input deve essere base64 grezzo senza data: prefisso:
const { outputImage } = await Camera.editPhoto({ inputImage: base64 });
img.src = `data:image/jpeg;base64,${outputImage}`;
Nessuno dei due metodi è disponibile sul web. Se hai bisogno di filtri, sticker, testo o disegno libero, l'editor integrato non basta. Renderizza l'immagine in un <canvas> e applica le modifiche lì, oppure usa un cropper JavaScript come Cropper.js per rapporti di aspetto fissi come gli avatar. Un export canvas (canvas.toBlob(cb, 'image/jpeg', 0.85)) ti fornisce un Blob che puoi uploadare direttamente.
Seleziona foto dalla galleria
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);
Per una singola foto, allowMultipleSelection: false più editable: 'in-app' ti dà selezione e ritaglio in un'unica chiamata. Imposta mediaType: MediaTypeSelection.All per accettare anche i video.
Su iOS, gli utenti possono concedere un accesso limitato. pickLimitedLibraryPhotos() permette loro di modificare la selezione e getLimitedLibraryPhotos() restituisce ciò che vedi. Con il picker di sistema raramente ne hai bisogno, perché gli elementi selezionati sono sempre leggibili.
Creare la tua griglia per la galleria
Se la tua app mostra la libreria dell'utente all'interno della tua UI (un'app di stampa foto, un'app di backup), usa @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
}
Lo stesso plugin pickMedia() apre il picker di sistema e copia la selezione nella cache, se vuoi un plugin unico per entrambi i flussi. Ricorda la policy di Google Play sopra prima di richiedere l'accesso completo alla libreria su Android.
Creare una schermata fotocamera personalizzata
L'UI della fotocamera di sistema non può mostrare un riquadro per carte d'identità, un contorno per documenti o il tuo brand. @capgo/camera-preview rende il feed nativo della fotocamera e ti permette di disegnare l'UI in HTML:
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();
Con toBack: true, la preview è dietro la WebView, quindi la pagina e ogni elemento genitore devono avere uno sfondo trasparente mentre la fotocamera è attiva. Un pattern comune è un camera-active classe su body che imposta background: transparent e nasconde il resto dell'app. Aggiungi NSCameraUsageDescription su iOS, mentre su Android il plugin richiede il CAMERA permesso runtime tramite requestPermissions(). Chiama sempre stop() quando la schermata si chiude per rilasciare la fotocamera.
Visualizza, comprimi e converti
Usa webPath (o Capacitor.convertFileSrc(path) per i percorsi di altri plugin) per mostrare le immagini. Evita il base64 in <img> tag per foto a piena dimensione: un JPEG da 4 MB diventa una stringa da 5,3 MB nella memoria JS e attraversa il bridge.
Prima di caricare, riduci l'immagine:
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',
});
Il compressore accetta input HEIC da iPhone, quindi la stessa chiamata converte HEIC in JPEG per i backend che non leggono HEIC. Scrive il risultato in una directory temporanea, quindi spostalo con @capacitor/filesystem se ti serve conservarla.
Metadati EXIF e privacy della posizione
Con includeMetadata: true, metadata.exif contiene il blocco EXIF della foto. Le foto dalla galleria possono includere coordinate GPS. Due regole per evitare problemi:
- Rimuovi la posizione prima di caricare foto che diventano pubbliche (foto profilo, annunci di marketplace).
@capgo/capacitor-file-compressorrimuove i metadati EXIF su tutte le piattaforme durante la compressione. - Se ti serve la posizione (rapporti di campo, ispezioni), leggila dai metadati EXIF prima di comprimere e inviala come campo separato. Poi dichiarala nell'etichetta privacy dell'App Store e nel modulo Data safety di Play. Consulta la nostra guida al privacy manifest per la parte iOS.
Salva in galleria o nello storage dell'app
saveToGallery: true su takePhoto oppure editURIPhoto scrive l'immagine nella galleria e imposta saved sul risultato. Per tenere una foto privata nella tua app, copiala nello storage dell'app:
import { Filesystem, Directory } from '@capacitor/filesystem';
await Filesystem.copy({
from: photo.uri!,
to: `photos/${Date.now()}.jpg`,
toDirectory: Directory.Data,
});
I file nella directory cache possono essere rimossi dal sistema operativo in qualsiasi momento, quindi non conservare riferimenti a lungo termine a questi file.
Fai l'upload della foto
Per le foto piccole, fetch con FormData è sufficiente:
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 });
Per file grandi o reti instabili, usa un uploader nativo che continua anche quando l'app è in background:
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);
});
Esempio: flusso foto profilo
- Mostra un action sheet con “Scatta foto” e “Scegli dalla libreria”.
- Chiama
takePhotooppurechooseFromGalleryconeditable: 'in-app',targetWidth: 1024,targetHeight: 1024. - Ritaglia in un quadrato su un canvas se hai bisogno di un rapporto 1:1 garantito.
- Comprimi con
quality: 0.8e EXIF rimosso. - Carica, poi mostra l'URL del server, non il file locale, in modo che l'avatar sopravviva ai reinstall.
Risoluzione problemi
L'app va in crash su iOS quando si apre la fotocamera: manca una chiave di descrizione utilizzo in Info.plist.
La foto è ruotata su Android: imposta correctOrientation: true (il predefinito) ed evita di leggere i pixel grezzi senza applicare l'orientamento EXIF.
L'app si riavvia dopo aver scattato una foto su dispositivi Android con poca memoria: Android potrebbe terminare la tua activity mentre la fotocamera è aperta. Ascolta appRestoredResult da @capacitor/app per recuperare il risultato dopo il riavvio.
webPath non carica: hai salvato il webPath e lo hai riutilizzato dopo un riavvio. Mantieni il uri o un percorso Filesystem e convertilo di nuovo con Capacitor.convertFileSrc.
L'anteprima della fotocamera mostra uno schermo nero: un elemento sopra la WebView ha ancora un colore di sfondo, oppure start() è stata chiamata prima che il permesso venisse concesso.
Play Console rifiuta la build per i permessi foto: rimuovi READ_MEDIA_IMAGES e READ_MEDIA_VIDEO a meno che tu non abbia dichiarato che la tua app necessita di un accesso esteso.
Rilascia fix senza passare dallo store
Dimensioni delle immagini, livelli di compressione, rapporti di ritaglio ed endpoint di upload risiedono nel tuo code web. Con Live updates di Capgo puoi modificarli per ogni utente senza aspettare la revisione, mentre le modifiche native come i nuovi plugin passano da una build normale. Se ti servono build iOS senza un Mac, Build Capgo gestisce firma e upload con Xcode 26, che App Store Connect richiede da aprile 2026.