在现代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[type=file]和FileReader实现即时预览:- <input type="file" id="fileInput" accept="image/*">
复制代码- document.getElementById('fileInput').addEventListener('change', function(e) {
- var file = e.target.files[0];
- 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[type=file]优先使用设备摄像头:- <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[type=file]的file对象),直接追加:- var formData = new FormData();
- formData.append('image', file);
复制代码 - 若只有DataURL(来自canvas截图),需先转换为Blob:- function dataURLtoBlob(dataURL) {
- var arr = dataURL.split(','), mime = arr[0].match(/:(.*?);/)[1];
- var bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
- while (n--) u8arr[n] = bstr.charCodeAt(n);
- return new Blob([u8arr], {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和交互逻辑。 |