Lompat ke konten utama

Cara Mengambil dan Mengedit Foto di Aplikasi Capacitor

Ambil dan edit foto di aplikasi Capacitor dengan Camera 8 API: pengambilan, pilihan galeri, potong dalam aplikasi, tampilan langsung, kompresi, EXIF, dan unggah.

Bagaimana Mengambil dan Mengedit Foto di Aplikasi Capacitor

Untuk mengambil dan mengedit foto di aplikasi Capacitor, gunakan @capacitor/camera 8.1 atau lebih baru: Camera.takePhoto() membuka kamera, editable: 'in-app' menambahkan langkah potong dan putar, dan chooseFromGallery() memilih foto yang sudah ada melalui picker sistem. Untuk layar kamera yang disesuaikan dengan kontrol sendiri, gunakan @capgo/camera-preview. Panduan ini mencakup pengambilan, pengeditan, izin, kompresi, metadata, menyimpan, dan mengunggah, dengan code untuk Capacitor 8.

Pilih alat yang tepat

Perlu Plugin Mengapa
Ambil foto dengan antarmuka kamera sistem @capacitor/camera takePhoto Tidak perlu UI khusus untuk dipelihara, berfungsi di mana saja
Biarkan pengguna memotong atau memutar @capacitor/camera editable, editPhoto, editURIPhoto Pengeditan bawaan di dalam aplikasi di iOS dan Android
Pilih satu atau banyak foto atau video @capacitor/camera chooseFromGallery Gunakan Picker Foto Android dan PHPicker, tanpa izin luas yang luas
Screen kamera yang dapat disesuaikan (overlay, frame ID kartu, panduan wajah) @capgo/camera-preview Penglihatan asli yang dapat dikendalikan dari HTML dan JS
Akses perpustakaan seluruhnya di dalam grid sendiri @capgo/capacitor-photo-library Akses perpustakaan yang dipaginasi dengan thumbnail
Memampatkan, mengubah ukuran atau mengonversi HEIC @capgo/capacitor-file-compressor Encoder asli, menghilangkan EXIF
Mengunggah foto besar secara andal @capgo/capacitor-uploader Mengunggah latar belakang dengan kemajuan

Instal dan konfigurasi plugin Kamera

bun add @capacitor/camera
bunx cap sync

Pastikan Anda memiliki versi 8.1 atau lebih baru. Metode dalam panduan ini (takePhoto, chooseFromGallery, editPhoto, editURIPhoto) ditambahkan pada 8.1, dan CameraErrorCode In 8.2. Yang lebih tua getPhoto dan pickImages telah dihapus.

iOS

Tambahkan kunci-kunci ini ke ios/App/App/Info.plist. Apple menolak membangun dengan string yang tidak jelas, jadi jelaskan alasan sebenarnya:

<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

Memotret dan memilih galeri tidak memerlukan izin. Plugin ini memulai aktivitas kamera sistem dan menggunakan Pemilih Foto Android pada perangkat Android 11+ yang mendukungnya, kembali ke ACTION_OPEN_DOCUMENT untuk mendapatkan pemilih yang diperbarui pada perangkat yang lebih tua dengan layanan Google Play, tambahkan ini di dalam <application> In AndroidManifest.xml. Potongan kode ini menggunakan tools nama ruang, jadi tambahkan juga xmlns:tools="http://schemas.android.com/tools" ke root <manifest> Jika elemen tersebut belum ada (projek baru hanya mengumumkan Capacitor 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>

Hanya saveToGallery: true memerlukan izin penyimpanan, dan hanya pada versi Android lama:

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

Hindari menambahkan READ_MEDIA_IMAGES kecuali tujuan utama aplikasi Anda adalah galeri atau editor. Google Play memiliki kebijakan izin foto dan video yang memerlukan aplikasi dengan kebutuhan foto sementara menggunakan picker, dan bangun yang mengumumkan izin tanpa alasan akan ditolak.

Ambil 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;
  }
}

Apa yang Anda dapatkan adalah sebuah MediaResult:

  • uri: URI file asli native dari gambar penuh (tidak di web). Kirimkannya ke Filesystem, uploader, atau editURIPhoto.
  • webPath: sebuah URL yang dapat diakses oleh WebView. Gunakan untuk pratinjau daripada base64.
  • thumbnail: sebuah thumbnail base64 native, gambar penuh di web.
  • metadata: ukuran, format, resolusi, tanggal pembuatan dan EXIF ketika includeMetadata benar.
  • saved: apakah saveToGallery berhasil.

targetWidth dan targetHeight harus disetel bersama. Mereka adalah cara termurah untuk menghindari mengunggah foto 12 MP ketika Anda hanya membutuhkan gambar 2048 piksel.

Edit foto: potong dan putar

Edit setelah tangkapan

Set editable dan pengguna mendarat di editor sebelum janji diselesaikan:

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

'external' menghadiahkan gambar ke editor lain yang diinstal di Android (misalnya Google Photos) dan kembali ke editor di aplikasi. iOS tidak memiliki editing eksternal dan selalu menggunakan 'in-app'.

Edit foto yang sudah ada

Untuk mengedit foto yang dipilih atau yang disimpan sebelumnya, lewatkan URI-nya:

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

Jika Anda hanya memiliki base64 (misalnya dari canvas atau respons server), gunakan editPhoto. Masukan harus berupa base64 mentah tanpa prefix: data: prefix:

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

dan aplikasikan perubahan di sana, atau gunakan pemotong JavaScript seperti Cropper.js untuk rasio aspek tetap seperti avatar. Ekspor canvas ( <canvas> dan aplikasikan perubahan-perubahan tersebut di sana, atau gunakan pemotong JavaScript seperti Cropper.js untuk rasio aspek tetap seperti avatar.canvas.toBlob(cb, 'image/jpeg', 0.85)Mengembalikan Anda sebuah Blob yang dapat diunggah langsung.

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);

untuk menerima video juga. allowMultipleSelection: false plus editable: 'in-app' memberi Anda pilihan lalu potong dalam satu langkah. Tetapkan mediaType: MediaTypeSelection.All Untuk foto tunggal

Pada iOS, pengguna dapat memberikan akses terbatas. pickLimitedLibraryPhotos() biarkan mereka mengubah pilihan dan getLimitedLibraryPhotos() kembali apa yang Anda lihat. Dengan picker sistem Anda jarang perlu salah satu, karena item yang dipilih selalu dapat dibaca.

Jika aplikasi Anda menampilkan perpustakaan pengguna di dalam UI sendiri (sebuah aplikasi cetak foto, aplikasi backup), gunakan @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
}

Plugin yang sama pickMedia() membuka picker sistem dan menyalin pilihan ke cache, jika Anda ingin satu plugin untuk aliran yang sama. Ingatlah kebijakan Google Play di atas sebelum meminta akses penuh ke perpustakaan di Android.

Membangun layar kamera custom

UI kamera sistem tidak dapat menampilkan kerangka ID kartu, garis-garis dokumen, atau merek Anda. @capgo/camera-preview mengrender feed kamera native dan memungkinkan Anda menarik UI di 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();

Dengan toBack: true, preview ditempatkan di belakang WebView, sehingga halaman dan setiap elemen induk harus memiliki latar belakang transparan saat kamera aktif. Pola umum adalah camera-active kelas pada body yang mengatur background: transparent and hides the rest of the app. Add NSCameraUsageDescription di iOS, dan di Android plugin meminta CAMERA ketika layar ditutup untuk melepaskan kamera. requestPermissions()Tampilkan, kompres, dan konversi stop() ketika layar ditutup untuk melepaskan kamera.

Tampilkan, kompresi, dan konversi

Use webPath (or Capacitor.convertFileSrc(path) Untuk menampilkan gambar, hindari penggunaan base64 <img> tag untuk foto penuh ukuran: sebuah JPEG 4 MB menjadi sebuah string 5,3 MB di memori JS dan menyeberangi jembatan.

sebelum mengunggah, kecilkan gambar:

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',
});

Kompresor menerima input HEIC dari iPhone, jadi panggilan yang sama mengonversi HEIC ke JPEG untuk backend yang tidak dapat membaca HEIC. Ia menulis hasilnya ke direktori sementara, jadi pindahkanlah dengan @capacitor/filesystem jika Anda perlu menjaganya.

metadata EXIF dan privasi lokasi

Yang includeMetadata: true, metadata.exif Mengandung blok EXIF foto. Foto dari galeri kamera dapat mencakup koordinat GPS. Dua aturan ini akan menjaga Anda tetap aman:

  1. Menghilangkan lokasi sebelum mengunggah foto yang menjadi publik (foto profil, daftar marketplace). @capgo/capacitor-file-compressor Menghilangkan EXIF di semua platform sebagai bagian dari kompresi.
  2. Jika Anda membutuhkan lokasi (laporan lapangan, inspeksi), baca dari EXIF sebelum kompresi dan kirim sebagai bidang terpisah. Kemudian, beritahukan di label privasi App Store dan formulir keamanan data Play. Lihat panduan manifest privasi kami Petunjuk Privasi Simpan ke galeri atau penyimpanan aplikasi

saveToGallery: true pada takePhoto atau editURIPhoto menulis gambar ke rol kamera dan mengatur saved Membuat foto tersebut tetap pribadi untuk aplikasi Anda, salin foto tersebut ke penyimpanan aplikasi.

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

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

File di direktori cache dapat dihapus oleh sistem operasi kapan saja, jadi jangan menyimpan referensi yang berumur panjang kepadanya.

Upload the photo

Untuk foto kecil, fetch dengan FormData adalah cukup:

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 });

Untuk file besar atau jaringan yang tidak stabil, gunakan uploader native yang tetap berjalan ketika aplikasi di 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);
});

Contoh: alur foto profil

  1. Tampilkan sheet aksi dengan “Ambil foto” dan “Pilih dari galeri”.
  2. Panggil takePhoto atau chooseFromGallery with editable: 'in-app', targetWidth: 1024, targetHeight: 1024.
  3. Potong menjadi persegi dalam sebuah kanvas jika Anda membutuhkan rasio 1:1 yang dijamin.
  4. Compress dengan quality: 0.8 Mengompresi dengan
  5. Unggah terlebih dahulu, kemudian tampilkan URL server, bukan file lokal, sehingga avatar tetap bertahan setelah penginstalan ulang.

Troubleshooting

Aplikasi mengalami crash di iOS ketika kamera dibuka.Kunci deskripsi pengguna : tidak ada Info.plist.

Aplikasi mengalami crash pada iOS ketika kamera dibuka: set correctOrientation: true (default) dan hindari membaca piksel mentah tanpa menerapkan orientasi EXIF.

Aplikasi akan restart setelah mengambil foto pada perangkat Android dengan memori rendah: Android mungkin membunuh aktivitas Anda saat kamera masih terbuka. Dengarkan appRestoredResult from @capacitor/app untuk mengembalikan hasil setelah restart.

webPath : Anda menyimpandan menggunakannya lagi setelah restart. Simpan webPath atau jalur filesystem dan konversi lagi dengan uri atau sebuah path file sistem dan konversi lagi dengan Capacitor.convertFileSrc.

Tampilan kamera menampilkan layar hitam: elemen di atas WebView masih memiliki warna latar belakang, start() Play Console menolak membangun aplikasi untuk izin foto

untuk memulihkan hasil setelah restart.remove READ_MEDIA_IMAGES dan READ_MEDIA_VIDEO kecuali Anda telah mengajukan deklarasi bahwa aplikasi Anda memerlukan akses luas.

Kirim perbaikan tanpa rilis toko

Ukuran gambar, tingkat kompresi, rasio potong, dan endpoint unggah semua berada di web code. Dengan Capgo live updates you can change them for every user without waiting for review, while native changes like new plugins go through a regular build. If you need iOS builds without a Mac, Capgo Pembangunan menangani tanda tangan dan unggah dengan Xcode 26, yang App Store Connect memerlukan sejak April 2026.

Pembaruan Langsung untuk Aplikasi Capacitor

When a web-layer bug is live, ship the fix through Capgo instead of waiting days for app store approval. Users get the update in the background while native changes stay in the normal review path.

Terbaru dari Blog Kami

Capgo gives you the best insights you need to create a truly professional mobile app.