查看: 110|回复: 1

HTML5调用摄像头拍照上传:getUserMedia+FileReader+Ajax实践

[复制链接]
发表于 3 小时前 | 显示全部楼层 |阅读模式
在现代Web应用中,利用HTML5直接调用本地摄像头拍照并上传,已成为用户认证、资料录入等功能的标配。本文以实战为导向,完整实现从获取视频流、拍照截取、预览到Ajax上传的整个流程,代码可直接复用。

一、技术路线概述
整体流程分为四步:
1. 通过navigator.mediaDevices.getUserMedia请求摄像头视频流,并绑定到video元素实现实时显示。
2. 使用canvas元素截取video当前帧作为照片。
3. 利用FileReader API将照片转为DataURL进行预览,或作为上传数据。
4. 通过XMLHttpRequest构造FormData上传图片至服务器。

二、获取摄像头视频流
首先在页面放置video元素用于预览:
  1. <video id="video" width="640" height="480" autoplay></video>
复制代码
调用getUserMedia请求摄像头权限,成功后将流赋值给video.srcObject:
  1. navigator.mediaDevices.getUserMedia({ video: true })
  2.   .then(function(stream) {
  3.     var video = document.getElementById('video');
  4.     video.srcObject = stream;
  5.   })
  6.   .catch(function(err) {
  7.     console.log(err.name + ': ' + err.message);
  8.   });
复制代码
注意:getUserMedia必须在安全上下文(HTTPS或localhost)下运行,且需用户明确授权。

三、拍照:canvas截取帧
在需要拍照时,使用canvas获取video当前画面并保存为图片数据。添加canvas元素(可隐藏):
  1. <canvas id="canvas" width="640" height="480" style="display:none"></canvas>
复制代码
拍照逻辑:
  1. function takePhoto() {
  2.   var video = document.getElementById('video');
  3.   var canvas = document.getElementById('canvas');
  4.   var ctx = canvas.getContext('2d');
  5.   ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
  6.   var dataURL = canvas.toDataURL('image/png');
  7.   // dataURL可直接用于预览或上传
  8.   showPreview(dataURL);
  9. }
复制代码
canvas.toDataURL默认输出PNG格式,也可以指定为'image/jpeg'并传入质量参数。

四、照片预览与文件选择
4.1 使用FileReader预览本地图片
如果用户选择从相册上传,可借助input[type=file]和FileReader实现即时预览:
  1. <input type="file" id="fileInput" accept="image/*">
复制代码
  1. document.getElementById('fileInput').addEventListener('change', function(e) {
  2.   var file = e.target.files[0];
  3.   if (!file) return;
  4.   var reader = new FileReader();
  5.   reader.onload = function(e) {
  6.     var img = document.createElement('img');
  7.     img.src = e.target.result;
  8.     document.body.appendChild(img);
  9.   };
  10.   reader.readAsDataURL(file);
  11. });
复制代码
FileReader的readAsDataURL方法将文件转为Base64编码的DataURL,便于显示或上传。

4.2 capture属性直接调用摄像头
HTML5的capture属性可让input[type=file]优先使用设备摄像头:
  1. <input type="file" accept="image/*" capture>
复制代码
在支持的移动设备上,点击会直接打开相机拍照,返回的file对象同样可通过FileReader预览。

五、实时预览优化
若需要持续展示摄像头画面(如自拍或扫码),可借助requestAnimationFrame高效绘制canvas:
  1. var video = document.getElementById('video');
  2. var canvas = document.getElementById('canvas');
  3. var ctx = canvas.getContext('2d');
  4. function drawVideo() {
  5.   ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
  6.   requestAnimationFrame(drawVideo);
  7. }
  8. navigator.mediaDevices.getUserMedia({ video: true })
  9.   .then(function(stream) {
  10.     video.srcObject = stream;
  11.     video.play();
  12.     drawVideo();
  13.   });
复制代码
使用requestAnimationFrame替代setInterval可显著降低CPU消耗,并保证60fps的平滑渲染。

六、Ajax上传图片
将拍照或选择的图片转换为Blob后,通过FormData和XMLHttpRequest异步上传至服务器。

6.1 从DataURL或Blob生成上传数据
- 若已有Blob(如input[type=file]的file对象),直接追加:
  1. var formData = new FormData();
  2. formData.append('image', file);
复制代码
- 若只有DataURL(来自canvas截图),需先转换为Blob:
  1. function dataURLtoBlob(dataURL) {
  2.   var arr = dataURL.split(','), mime = arr[0].match(/:(.*?);/)[1];
  3.   var bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
  4.   while (n--) u8arr[n] = bstr.charCodeAt(n);
  5.   return new Blob([u8arr], {type: mime});
  6. }
复制代码
然后构造FormData:
  1. var blob = dataURLtoBlob(canvas.toDataURL('image/jpeg', 0.9));
  2. formData.append('image', blob, 'photo.jpg');
复制代码

6.2 发送Ajax请求
  1. function uploadImage(formData) {
  2.   var xhr = new XMLHttpRequest();
  3.   xhr.open('POST', '/upload', true);
  4.   xhr.onload = function() {
  5.     if (xhr.status === 200) {
  6.       console.log('上传成功', xhr.responseText);
  7.     } else {
  8.       console.error('上传失败:', xhr.statusText);
  9.     }
  10.   };
  11.   xhr.upload.onprogress = function(e) {
  12.     if (e.lengthComputable) {
  13.       var percent = (e.loaded / e.total * 100).toFixed(2);
  14.       console.log('上传进度:', percent + '%');
  15.     }
  16.   };
  17.   xhr.onerror = function() {
  18.     console.error('网络异常');
  19.   };
  20.   xhr.send(formData);
  21. }
复制代码

6.3 重试机制
可封装重试逻辑,在失败时自动重试指定次数:
  1. function uploadWithRetry(formData, retries = 3) {
  2.   return new Promise((resolve, reject) => {
  3.     function attempt() {
  4.       var xhr = new XMLHttpRequest();
  5.       xhr.open('POST', '/upload', true);
  6.       xhr.onload = function() {
  7.         if (xhr.status === 200) resolve(xhr.responseText);
  8.         else reject(new Error('HTTP ' + xhr.status));
  9.       };
  10.       xhr.onerror = function() {
  11.         if (retries > 0) {
  12.           retries--;
  13.           setTimeout(attempt, 1000);
  14.         } else {
  15.           reject(new Error('重试耗尽'));
  16.         }
  17.       };
  18.       xhr.send(formData);
  19.     }
  20.     attempt();
  21.   });
  22. }
复制代码

七、兼容性与注意事项
- getUserMedia需要HTTPS协议(localhost例外)。
- capture属性在桌面端浏览器通常被忽略,建议提供备用文件选择。
- 移动端iOS Safari对MediaRecorder支持有限,推荐使用canvas截图方案。
- 上传前可在客户端对图片进行压缩(缩小canvas尺寸或降低JPEG质量),减小带宽消耗。
- 服务器端需接收multipart/form-data并处理文件存储,这里不再展开。

通过以上步骤,你已经拥有一个完整可用的HTML5拍照上传组件,可根据项目需求灵活调整UI和交互逻辑。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: HTML5调用摄像头拍照上传:getUserMedia+FileReader+Ajax实践

感谢楼主的详细教程,代码结构清晰,从获取流到上传的每个环节都写得很明白。特别是 `requestAnimationFrame` 代替 `setInterval` 来优化预览性能的点,确实很实用。另外 `dataURLtoBlob` 转换那段也解决了很多人上传时数据格式不匹配的问题。我有个小疑问:如果用户想在前端直接压缩图片尺寸再上传,楼主是否建议在 canvas 截取时直接调整宽高?期待后续能补充一下这方面的思路。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-10 19:04 , Processed in 0.025199 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部