相机与相册 #
一、插件安装 #
1.1 安装依赖 #
bash
npm install @capacitor/camera
npx cap sync
1.2 权限配置 #
iOS (Info.plist):
xml
<key>NSCameraUsageDescription</key>
<string>需要访问相机来拍照</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册来选择照片</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>需要保存照片到相册</string>
Android (AndroidManifest.xml):
xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
二、基本使用 #
2.1 拍照 #
typescript
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
async function takePhoto() {
const photo = await Camera.getPhoto({
quality: 90,
allowEditing: false,
resultType: CameraResultType.Uri,
source: CameraSource.Camera
});
return photo;
}
2.2 从相册选择 #
typescript
async function pickFromGallery() {
const photo = await Camera.getPhoto({
quality: 90,
allowEditing: false,
resultType: CameraResultType.Uri,
source: CameraSource.Photos
});
return photo;
}
2.3 用户选择来源 #
typescript
async function getPhoto() {
const photo = await Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Uri,
source: CameraSource.Prompt
});
return photo;
}
三、返回类型 #
3.1 URI格式 #
typescript
const photo = await Camera.getPhoto({
quality: 90,
resultType: CameraResultType.Uri
});
// photo.webPath: 可直接用于img src
// photo.path: 本地文件路径
const imageUrl = photo.webPath;
3.2 Base64格式 #
typescript
const photo = await Camera.getPhoto({
quality: 90,
resultType: CameraResultType.Base64
});
// photo.base64String: Base64编码字符串
const base64Data = photo.base64String;
const dataUrl = `data:image/jpeg;base64,${base64Data}`;
3.3 Data URL格式 #
typescript
const photo = await Camera.getPhoto({
quality: 90,
resultType: CameraResultType.DataUrl
});
// photo.dataUrl: 完整的Data URL
const imageUrl = photo.dataUrl;
四、多图选择 #
4.1 选择多张图片 #
typescript
import { Camera, CameraResultType, CameraSource } from '@capacitor/camera';
async function pickMultipleImages() {
const result = await Camera.pickImages({
quality: 90,
limit: 5,
source: CameraSource.Photos
});
// result.photos 是图片数组
return result.photos.map(photo => photo.webPath);
}
4.2 处理多图 #
typescript
async function handleMultipleImages() {
const { photos } = await Camera.pickImages({
quality: 90,
limit: 10
});
for (const photo of photos) {
console.log('Photo path:', photo.webPath);
// 处理每张图片
}
}
五、图片选项 #
5.1 完整选项配置 #
typescript
interface PhotoOptions {
// 图片质量 (0-100)
quality?: number;
// 是否允许编辑
allowEditing?: boolean;
// 返回类型
resultType: CameraResultType;
// 图片来源
source?: CameraSource;
// 保存到相册
saveToGallery?: boolean;
// 宽度限制
width?: number;
// 高度限制
height?: number;
// 是否保持宽高比
preserveAspectRatio?: boolean;
// 缩放以填充
resizeToFill?: boolean;
// 正确方向
correctOrientation?: boolean;
// 提示文本
promptLabelHeader?: string;
promptLabelCancel?: string;
promptLabelPhoto?: string;
promptLabelPicture?: string;
}
// 使用示例
const photo = await Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Uri,
source: CameraSource.Prompt,
width: 800,
height: 600,
correctOrientation: true,
saveToGallery: true
});
5.2 图片压缩 #
typescript
async function takeCompressedPhoto() {
const photo = await Camera.getPhoto({
quality: 50, // 降低质量
resultType: CameraResultType.Base64,
width: 800, // 限制宽度
height: 800 // 限制高度
});
return photo;
}
六、权限管理 #
6.1 检查权限 #
typescript
async function checkPermissions() {
const permissions = await Camera.checkPermissions();
console.log('Camera permission:', permissions.camera);
console.log('Photos permission:', permissions.photos);
return permissions;
}
6.2 请求权限 #
typescript
async function requestPermissions() {
const permissions = await Camera.requestPermissions();
if (permissions.camera === 'granted' && permissions.photos === 'granted') {
// 权限已授予
return true;
}
return false;
}
6.3 权限处理封装 #
typescript
async function ensureCameraPermission(): Promise<boolean> {
const permissions = await Camera.checkPermissions();
if (permissions.camera === 'granted') {
return true;
}
const result = await Camera.requestPermissions();
if (result.camera === 'granted') {
return true;
}
if (result.camera === 'denied') {
// 权限被拒绝,引导用户去设置
const { value } = await Dialog.confirm({
title: '权限被拒绝',
message: '需要相机权限才能拍照,是否前往设置开启?'
});
if (value) {
// 打开应用设置
// 需要使用其他插件如 @capacitor/app-launcher
}
}
return false;
}
七、图片处理 #
7.1 显示图片 #
tsx
import { useState } from 'react';
import { Camera, CameraResultType } from '@capacitor/camera';
function PhotoCapture() {
const [photo, setPhoto] = useState<string | null>(null);
const takePhoto = async () => {
const result = await Camera.getPhoto({
quality: 90,
resultType: CameraResultType.Uri
});
setPhoto(result.webPath);
};
return (
<div>
{photo && (
<img src={photo} alt="Captured" style={{ maxWidth: '100%' }} />
)}
<button onClick={takePhoto}>拍照</button>
</div>
);
}
7.2 上传图片 #
typescript
async function uploadPhoto(photoUrl: string) {
// 获取Blob
const response = await fetch(photoUrl);
const blob = await response.blob();
// 创建FormData
const formData = new FormData();
formData.append('photo', blob, 'photo.jpg');
// 上传
const uploadResponse = await fetch('https://api.example.com/upload', {
method: 'POST',
body: formData
});
return uploadResponse.json();
}
7.3 图片预览 #
typescript
async function previewAndUpload() {
const photo = await Camera.getPhoto({
quality: 90,
resultType: CameraResultType.Uri,
allowEditing: true
});
// 显示预览
const confirmed = await showPreview(photo.webPath);
if (confirmed) {
await uploadPhoto(photo.webPath);
}
}
async function showPreview(imageUrl: string): Promise<boolean> {
// 使用Dialog或其他方式显示预览
const { value } = await Dialog.confirm({
title: '确认上传',
message: '确定要上传这张照片吗?'
});
return value;
}
八、高级用法 #
8.1 自定义相机界面 #
typescript
// 注意:Capacitor Camera使用系统相机
// 如需自定义界面,需要开发自定义插件
// 或使用第三方库如:
// - react-native-vision-camera (需要自定义插件)
// - capacitor-camera-preview
8.2 图片裁剪 #
typescript
// 使用allowEditing启用系统裁剪
const photo = await Camera.getPhoto({
quality: 90,
allowEditing: true,
resultType: CameraResultType.Uri
});
// 或使用第三方裁剪库
// 如 capacitor-image-cropper
8.3 图片压缩优化 #
typescript
async function compressImage(base64: string, quality: number): Promise<string> {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// 计算缩放比例
const maxWidth = 1024;
const maxHeight = 1024;
let width = img.width;
let height = img.height;
if (width > maxWidth || height > maxHeight) {
const ratio = Math.min(maxWidth / width, maxHeight / height);
width *= ratio;
height *= ratio;
}
canvas.width = width;
canvas.height = height;
ctx?.drawImage(img, 0, 0, width, height);
resolve(canvas.toDataURL('image/jpeg', quality));
};
img.src = `data:image/jpeg;base64,${base64}`;
});
}
九、完整示例 #
9.1 React组件 #
tsx
import { useState } from 'react';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
import { Dialog } from '@capacitor/dialog';
function PhotoCaptureApp() {
const [photos, setPhotos] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const takePhoto = async () => {
try {
setLoading(true);
const photo = await Camera.getPhoto({
quality: 90,
allowEditing: false,
resultType: CameraResultType.Uri,
source: CameraSource.Camera
});
setPhotos(prev => [photo.webPath!, ...prev]);
} catch (error) {
console.error('Camera error:', error);
await Dialog.alert({
title: '错误',
message: '拍照失败,请重试'
});
} finally {
setLoading(false);
}
};
const pickImages = async () => {
try {
setLoading(true);
const result = await Camera.pickImages({
quality: 90,
limit: 5,
source: CameraSource.Photos
});
const newPhotos = result.photos.map(p => p.webPath!);
setPhotos(prev => [...newPhotos, ...prev]);
} catch (error) {
console.error('Pick images error:', error);
} finally {
setLoading(false);
}
};
const removePhoto = (index: number) => {
setPhotos(prev => prev.filter((_, i) => i !== index));
};
return (
<div className="photo-app">
<div className="actions">
<button onClick={takePhoto} disabled={loading}>
{loading ? '处理中...' : '拍照'}
</button>
<button onClick={pickImages} disabled={loading}>
选择图片
</button>
</div>
<div className="photo-grid">
{photos.map((photo, index) => (
<div key={index} className="photo-item">
<img src={photo} alt={`Photo ${index + 1}`} />
<button onClick={() => removePhoto(index)}>删除</button>
</div>
))}
</div>
{photos.length === 0 && (
<div className="empty">暂无照片</div>
)}
</div>
);
}
export default PhotoCaptureApp;
9.2 Vue组合式函数 #
typescript
// composables/useCamera.ts
import { ref, onUnmounted } from 'vue';
import { Camera, CameraResultType, CameraSource, Photo } from '@capacitor/camera';
export function useCamera() {
const photos = ref<string[]>([]);
const loading = ref(false);
const error = ref<string | null>(null);
const takePhoto = async (options = {}) => {
loading.value = true;
error.value = null;
try {
const photo = await Camera.getPhoto({
quality: 90,
resultType: CameraResultType.Uri,
source: CameraSource.Camera,
...options
});
photos.value.unshift(photo.webPath!);
return photo;
} catch (e) {
error.value = e instanceof Error ? e.message : 'Unknown error';
throw e;
} finally {
loading.value = false;
}
};
const pickImages = async (limit = 5) => {
loading.value = true;
error.value = null;
try {
const result = await Camera.pickImages({
quality: 90,
limit,
source: CameraSource.Photos
});
const newPhotos = result.photos.map(p => p.webPath!);
photos.value = [...newPhotos, ...photos.value];
return result.photos;
} catch (e) {
error.value = e instanceof Error ? e.message : 'Unknown error';
throw e;
} finally {
loading.value = false;
}
};
const removePhoto = (index: number) => {
photos.value.splice(index, 1);
};
const clearPhotos = () => {
photos.value = [];
};
return {
photos,
loading,
error,
takePhoto,
pickImages,
removePhoto,
clearPhotos
};
}
十、常见问题 #
10.1 iOS白屏问题 #
typescript
// 确保Info.plist配置正确
// 检查权限描述是否添加
10.2 Android权限问题 #
xml
<!-- Android 11+ 需要添加 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
10.3 图片方向问题 #
typescript
const photo = await Camera.getPhoto({
correctOrientation: true // 自动修正方向
});
十一、总结 #
11.1 核心功能 #
| 功能 | 方法 |
|---|---|
| 拍照 | Camera.getPhoto({ source: CameraSource.Camera }) |
| 选择图片 | Camera.getPhoto({ source: CameraSource.Photos }) |
| 多图选择 | Camera.pickImages() |
| 权限检查 | Camera.checkPermissions() |
| 权限请求 | Camera.requestPermissions() |
11.2 下一步 #
了解相机功能后,让我们学习 地理位置!
最后更新:2026-03-28