Câmera com Capacitor em apps Ionic: captura, galeria e exibição

Capítulo 14

Tempo estimado de leitura: 8 minutos

+ Exercício

O que vamos fazer com a Câmera no Ionic (Capacitor)

No Ionic com Capacitor, o acesso à câmera e à galeria é feito pelo plugin @capacitor/camera. Ele permite: (1) capturar uma foto com a câmera, (2) escolher uma imagem da galeria, e (3) receber o resultado em diferentes formatos (URI do arquivo, base64 ou Data URL). A escolha do formato impacta diretamente desempenho, consumo de memória e como você exibe/salva a imagem.

  • URI (recomendado): retorna um caminho/URL local para o arquivo. Melhor para performance e para armazenar referência.
  • Base64/Data URL: útil para enviar para API rapidamente, mas pode ficar pesado e causar travamentos em imagens grandes.

Instalação do plugin e sincronização

1) Instale o plugin

npm i @capacitor/camera

2) Sincronize com as plataformas

npx cap sync

Se você já tem o projeto com Android/iOS adicionados, o sync atualiza as dependências nativas. Em seguida, recompile o app no dispositivo/emulador.

Permissões: como funcionam e como tratar

Em geral, o plugin solicita permissões automaticamente quando necessário. Ainda assim, é importante: (1) checar permissões antes de tentar abrir câmera/galeria, (2) lidar com o usuário negando, e (3) oferecer um caminho de recuperação (abrir configurações do app).

Checando e solicitando permissões

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

async function ensureCameraPermissions() {
  const perms = await Camera.checkPermissions();
  // perms.camera e perms.photos podem existir dependendo da plataforma

  const needsRequest =
    perms.camera !== 'granted' ||
    (perms.photos && perms.photos !== 'granted');

  if (needsRequest) {
    const requested = await Camera.requestPermissions({ permissions: ['camera', 'photos'] });
    return requested;
  }

  return perms;
}

UX recomendada: antes de pedir permissão, explique em uma mensagem curta por que você precisa dela (ex.: “Precisamos da câmera para você tirar sua foto de perfil”). Se o usuário negar, mostre uma ação para abrir as configurações.

Abrindo configurações do app (quando negado)

import { App } from '@capacitor/app';

async function openAppSettings() {
  await App.openSettings();
}

Captura e seleção: opções comuns (qualidade, fonte, formato)

A função principal é Camera.getPhoto(). Você define a fonte (câmera ou galeria), a qualidade e o formato de retorno.

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

Baixar o aplicativo

Opções mais usadas

  • source: CameraSource.Camera (captura) ou CameraSource.Photos (galeria) ou CameraSource.Prompt (pergunta ao usuário).
  • quality: 0 a 100. Valores entre 60 e 85 costumam equilibrar bem.
  • resultType: CameraResultType.Uri (recomendado), Base64 ou DataUrl.
  • allowEditing: permite edição/corte em alguns dispositivos (comportamento varia).
  • width/height: redimensiona para reduzir peso (ótimo para avatares).

Exemplo: pedir ao usuário câmera ou galeria e retornar URI

import {
  Camera,
  CameraResultType,
  CameraSource,
  Photo
} from '@capacitor/camera';

async function pickOrTakePhoto(): Promise<Photo> {
  return Camera.getPhoto({
    source: CameraSource.Prompt,
    quality: 80,
    resultType: CameraResultType.Uri,
    allowEditing: false
  });
}

Exibindo a imagem no app (URI, base64 e WebView)

Para exibir a imagem em um <img>, você precisa de uma string que o WebView consiga renderizar. Com resultType: Uri, o retorno traz propriedades como webPath (muito útil no Ionic) e path (caminho nativo).

Template (Angular) com preview

<ion-content class="ion-padding">
  <ion-button expand="block" (click)="onSelectPhoto()">
    Escolher/Tirar foto
  </ion-button>

  <ion-card *ngIf="photoWebPath">
    <img [src]="photoWebPath" alt="Prévia" />
    <ion-card-content>
      <p>Prévia da imagem selecionada.</p>
    </ion-card-content>
  </ion-card>
</ion-content>

Componente (TypeScript) usando webPath

import { Component } from '@angular/core';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';

@Component({
  selector: 'app-photo',
  templateUrl: './photo.page.html'
})
export class PhotoPage {
  photoWebPath?: string;
  photoPath?: string;

  async onSelectPhoto() {
    try {
      const photo: Photo = await Camera.getPhoto({
        source: CameraSource.Prompt,
        quality: 80,
        resultType: CameraResultType.Uri
      });

      // webPath é ideal para exibir no WebView
      this.photoWebPath = photo.webPath ?? undefined;

      // path pode ser útil para persistência nativa (quando disponível)
      this.photoPath = photo.path ?? undefined;
    } catch (err) {
      // Tratamento detalhado mais abaixo
      console.error('Falha ao obter foto', err);
    }
  }
}

Dica prática: se você só precisa mostrar a imagem na tela, use webPath. Se você precisa guardar referência local para reabrir depois, avalie salvar o arquivo e guardar um caminho estável (ver seção de persistência).

Quando usar Base64/Data URL (e cuidados de performance)

Base64 ou Data URL pode ser conveniente para enviar a imagem em um JSON para uma API, mas aumenta o tamanho em ~33% e pode consumir muita memória. Se optar por base64, prefira reduzir quality e definir width/height.

Exemplo: obter base64 para upload

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

async function getPhotoBase64() {
  const photo = await Camera.getPhoto({
    source: CameraSource.Photos,
    quality: 70,
    width: 1024,
    resultType: CameraResultType.Base64
  });

  // photo.base64String contém apenas o base64 (sem prefixo data:)
  return photo.base64String;
}

Exemplo: Data URL direto para preview

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

async function getPhotoDataUrlForPreview() {
  const photo = await Camera.getPhoto({
    source: CameraSource.Camera,
    quality: 70,
    resultType: CameraResultType.DataUrl
  });

  return photo.dataUrl; // já vem no formato data:image/...;base64,...
}

Armazenando referência local (quando necessário)

Se você precisa que a foto continue disponível após fechar o app, o ideal é salvar uma cópia em um diretório do app e guardar apenas a referência (caminho/identificador). Em muitos casos, a URI retornada pela galeria pode mudar ou ficar inacessível dependendo de permissões e limpeza do sistema.

Uma abordagem comum é: (1) obter a foto como URI, (2) baixar os bytes via fetch(photo.webPath), (3) escrever em Filesystem e (4) guardar o caminho salvo.

Salvando no Filesystem do app

import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import { Filesystem, Directory } from '@capacitor/filesystem';

async function savePhotoToAppDirectory() {
  const photo = await Camera.getPhoto({
    source: CameraSource.Prompt,
    quality: 80,
    resultType: CameraResultType.Uri
  });

  if (!photo.webPath) throw new Error('webPath não disponível para salvar');

  const response = await fetch(photo.webPath);
  const blob = await response.blob();
  const base64 = await blobToBase64(blob);

  const fileName = `photo_${Date.now()}.jpeg`;

  const saved = await Filesystem.writeFile({
    path: fileName,
    data: base64,
    directory: Directory.Data
  });

  // saved.uri é uma URI do Filesystem
  return { fileName, uri: saved.uri };
}

function blobToBase64(blob: Blob): Promise<string> {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onerror = reject;
    reader.onload = () => {
      const dataUrl = reader.result as string;
      // remove o prefixo "data:*/*;base64,"
      resolve(dataUrl.split(',')[1]);
    };
    reader.readAsDataURL(blob);
  });
}

Depois de salvar, você pode persistir fileName ou saved.uri no armazenamento local e reconstruir a exibição quando necessário.

Exibindo um arquivo salvo

Para exibir um arquivo salvo no Filesystem, você pode ler o arquivo e montar um Data URL (simples, mas pode ser pesado), ou converter a URI para algo exibível no WebView. Uma forma direta para casos pequenos (ex.: avatar) é ler e montar Data URL:

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

async function loadSavedPhotoAsDataUrl(fileName: string) {
  const file = await Filesystem.readFile({
    path: fileName,
    directory: Directory.Data
  });

  // file.data é base64
  return `data:image/jpeg;base64,${file.data}`;
}

Tratamento de erro e cancelamento pelo usuário (UX adequada)

Ao chamar Camera.getPhoto(), o usuário pode cancelar o fluxo (fechar câmera, voltar, cancelar seletor). Isso não deve ser tratado como erro “grave”: o app deve simplesmente manter o estado anterior e, se necessário, mostrar uma mensagem discreta.

Padrão de tratamento: loading, cancelamento e falhas

import { Component } from '@angular/core';
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
import { ToastController, LoadingController } from '@ionic/angular';

@Component({
  selector: 'app-photo',
  templateUrl: './photo.page.html'
})
export class PhotoPage {
  photoWebPath?: string;

  constructor(
    private toastCtrl: ToastController,
    private loadingCtrl: LoadingController
  ) {}

  async onSelectPhoto() {
    const loading = await this.loadingCtrl.create({ message: 'Abrindo câmera/galeria...' });
    await loading.present();

    try {
      const photo = await Camera.getPhoto({
        source: CameraSource.Prompt,
        quality: 80,
        resultType: CameraResultType.Uri
      });

      this.photoWebPath = photo.webPath ?? undefined;
    } catch (err: any) {
      // Cancelamento costuma cair aqui em algumas plataformas/versões
      const msg = (err?.message || '').toLowerCase();
      const isCancel = msg.includes('cancel') || msg.includes('canceled') || msg.includes('user cancelled');

      if (!isCancel) {
        const toast = await this.toastCtrl.create({
          message: 'Não foi possível obter a foto. Verifique permissões e tente novamente.',
          duration: 2500,
          position: 'bottom'
        });
        await toast.present();
        console.error('Erro ao obter foto:', err);
      }
      // Se cancelou, não faz nada: mantém UX limpa
    } finally {
      await loading.dismiss();
    }
  }
}

Boas práticas de UX para câmera/galeria

  • Evite loops de permissão: se o usuário negar permanentemente, mostre uma explicação e um botão “Abrir configurações”.
  • Mostre feedback: use ion-loading ao abrir o fluxo e ao processar/salvar a imagem.
  • Prévia e ação clara: após selecionar, mostre preview e botões como “Usar esta foto” e “Trocar”.
  • Reduza tamanho: para avatar, use width/quality menores para evitar travamentos.

Resumo rápido: qual formato escolher?

ObjetivoConfiguração sugeridaPor quê
Exibir preview na telaresultType: Uri e usar photo.webPathLeve e direto no WebView
Enviar para API (simples)resultType: Base64 (com quality/width)Fácil de embutir no payload
Persistir localmenteresultType: Uri + salvar cópia no FilesystemReferência estável e controle do app

Agora responda o exercício sobre o conteúdo:

Ao escolher o formato de retorno da foto no plugin @capacitor/camera, qual abordagem é mais indicada quando o objetivo é exibir um preview no app com bom desempenho?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

Para preview, Uri costuma ser a melhor opção: a foto vem como caminho/URL e o photo.webPath é ideal para renderização no WebView, com menor custo de memória do que Base64/Data URL.

Próximo capitúlo

Geolocalização com Capacitor no Ionic: coordenadas, mapas e boas práticas

Arrow Right Icon
Capa do Ebook gratuito Ionic para Iniciantes: aplicativos híbridos com HTML, CSS e TypeScript
67%

Ionic para Iniciantes: aplicativos híbridos com HTML, CSS e TypeScript

Novo curso

21 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.