基本流程
- 使用 input 调起文件选择器/系统相机
- 监听 change 事件, 并用
Filereader
读取 base64 - 将 base64 直接发送给后端
- 后端进行写入文件/发送给CDN等操作
踩坑记录
图片压缩
现在手机相机的像素较高, 前置摄像头也可能达到1M, 为了加快传输和节省带宽资源, 使用 canvas 进行压缩
- 根据限定的最大尺寸, 等比创建一个 canvas , 将图片直接绘制到 canvas 上, 再导出为 base64
照片方向错误
有些手机(比如 iPhone)的前置摄像头拍出来的照片读取出来是水平翻转或者旋转90度的, 需要纠正方向后上传
- 使用 exif.js 读取照片元数据, 根据照片的被旋转方向反向绘制到 canvas
安卓 webview 中无法唤起相机直接拍照
这是一个比较隐蔽的坑, 当时测试时在有些机型复现, 有些没有复现, 所以起初怀疑是 webview 的限制或兼容性问题.
原因就是在安卓系统中APP要调用相机需要和系统申请权限, APP 内置的 webview 也同样需要, 如果用户安装 APP 后从未获取过相机权限, 直接调用这个功能就会出错. 有些机器没有复现就是因为在那个台机器的 APP 上使用过拍照功能, 已经有权限了.
这个问题需要 native 对此提供支持.
一种方法是检测 webview 中调起相机的行为或提供 bridge 接口, 提前向系统申请权限.
二是在 APP 首次启动时就申请拍照权限 (不够友好).
美图手机自带相机不可用
无论是在 webview 还是在普通浏览器, 美图系统自带的相机拍照之后, 都不能触发 input 的 change 事件, 更无法获取到照片.
但下载一个第三方相机比如 Camare360, 却可以正常读取.
没找到解决方案, 怀疑是 ROM 问题.
前端代码
文件读取
1 | let file = input.files[0] |
upload 方法
1 | // 创建图片对象写入 base 64 |
后端代码
以Node.js为例 (未做类型判断, 尺寸限制等处理, 仅供调试使用)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19app.use('/upload', function (req, res) {
let data = req.body.img
var dataTypes = new RegExp('data:(.*)/(.*);base64').exec(data) // 读取文件和图片格式
var dataType = dataTypes[1] // 数据类型, 如果是图片, 此值均为 image
var extName = dataTypes[2] // 文件扩展名, jpg/png/gif 等
var fileName = new Date().getTime() + '.' + extName
var base64Data = data.replace(/^data:image\/\w+;base64,/, '') // 图片数据
var dataBuffer = Buffer.alloc(base64Data.length, base64Data, 'base64') // 创建数据流
fs.writeFile(`static/up/${fileName}`, dataBuffer, function (err) {
if (err) {
res.send(err)
} else {
res.send({ // 如果写入成功, 返回文件访问路径
code: 0,
path: `/static/up/${fileName}`
})
}
})
})