相机与相册 #

一、插件安装 #

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