更新记录
1.1.2(2026-05-27)
补充文档
1.1.0(2026-05-27)
新增能力(Android、ios):
- 点击聚焦 + AF 区域回切连续对焦
- 双指缩放焦距,自动映射
setZoomPercent(0~100) 到设备 maxZoom
- 新 props:
default-camera、tap-focus、pinch-zoom、flash-mode
- 闪光灯 4 模式:
off / torch / on / auto
- 拍照 4 档画质预设:
low / medium / high / ultra
- 视频录制 4 档画质:QUALITY_LOW / 480P / 720P / 1080P
- 视频录制最大时长限制(
maxDurationMs),到时自动停止
- 视频录制可选关闭音频(
withAudio: false)
- 视频输出格式:
mp4 / 3gp
- 音频处理(AEC / NS / AGC),best-effort 模式,自动检测设备能力并在结果中返回
- 新 actions:
focusOnPoint、setZoomPercent、getZoomInfo、`setFlas
平台兼容性
uni-app(4.72)
| Vue2 |
Vue3 |
Chrome |
Safari |
app-vue |
app-nvue |
app-nvue插件版本 |
Android |
Android插件版本 |
iOS |
iOS插件版本 |
鸿蒙 |
| - |
- |
- |
- |
× |
√ |
1.1.2 |
5.0 |
1.1.2 |
13 |
1.1.2 |
× |
| 微信小程序 |
支付宝小程序 |
抖音小程序 |
百度小程序 |
快手小程序 |
京东小程序 |
鸿蒙元服务 |
QQ小程序 |
飞书小程序 |
小红书小程序 |
快应用-华为 |
快应用-联盟 |
| - |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
- |
uni-app x(4.72)
| Chrome |
Safari |
Android |
iOS |
鸿蒙 |
微信小程序 |
| - |
- |
- |
- |
- |
- |
hl-custom-photo
hl-custom-photo 当前提供两类能力:
- 原生相机预览组件:预览、拍照、录像、切换摄像头、闪光灯、曝光、缩放、对焦、取帧、取色
- 模块 API:
compressImage 图片压缩
当前状态
| 平台 |
状态 |
说明 |
| Android |
已接入 |
当前主用平台,组件和压缩 API 都可用 |
| iOS |
已接入 |
组件和压缩 API 都可用,但事件桥接和部分能力表现与 Android 有差异 |
快速接入
1. 挂载组件
<template>
<hl-custom-photo
v-if="cameraMounted"
class="camera-view"
:autoStart="autoStartFlag"
:scale="scale"
:tapFocus="tapFocusFlag"
:pinchZoom="pinchZoomFlag"
:command="commandStr"
@result="onCameraResult"
@log="onCameraLog"
/>
</template>
2. 通过 command 下发动作
import { ref } from 'vue'
const commandStr = ref('')
const cameraMounted = ref(true)
const autoStartFlag = ref('1')
const tapFocusFlag = ref('1')
const pinchZoomFlag = ref('1')
const scale = ref('4:3')
let seq = 0
const pendingCallbacks = {}
function sendCommand(action, options, callback) {
seq += 1
if (callback) {
pendingCallbacks[seq] = callback
}
commandStr.value = JSON.stringify({
action,
options: options || {},
seq
})
}
function normalizeEventPayload(event) {
const detail = event?.detail ?? event
if (detail?.dynamicJSONFields) {
return detail.dynamicJSONFields
}
const payloadJson = detail?.payloadJson ?? detail?.__json
if (typeof payloadJson === 'string' && payloadJson.length > 0) {
try {
return JSON.parse(payloadJson)
} catch (e) {
console.warn('[hl-custom-photo] payloadJson parse failed:', e)
}
}
return detail
}
function onCameraResult(event) {
const payload = normalizeEventPayload(event)
const currentSeq = payload?.seq
if (currentSeq != null && pendingCallbacks[currentSeq]) {
const cb = pendingCallbacks[currentSeq]
delete pendingCallbacks[currentSeq]
cb(payload)
}
}
function onCameraLog(event) {
const payload = normalizeEventPayload(event)
console.log('[native]', payload?.level, payload?.tag, payload?.msg)
}
sendCommand('takePhoto', {
qualityPreset: 'high',
returnFile: true
}, (result) => {
console.log('photo result:', result)
})
3. 如果权限弹窗后预览没有恢复
nvue 页面里可直接卸载再挂载组件:
import { nextTick } from 'vue'
function restartCameraAfterPermission() {
cameraMounted.value = false
nextTick(() => {
cameraMounted.value = true
})
}
事件
| 事件 |
说明 |
@result |
动作回调。通常包含 code、msg、message,command 模式下还会带 action、seq |
@log |
原生日志透传。常见字段:level、tag、msg、ts |
常见返回码约定:
| code |
含义 |
200 |
成功 |
201 |
业务失败或当前状态不满足 |
400 |
参数错误 |
401 |
权限失败 |
500 |
原生异常 |
501 |
当前平台还没迁移该能力 |
command 动作一览
下面这些 action 当前已经在 Android 和 iOS 的 dispatchAction 里接好,可以直接通过 command 使用。
基础控制
| action |
options |
说明 |
start |
{ scale? } |
启动相机 |
stop |
{} |
停止相机 |
destroy |
{} |
销毁组件并释放资源 |
getCamerasInfo |
{} |
获取摄像头列表 |
switchCamera |
{} |
前后摄像头切换 |
switchCameraById |
{ id } |
切换到指定摄像头 |
setIsbackCamera |
{ isBack } |
强制切前置或后置 |
reSetPreviewSize |
{} |
父布局尺寸变化后,重新同步预览尺寸 |
拍照
| action |
options |
说明 |
takePhoto |
{ qualityPreset?, quality?, width?, returnFile?, crop?, cropPx?, savePath?, fileName? } |
拍照 |
补充:
qualityPreset 支持:low、medium、high、ultra
crop 是百分比裁剪,格式 [x, y, w, h]
cropPx 是像素裁剪,格式 [x, y, w, h]
returnFile: true 时通常返回文件路径,否则通常返回 base64
视频录制
| action |
options |
说明 |
startRecord |
{ quality?, withAudio?, maxDurationMs?, format?, audioProcessing?, savePath?, fileName?, orientation?, profile? } |
开始录像 |
stopRecord |
{} |
停止录像 |
isRecording |
{} |
查询当前是否在录像 |
getAudioCapabilities |
{} |
查询 AEC / NS / AGC 支持情况 |
hasProfile |
{ profile } |
查询设备是否支持指定录制档位 |
audioProcessing 结构示例:
{
aec: true,
ns: true,
agc: false
}
闪光灯 / 曝光 / 缩放 / 对焦
| action |
options |
说明 |
setFlash |
{ open } |
简化版闪光灯控制,true 等价于 torch,false 等价于 off |
setFlashMode |
{ mode } |
完整模式,支持 off、torch、on、auto |
getExposureCompensationVal |
{} |
获取曝光补偿区间 |
setExposureCompensation |
{ value } |
设置曝光补偿 |
setZoom |
{ level } |
直接设置底层 zoom 原始值 |
setZoomPercent |
{ percent } |
按百分比设置缩放,建议范围 0~100 |
getZoomInfo |
{} |
获取当前缩放百分比和最大原始值 |
focusOnPoint |
{ x, y } |
按预览 view 像素坐标对焦 |
configureTapFocus |
{ enabled } |
开关点击聚焦 |
configurePinchZoom |
{ enabled } |
开关双指缩放 |
iOS 额外兼容:
setZoomPercent({ percent, silent: true })
getZoomInfo({ silent: true })
silent: true 会执行原生动作,但不回发 @result。
取帧 / 取色
| action |
options |
说明 |
startFrameListen |
{} |
开始帧监听 |
stopFrameListen |
{} |
停止帧监听 |
getFrameData |
{ quality?, width?, crop?, cropPx? } |
获取一帧图像 base64 |
getColor |
{ point: [xPercent, yPercent] } |
在预览帧的百分比坐标取单像素 RGB |
注意事项:
getFrameData 前必须先调 startFrameListen
getColor.point 建议传 0~100 的百分比坐标
getColor 取的是单像素,不是区域平均色
- 返回格式通常是
color: "r,g,b",例如 145,144,163
直接 expose 方法
除了 command,组件还直接暴露了一批方法,例如:
openCamera
setCameraConfigAndStart
setCropOption
startRealtimeFrame
pauseRealtimeFrame
setLogSink
但建议把这些当作高级用法:
- 对业务页面,优先使用
command
- 不是所有 expose 方法都已经映射进
command
- 如果你新增了原生能力,并希望页面统一通过
command 使用,需要同步修改对应平台的 dispatchAction
画质映射
拍照 qualityPreset
| 档位 |
输出宽度 |
JPEG quality |
low |
640 |
70 |
medium |
1280 |
80 |
high |
1920 |
90 |
ultra |
0,表示尽量保留原尺寸 |
100 |
视频 quality
| 档位 |
Android 映射 |
low |
CamcorderProfile.QUALITY_LOW |
medium |
CamcorderProfile.QUALITY_480P |
high |
CamcorderProfile.QUALITY_720P |
ultra |
CamcorderProfile.QUALITY_1080P |
设备不支持目标档位时,通常会返回 201。
compressImage API
用法
import * as CustomPhoto from '@/uni_modules/hl-custom-photo'
CustomPhoto.compressImage(
{
path: '/storage/emulated/0/DCIM/Camera/test.jpg',
maxWidth: 1080,
maxHeight: 1920,
quality: 80,
format: 'webp',
targetPath: '/storage/emulated/0/Android/data/xxx/cache/out.webp'
},
(result) => {
if (result.code === 200) {
console.log(result.path, result.width, result.height, result.size)
}
}
)
参数
| 字段 |
类型 |
说明 |
path |
string |
输入图片路径 |
maxWidth |
number |
最大输出宽度,可选 |
maxHeight |
number |
最大输出高度,可选 |
quality |
number |
输出质量,可选 |
format |
string |
jpeg / png / webp,可选 |
targetPath |
string |
自定义输出路径,可选 |
返回结果
| 字段 |
说明 |
code |
200 成功,其他值表示失败 |
msg |
结果消息 |
path |
输出文件路径 |
format |
实际输出格式 |
width / height |
输出宽高 |
size |
输出文件大小 |
originalWidth / originalHeight / originalSize |
原图信息 |
scale |
相对原图的缩放比例 |
平台差异
1. @result / @log 事件对象
- Android 端一般可以直接读取
event.detail
- Android 若经过 UTS 包装,通常也能从
event.detail.dynamicJSONFields 拿到 plain object
- iOS 端当前更稳妥的做法是优先读
payloadJson,其次兼容旧的 __json
- 如果 iOS 页面看到的是空对象,不要先怀疑原生没执行,先排查事件解包逻辑
2. takePhoto
- Android 更接近原生静态拍照输出
- iOS 当前稳定实现更偏“预览帧截图后再加工”
- 这意味着 iOS 最终分辨率上限受当前预览帧影响,不完全等同于传感器原图能力
3. 缩放
- Android 适合直接走
getZoomInfo -> setZoomPercent
- iOS 按钮式缩放更建议页面自己维护缩放状态,再调用
setZoomPercent
- iOS 的
silent: true 适合“只下发控制,不关心本次回调”的场景
4. getColor
- Android 当前更接近“开关式”取色
- iOS 当前更接近“单次调用,单次返回”
- 如果业务想两端交互一致,建议页面层统一封装成“点一次,回一次”