HTML5调用摄像头拍照上传:getUserMedia+FileReader+Ajax实践
在现代Web应用中,利用HTML5直接调用本地摄像头拍照并上传,已成为用户认证、资料录入等功能的标配。本文以实战为导向,完整实现从获取视频流、拍照截取、预览到Ajax上传的整个流程,代码可直接复用。一、技术路线概述
整体流程分为四步:
1. 通过navigator.mediaDevices.getUserMedia请求摄像头视频流,并绑定到video元素实现实时显示。
2. 使用canvas元素截取video当前帧作为照片。
3. 利用FileReader API将照片转为DataURL进行预览,或作为上传数据。
4. 通过XMLHttpRequest构造FormData上传图片至服务器。
二、获取摄像头视频流
首先在页面放置video元素用于预览:
<video id="video" width="640" height="480" autoplay></video>
调用getUserMedia请求摄像头权限,成功后将流赋值给video.srcObject:
navigator.mediaDevices.getUserMedia({ video: true })
.then(function(stream) {
var video = document.getElementById('video');
video.srcObject = stream;
})
.catch(function(err) {
console.log(err.name + ': ' + err.message);
});
注意:getUserMedia必须在安全上下文(HTTPS或localhost)下运行,且需用户明确授权。
三、拍照:canvas截取帧
在需要拍照时,使用canvas获取video当前画面并保存为图片数据。添加canvas元素(可隐藏):
<canvas id="canvas" width="640" height="480" style="display:none"></canvas>
拍照逻辑:
function takePhoto() {
var video = document.getElementById('video');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
var dataURL = canvas.toDataURL('image/png');
// dataURL可直接用于预览或上传
showPreview(dataURL);
}
canvas.toDataURL默认输出PNG格式,也可以指定为'image/jpeg'并传入质量参数。
四、照片预览与文件选择
4.1 使用FileReader预览本地图片
如果用户选择从相册上传,可借助input和FileReader实现即时预览:
<input type="file" id="fileInput" accept="image/*">
document.getElementById('fileInput').addEventListener('change', function(e) {
var file = e.target.files;
if (!file) return;
var reader = new FileReader();
reader.onload = function(e) {
var img = document.createElement('img');
img.src = e.target.result;
document.body.appendChild(img);
};
reader.readAsDataURL(file);
});
FileReader的readAsDataURL方法将文件转为Base64编码的DataURL,便于显示或上传。
4.2 capture属性直接调用摄像头
HTML5的capture属性可让input优先使用设备摄像头:
<input type="file" accept="image/*" capture>
在支持的移动设备上,点击会直接打开相机拍照,返回的file对象同样可通过FileReader预览。
五、实时预览优化
若需要持续展示摄像头画面(如自拍或扫码),可借助requestAnimationFrame高效绘制canvas:
var video = document.getElementById('video');
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
function drawVideo() {
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
requestAnimationFrame(drawVideo);
}
navigator.mediaDevices.getUserMedia({ video: true })
.then(function(stream) {
video.srcObject = stream;
video.play();
drawVideo();
});
使用requestAnimationFrame替代setInterval可显著降低CPU消耗,并保证60fps的平滑渲染。
六、Ajax上传图片
将拍照或选择的图片转换为Blob后,通过FormData和XMLHttpRequest异步上传至服务器。
6.1 从DataURL或Blob生成上传数据
- 若已有Blob(如input的file对象),直接追加:
var formData = new FormData();
formData.append('image', file);
- 若只有DataURL(来自canvas截图),需先转换为Blob:
function dataURLtoBlob(dataURL) {
var arr = dataURL.split(','), mime = arr.match(/:(.*?);/);
var bstr = atob(arr), n = bstr.length, u8arr = new Uint8Array(n);
while (n--) u8arr = bstr.charCodeAt(n);
return new Blob(, {type: mime});
}
然后构造FormData:
var blob = dataURLtoBlob(canvas.toDataURL('image/jpeg', 0.9));
formData.append('image', blob, 'photo.jpg');
6.2 发送Ajax请求
function uploadImage(formData) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/upload', true);
xhr.onload = function() {
if (xhr.status === 200) {
console.log('上传成功', xhr.responseText);
} else {
console.error('上传失败:', xhr.statusText);
}
};
xhr.upload.onprogress = function(e) {
if (e.lengthComputable) {
var percent = (e.loaded / e.total * 100).toFixed(2);
console.log('上传进度:', percent + '%');
}
};
xhr.onerror = function() {
console.error('网络异常');
};
xhr.send(formData);
}
6.3 重试机制
可封装重试逻辑,在失败时自动重试指定次数:
function uploadWithRetry(formData, retries = 3) {
return new Promise((resolve, reject) => {
function attempt() {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/upload', true);
xhr.onload = function() {
if (xhr.status === 200) resolve(xhr.responseText);
else reject(new Error('HTTP ' + xhr.status));
};
xhr.onerror = function() {
if (retries > 0) {
retries--;
setTimeout(attempt, 1000);
} else {
reject(new Error('重试耗尽'));
}
};
xhr.send(formData);
}
attempt();
});
}
七、兼容性与注意事项
- getUserMedia需要HTTPS协议(localhost例外)。
- capture属性在桌面端浏览器通常被忽略,建议提供备用文件选择。
- 移动端iOS Safari对MediaRecorder支持有限,推荐使用canvas截图方案。
- 上传前可在客户端对图片进行压缩(缩小canvas尺寸或降低JPEG质量),减小带宽消耗。
- 服务器端需接收multipart/form-data并处理文件存储,这里不再展开。
通过以上步骤,你已经拥有一个完整可用的HTML5拍照上传组件,可根据项目需求灵活调整UI和交互逻辑。
Re: HTML5调用摄像头拍照上传:getUserMedia+FileReader+Ajax实践
感谢楼主的详细教程,代码结构清晰,从获取流到上传的每个环节都写得很明白。特别是 `requestAnimationFrame` 代替 `setInterval` 来优化预览性能的点,确实很实用。另外 `dataURLtoBlob` 转换那段也解决了很多人上传时数据格式不匹配的问题。我有个小疑问:如果用户想在前端直接压缩图片尺寸再上传,楼主是否建议在 canvas 截取时直接调整宽高?期待后续能补充一下这方面的思路。Re: HTML5调用摄像头拍照上传:getUserMedia+FileReader+Ajax实践
很清晰的实战教程!从获取摄像头权限、canvas截取帧到Ajax上传,每一步都给出了可直接复用的代码,而且对安全上下文、capture属性等容易忽略的细节也有提醒,对新手非常友好。尤其是用requestAnimationFrame来优化实时预览这一点,省电又流畅,很多教程都忽略了。想问一下,如果需要在不同浏览器上兼容较老的版本(比如不支持getUserMedia的),有没有推荐的回退方案?另外,dataURL转Blob那段代码似乎没写完,能否补全一下?感谢分享!Re: HTML5调用摄像头拍照上传:getUserMedia+FileReader+Ajax实践
楼主分享得非常全面,从权限申请、帧截取到预览上传,每一步都有清晰代码和注意事项,尤其是用 requestAnimationFrame 替代 setInterval 来降低 CPU 消耗,这个优化细节很实用。之前我在项目里踩过 getUserMedia 必须 HTTPS 的坑,看到你标出来很到位。想请教一下,canvas 截图质量参数(比如 JPEG 的 0.92)你一般怎么设?还有如果移动端 capture 属性直接调用相机,返回的 file 路径在部分 Android 浏览器上会有兼容问题,你遇到过吗?
页:
[1]