翼度科技»论坛 编程开发 html5 查看内容

手机端H5 实现自定义拍照界面

4

主题

4

帖子

12

积分

新手上路

Rank: 1

积分
12
手机端 H5 实现自定义拍照界面也可以使用 MediaDevices API 和  标签来实现,和在桌面端做法基本一致。
首先,使用 MediaDevices.getUserMedia() 方法获取摄像头媒体流,并将其传递给  标签进行渲染。
接着,使用 HTML 的  标签来截取当前摄像头的画面,通过  上的 getContext('2d') 方法来绘制。
最后,使用 canvas.toDataURL() 方法将图像转换为 base64 格式,可以通过将其保存到本地或发送到服务器来存储照片。
但是需要注意的是,在手机端,调用摄像头需要在 HTTPS 或 localhost 下访问,还需要用户事先进行授权。
且在手机端可能会有些浏览器对于getUserMedia有所限制,需要额外兼容性处理。且手机端的实现需要考虑屏幕的方向,在绘制截图时需要根据不同的屏幕方向调整画布尺寸。
在手机端,为了让用户能够在页面中手动切换摄像头,需要检测手机端设备是否有多个摄像头,在有多个摄像头时,提供给用户切换摄像头的选项。
此外,需要进行一些兼容性处理,以便在不同浏览器和手机设备上正常工作。同时,需要考虑手机端的交互体验,例如提供给用户切换摄像头和调整照片尺寸的选项。
对于一些高级功能,例如人脸检测和识别,美颜,以及其他高级图像处理功能可以使用第三方库,如openCV.js,tracking.js, face-api.js等来实现。
还可以使用框架,如 React Native, Ionic, PhoneGap 等更加轻松地在移动端实现相关功能。
总之,通过使用 MediaDevices API 和  标签在手机端实现自定义拍照界面是可行的,但是需要注意的点比桌面端多一些。虽然在手机端实现自定义拍照界面有一定的挑战,但是通过使用 MediaDevices API 和相关第三方库,还有经验丰富的前端工程师在这个问题上是有解决方案的。
一、实现示例框架代码
  1. <video id="camera" width="640" height="480" autoplay></video>
  2. <button id="invoking" onclick="invokingCamera">invoking Camera</button>
  3. <button id="snapshot" onclick="takeSnapshot">Take snapshot</button>
复制代码
 使用 MediaDevices.getUserMedia() 方法获取摄像头媒体流,并将其传递给  标签进行渲染
  1. // 调用摄像头
  2. function invokingCamera() {
  3.     // 注意本例需要在HTTPS协议网站中运行,新版本Chrome中getUserMedia接口在http下不再支持。
  4.     // 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
  5.     if (navigator.mediaDevices === undefined) {
  6.         navigator.mediaDevices = {};
  7.     }
  8.     // 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
  9.     // 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
  10.     if (navigator.mediaDevices.getUserMedia === undefined) {
  11.         navigator.mediaDevices.getUserMedia = function (constraints) {
  12.             // 首先,如果有getUserMedia的话,就获得它
  13.             const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||
  14.                 navigator.mozGetUserMedia;
  15.             // 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
  16.             if (!getUserMedia) {
  17.                 return Promise.reject(new Error(
  18.                     'getUserMedia is not implemented in this browser'));
  19.             }
  20.             // 否则,为老的navigator.getUserMedia方法包裹一个Promise
  21.             return new Promise(function (resolve, reject) {
  22.                 getUserMedia.call(navigator, constraints, resolve, reject);
  23.             });
  24.         }
  25.     }
  26.     // 手机可视区域宽度(请通过相关API获取真实宽度)
  27.     const windowWidth = 375;
  28.     // 手机可视区域高度(请通过相关API获取真实高度)
  29.     const windowHeight = 700;
  30.     const constraints = {
  31.         audio: false,
  32.         video: {
  33.             // 前置摄像头
  34.             facingMode: 'user',
  35.             // 该属性相当于手机端的高
  36.             width: Math.max(windowWidth, windowHeight) - 120,   // 减去 120 用于在页面底部放置拍照等功能按钮
  37.             // 该属性相当于手机端的宽
  38.             height: Math.min(windowWidth, windowHeight),
  39.         }
  40.     };
  41.     navigator.mediaDevices.getUserMedia(constraints)
  42.         .then(function (stream) {
  43.             const video = document.querySelector('camera');
  44.             // 旧的浏览器可能没有srcObject
  45.             if ("srcObject" in video) {
  46.                 video.srcObject = stream;
  47.             } else {
  48.                 // 防止在新的浏览器里使用它,应为它已经不再支持了
  49.                 video.src = window.URL.createObjectURL(stream);
  50.             }
  51.             video.onloadedmetadata = function (e) {
  52.                 video.play();
  53.             };
  54.         })
  55.         .catch(function (err) {
  56.             console.log(err.name + ": " + err.message);
  57.         });
  58. }
复制代码
 使用 HTML 的  标签来截取当前摄像头的画面,通过  上的 getContext('2d') 方法来绘制
  1. function takeSnapshot() {
  2.     const canvas = document.createElement('canvas');
  3.     const ctx = canvas.getContext('2d');
  4.     const video = document.querySelector('video');
  5.     canvas.width = Math.min(video.videoWidth, video.videoHeight);
  6.     canvas.height = Math.max(video.videoWidth, video.videoHeight);
  7.     ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
  8.     // ****** 镜像处理 ******
  9.     function getPixel(imageData, row, column) {
  10.         const uint8ClampedArray = imageData.data;
  11.         const width = imageData.width;
  12.         const height = imageData.height;
  13.         const pixel = [];
  14.         for (let i = 0; i < 4; i++) {
  15.             pixel.push(uint8ClampedArray[row * width * 4 + column * 4 + i]);
  16.         }
  17.         return pixel;
  18.     }
  19.     function setPixel(imageData, row, column, pixel) {
  20.         const uint8ClampedArray = imageData.data;
  21.         const width = imageData.width;
  22.         const height = imageData.height;
  23.         for (let i = 0; i < 4; i++) {
  24.             uint8ClampedArray[row * width * 4 + column * 4 + i] = pixel[i];
  25.         }
  26.     }
  27.     const mirrorImageData = ctx.createImageData(canvas.width, canvas.height);
  28.     const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  29.     for (let h = 0; h < canvas.height; h++) {
  30.         for (let w = 0; w < canvas.width; w++) {
  31.             const pixel = getPixel(imageData, h, canvas.width - w - 1);
  32.             setPixel(mirrorImageData, h, w, pixel);
  33.         }
  34.     }
  35.     ctx.putImageData(mirrorImageData, 0, 0);
  36.     // ****** 镜像处理 ******
  37.     const base64 = canvas.toDataURL('image/jpeg');
  38. }
复制代码
最后,使用 canvas.toDataURL() 方法将图像转换为 base64 格式,可以通过将其保存到本地或发送到服务器来存储照片
二、具体实现代码(基于uni-app)
布局代码:
  1. <template>
  2.     <view  >
  3.         <video  object-fit="fill"></video>
  4.         <view
  5.               >
  6.             <view
  7.                   >
  8.                 <image :src="qjkImgSrc" mode="widthFix" ></image>
  9.             </view>
  10.             <view
  11.                   >
  12.                 <image :src="qjtxkImgSrc" mode="widthFix" ></image>
  13.             </view>
  14.             <view
  15.                   >
  16.                 <view
  17.                       >
  18.                     <uni-icons type="close" :size="32" @click="handlePhotographCloseClick">
  19.                     </uni-icons>
  20.                 </view>
  21.                 <view   @click="handlePhotographClick">
  22.                     <view >
  23.                         <view ></view>
  24.                     </view>
  25.                 </view>
  26.                 <view
  27.                       >
  28.                     <uni-icons type="folder-add" :size="32" @click="handleAddPhotographClick">
  29.                     </uni-icons>
  30.                 </view>
  31.             </view>
  32.         </view>
  33.     </view>
  34. </template>
复制代码
 JavaScript 代码:
  1. export default {
  2.     data() {
  3.         return {
  4.             imageUrl: "",
  5.             // 媒体流,用于关闭摄像头
  6.             mediaStreamTrack: null,
  7.         };
  8.     },
  9.     onLoad() {
  10.         this.invokingCamera();
  11.     },
  12.     onUnload() {
  13.         this.handlePhotographCloseClick();
  14.     },
  15.     methods: {
  16.         invokingCamera() {
  17.             const self = this;
  18.             // 注意本例需要在HTTPS协议网站中运行,新版本Chrome中getUserMedia接口在http下不再支持。
  19.             // 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
  20.             if (navigator.mediaDevices === undefined) {
  21.                 navigator.mediaDevices = {};
  22.             }
  23.             // 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
  24.             // 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
  25.             if (navigator.mediaDevices.getUserMedia === undefined) {
  26.                 navigator.mediaDevices.getUserMedia = function (constraints) {
  27.                     // 首先,如果有getUserMedia的话,就获得它
  28.                     const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||
  29.                         navigator.mozGetUserMedia;
  30.                     // 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
  31.                     if (!getUserMedia) {
  32.                         return Promise.reject(new Error(
  33.                             'getUserMedia is not implemented in this browser'));
  34.                     }
  35.                     // 否则,为老的navigator.getUserMedia方法包裹一个Promise
  36.                     return new Promise(function (resolve, reject) {
  37.                         getUserMedia.call(navigator, constraints, resolve, reject);
  38.                     });
  39.                 }
  40.             }
  41.             uni.getSystemInfo({
  42.                 success: function (res) {
  43.                     const constraints = {
  44.                         audio: false,
  45.                         video: {
  46.                             // 前置摄像头
  47.                             facingMode: 'user',
  48.                             // 手机端相当于高
  49.                             width: Math.max(res.windowWidth, res.windowHeight) - 120,
  50.                             // 手机端相当于宽
  51.                             height: Math.min(res.windowWidth, res.windowHeight),
  52.                         }
  53.                     };
  54.                     navigator.mediaDevices.getUserMedia(constraints)
  55.                         .then(function (stream) {
  56.                             self.mediaStreamTrack = stream;
  57.                             const video = document.querySelector('video');
  58.                             // 旧的浏览器可能没有srcObject
  59.                             if ("srcObject" in video) {
  60.                                 video.srcObject = stream;
  61.                             } else {
  62.                                 // 防止在新的浏览器里使用它,应为它已经不再支持了
  63.                                 video.src = window.URL.createObjectURL(stream);
  64.                             }
  65.                             video.onloadedmetadata = function (e) {
  66.                                 video.play();
  67.                             };
  68.                         })
  69.                         .catch(function (err) {
  70.                             console.log(err.name + ": " + err.message);
  71.                         });
  72.                 }
  73.             });
  74.         },
  75.         handlePhotographCloseClick() {
  76.             if (this.mediaStreamTrack) {
  77.                 // 关闭摄像头
  78.                 this.mediaStreamTrack.getTracks().forEach(function (track) {
  79.                     track.stop();
  80.                 });
  81.                 this.mediaStreamTrack = null;
  82.             }
  83.         },
  84.         handlePhotographClick() {
  85.             const self = this;
  86.             const canvas = document.createElement('canvas');
  87.             const ctx = canvas.getContext('2d');
  88.             const video = document.querySelector('video');
  89.             canvas.width = Math.min(video.videoWidth, video.videoHeight);
  90.             canvas.height = Math.max(video.videoWidth, video.videoHeight);
  91.             ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
  92.             // ****** 镜像处理 ******
  93.             function getPixel(imageData, row, column) {
  94.                 const uint8ClampedArray = imageData.data;
  95.                 const width = imageData.width;
  96.                 const height = imageData.height;
  97.                 const pixel = [];
  98.                 for (let i = 0; i < 4; i++) {
  99.                     pixel.push(uint8ClampedArray[row * width * 4 + column * 4 + i]);
  100.                 }
  101.                 return pixel;
  102.             }
  103.             function setPixel(imageData, row, column, pixel) {
  104.                 const uint8ClampedArray = imageData.data;
  105.                 const width = imageData.width;
  106.                 const height = imageData.height;
  107.                 for (let i = 0; i < 4; i++) {
  108.                     uint8ClampedArray[row * width * 4 + column * 4 + i] = pixel[i];
  109.                 }
  110.             }
  111.             const mirrorImageData = ctx.createImageData(canvas.width, canvas.height);
  112.             const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  113.             for (let h = 0; h < canvas.height; h++) {
  114.                 for (let w = 0; w < canvas.width; w++) {
  115.                     const pixel = getPixel(imageData, h, canvas.width - w - 1);
  116.                     setPixel(mirrorImageData, h, w, pixel);
  117.                 }
  118.             }
  119.             ctx.putImageData(mirrorImageData, 0, 0);
  120.             // ****** 镜像处理 ******
  121.             self.$nextTick(() => {
  122.                 const base64 = canvas.toDataURL('image/jpeg');
  123.                 self.imageUrl = base64;
  124.                 self.handlePhotographCloseClick();
  125.             });
  126.         },
  127.         handleAddPhotographClick() {
  128.             this.uploadImage();
  129.         },
  130.         uploadImage: function () {
  131.             const self = this;
  132.             uni.chooseImage({
  133.                 count: 1,
  134.                 sizeType: ['compressed'],
  135.                 success: function (res) {
  136.                     self.handlePhotographCloseClick();
  137.                     const file = res.tempFiles[0];
  138.                     const reader = new FileReader();
  139.                     reader.readAsDataURL(file);
  140.                     reader.onload = function (e) {
  141.                         self.imageUrl = e.target.result;
  142.                     }
  143.                 }
  144.             });
  145.         },
  146.     }
  147. };
复制代码
样式代码:
[code][/code]最终效果展示:



来源:https://www.cnblogs.com/yuzhihui/p/17044075.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具