最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

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

来源:博客园

手机端 H5 实现自定义拍照界面也可以使用 MediaDevices API 和


(资料图片)

首先,使用 MediaDevices.getUserMedia() 方法获取摄像头媒体流,并将其传递给

接着,使用 HTML 的 标签来截取当前摄像头的画面,通过 上的 getContext("2d") 方法来绘制。

最后,使用 canvas.toDataURL() 方法将图像转换为 base64 格式,可以通过将其保存到本地或发送到服务器来存储照片。

但是需要注意的是,在手机端,调用摄像头需要在 HTTPS 或 localhost 下访问,还需要用户事先进行授权。

且在手机端可能会有些浏览器对于getUserMedia有所限制,需要额外兼容性处理。且手机端的实现需要考虑屏幕的方向,在绘制截图时需要根据不同的屏幕方向调整画布尺寸。

在手机端,为了让用户能够在页面中手动切换摄像头,需要检测手机端设备是否有多个摄像头,在有多个摄像头时,提供给用户切换摄像头的选项。

总之,通过使用 MediaDevices API 和

一、实现示例框架代码

使用 MediaDevices.getUserMedia() 方法获取摄像头媒体流,并将其传递给

// 调用摄像头function invokingCamera() {    // 注意本例需要在HTTPS协议网站中运行,新版本Chrome中getUserMedia接口在http下不再支持。    // 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象    if (navigator.mediaDevices === undefined) {        navigator.mediaDevices = {};    }    // 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia     // 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。    if (navigator.mediaDevices.getUserMedia === undefined) {        navigator.mediaDevices.getUserMedia = function (constraints) {            // 首先,如果有getUserMedia的话,就获得它            const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||                navigator.mozGetUserMedia;            // 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口            if (!getUserMedia) {                return Promise.reject(new Error(                    "getUserMedia is not implemented in this browser"));            }            // 否则,为老的navigator.getUserMedia方法包裹一个Promise            return new Promise(function (resolve, reject) {                getUserMedia.call(navigator, constraints, resolve, reject);            });        }    }    // 手机可视区域宽度(请通过相关API获取真实宽度)    const windowWidth = 375;    // 手机可视区域高度(请通过相关API获取真实高度)    const windowHeight = 700;    const constraints = {        audio: false,        video: {            // 前置摄像头            facingMode: "user",            // 该属性相当于手机端的高            width: Math.max(windowWidth, windowHeight) - 120,   // 减去 120 用于在页面底部放置拍照等功能按钮            // 该属性相当于手机端的宽            height: Math.min(windowWidth, windowHeight),        }    };    navigator.mediaDevices.getUserMedia(constraints)        .then(function (stream) {            const video = document.querySelector("camera");            // 旧的浏览器可能没有srcObject            if ("srcObject" in video) {                video.srcObject = stream;            } else {                // 防止在新的浏览器里使用它,应为它已经不再支持了                video.src = window.URL.createObjectURL(stream);            }            video.onloadedmetadata = function (e) {                video.play();            };        })        .catch(function (err) {            console.log(err.name + ": " + err.message);        });}

使用 HTML 的 标签来截取当前摄像头的画面,通过 上的 getContext("2d") 方法来绘制

function takeSnapshot() {    const canvas = document.createElement("canvas");    const ctx = canvas.getContext("2d");    const video = document.querySelector("video");    canvas.width = Math.min(video.videoWidth, video.videoHeight);    canvas.height = Math.max(video.videoWidth, video.videoHeight);    ctx.drawImage(video, 0, 0, canvas.width, canvas.height);    // ****** 镜像处理 ******    function getPixel(imageData, row, column) {        const uint8ClampedArray = imageData.data;        const width = imageData.width;        const height = imageData.height;        const pixel = [];        for (let i = 0; i < 4; i++) {            pixel.push(uint8ClampedArray[row * width * 4 + column * 4 + i]);        }        return pixel;    }    function setPixel(imageData, row, column, pixel) {        const uint8ClampedArray = imageData.data;        const width = imageData.width;        const height = imageData.height;        for (let i = 0; i < 4; i++) {            uint8ClampedArray[row * width * 4 + column * 4 + i] = pixel[i];        }    }    const mirrorImageData = ctx.createImageData(canvas.width, canvas.height);    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);    for (let h = 0; h < canvas.height; h++) {        for (let w = 0; w < canvas.width; w++) {            const pixel = getPixel(imageData, h, canvas.width - w - 1);            setPixel(mirrorImageData, h, w, pixel);        }    }    ctx.putImageData(mirrorImageData, 0, 0);    // ****** 镜像处理 ******    const base64 = canvas.toDataURL("image/jpeg");}

最后,使用 canvas.toDataURL() 方法将图像转换为 base64 格式,可以通过将其保存到本地或发送到服务器来存储照片

二、具体实现代码(基于uni-app)

布局代码:

JavaScript 代码:

export default {    data() {        return {            imageUrl: "",            // 媒体流,用于关闭摄像头            mediaStreamTrack: null,        };    },    onLoad() {        this.invokingCamera();    },    onUnload() {        this.handlePhotographCloseClick();    },    methods: {        invokingCamera() {            const self = this;            // 注意本例需要在HTTPS协议网站中运行,新版本Chrome中getUserMedia接口在http下不再支持。            // 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象            if (navigator.mediaDevices === undefined) {                navigator.mediaDevices = {};            }            // 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia             // 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。            if (navigator.mediaDevices.getUserMedia === undefined) {                navigator.mediaDevices.getUserMedia = function (constraints) {                    // 首先,如果有getUserMedia的话,就获得它                    const getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||                        navigator.mozGetUserMedia;                    // 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口                    if (!getUserMedia) {                        return Promise.reject(new Error(                            "getUserMedia is not implemented in this browser"));                    }                    // 否则,为老的navigator.getUserMedia方法包裹一个Promise                    return new Promise(function (resolve, reject) {                        getUserMedia.call(navigator, constraints, resolve, reject);                    });                }            }            uni.getSystemInfo({                success: function (res) {                    const constraints = {                        audio: false,                        video: {                            // 前置摄像头                            facingMode: "user",                            // 手机端相当于高                            width: Math.max(res.windowWidth, res.windowHeight) - 120,                            // 手机端相当于宽                            height: Math.min(res.windowWidth, res.windowHeight),                        }                    };                    navigator.mediaDevices.getUserMedia(constraints)                        .then(function (stream) {                            self.mediaStreamTrack = stream;                            const video = document.querySelector("video");                            // 旧的浏览器可能没有srcObject                            if ("srcObject" in video) {                                video.srcObject = stream;                            } else {                                // 防止在新的浏览器里使用它,应为它已经不再支持了                                video.src = window.URL.createObjectURL(stream);                            }                            video.onloadedmetadata = function (e) {                                video.play();                            };                        })                        .catch(function (err) {                            console.log(err.name + ": " + err.message);                        });                }            });        },        handlePhotographCloseClick() {            if (this.mediaStreamTrack) {                // 关闭摄像头                this.mediaStreamTrack.getTracks().forEach(function (track) {                    track.stop();                });                this.mediaStreamTrack = null;            }        },        handlePhotographClick() {            const self = this;            const canvas = document.createElement("canvas");            const ctx = canvas.getContext("2d");            const video = document.querySelector("video");            canvas.width = Math.min(video.videoWidth, video.videoHeight);            canvas.height = Math.max(video.videoWidth, video.videoHeight);            ctx.drawImage(video, 0, 0, canvas.width, canvas.height);            // ****** 镜像处理 ******            function getPixel(imageData, row, column) {                const uint8ClampedArray = imageData.data;                const width = imageData.width;                const height = imageData.height;                const pixel = [];                for (let i = 0; i < 4; i++) {                    pixel.push(uint8ClampedArray[row * width * 4 + column * 4 + i]);                }                return pixel;            }            function setPixel(imageData, row, column, pixel) {                const uint8ClampedArray = imageData.data;                const width = imageData.width;                const height = imageData.height;                for (let i = 0; i < 4; i++) {                    uint8ClampedArray[row * width * 4 + column * 4 + i] = pixel[i];                }            }            const mirrorImageData = ctx.createImageData(canvas.width, canvas.height);            const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);            for (let h = 0; h < canvas.height; h++) {                for (let w = 0; w < canvas.width; w++) {                    const pixel = getPixel(imageData, h, canvas.width - w - 1);                    setPixel(mirrorImageData, h, w, pixel);                }            }            ctx.putImageData(mirrorImageData, 0, 0);            // ****** 镜像处理 ******            self.$nextTick(() => {                const base64 = canvas.toDataURL("image/jpeg");                self.imageUrl = base64;                self.handlePhotographCloseClick();            });        },        handleAddPhotographClick() {            this.uploadImage();        },        uploadImage: function () {            const self = this;            uni.chooseImage({                count: 1,                sizeType: ["compressed"],                success: function (res) {                    self.handlePhotographCloseClick();                    const file = res.tempFiles[0];                    const reader = new FileReader();                    reader.readAsDataURL(file);                    reader.onload = function (e) {                        self.imageUrl = e.target.result;                    }                }            });        },    }};

样式代码:

最终效果展示:

关键词: 存储照片 前置摄像头 真实高度