支持的特性
- 插入摄像头设备后,无需手动选择,自动显示摄像头画面,需要预先授权
- 支持多个摄像头切换显示
- 多个摄像头时支持 默认显示特定名称的摄像头
- 支持拍照
- 支持照片放大,缩小
显示效果
完整代码
<!DOCTYPE html>
<html lang="zh"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>摄像头实时检测,拍照</title><style type="text/css">body {text-align: center;}.flex {display: flex;flex-direction: row;/* align-items: center; */justify-content: center;}.hidden {display: none;}</style></head><body><h1>摄像头实时检测</h1><div class="flex"><div><video id="videoElement" autoplay playsinline controls></video><p><label for="videoSource">选择摄像头:</label><select id="videoSource"></select><span class="hidden" id="closeCamera" onclick="stopVideoStream()">关闭摄像头</span><span id="cameraStatus">摄像头状态:未连接或已被占用</span><button id="captureButton" onclick="takePicture()">拍照</button></p></div><div style="margin-left: 20px"><div style="border: 1px solid #ddd; box-sizing: content-box"><canvas id="paperCanvas" width="640px" height="480px"></canvas></div></div></div><script src="./paper-full.min.js"></script><script>const video = document.getElementById("videoElement");const cameraStatus = document.getElementById("cameraStatus");const captureButton = document.getElementById("captureButton");const videoSelect = document.getElementById("videoSource");let mediaStream;let raster;function gotDevices(deviceInfos) {videoSelect.value = "";videoSelect.innerHTML = "";for (let i = 0; i !== deviceInfos.length; ++i) {const deviceInfo = deviceInfos[i];const option = document.createElement("option");option.value = deviceInfo.deviceId;if (deviceInfo.kind === "videoinput") {option.text = deviceInfo.label || "摄像头 " + (videoSelect.length + 1);videoSelect.appendChild(option);if (deviceInfo.label.includes("TOOCAA")) {videoSelect.value = deviceInfo.deviceId;}}}}function getStream() {if (window.stream) {window.stream.getTracks().forEach((track) => {track.stop();});}const constraints = {video: {deviceId: { exact: videoSelect.value },},};navigator.mediaDevices.getUserMedia(constraints).then(gotStream).catch(handleError);}function gotStream(stream) {window.stream = stream;video.srcObject = stream;cameraStatus.textContent = "摄像头状态:已连接";const videoTrack = stream.getVideoTracks()[0];const settings = videoTrack.getSettings();captureButton.style.display = "inline-block";console.log(settings);}function startVideoStream() {if (navigator.mediaDevices) {navigator.mediaDevices.getUserMedia({ video: true }).then(function (stream) {mediaStream = stream; // 保存媒体流以便后续操作video.srcObject = stream;cameraStatus.textContent = "摄像头状态:已连接";const videoTrack = stream.getVideoTracks()[0];const settings = videoTrack.getSettings();captureButton.style.display = "inline-block";console.log(settings);}).catch(function (error) {console.error("无法获取摄像头:", error);cameraStatus.textContent = "摄像头状态:未连接或已被占用";captureButton.style.display = "none";});}}function stopVideoStream() {if (mediaStream) {mediaStream.getTracks().forEach((track) => track.stop()); // 停止所有媒体轨道}video.srcObject = null; // 清除视频源cameraStatus.textContent = "摄像头状态:已断开";captureButton.style.display = "none";}function initPaperCanvas() {paper.setup("paperCanvas");paper.view.element.addEventListener("wheel", function (event) {event.preventDefault();// 计算缩放因子var delta = event.deltaY > 0 ? 0.9 : 1.1; // 向下滚动缩小视图,向上滚动放大视图// 鼠标位置相对于视图的当前坐标var mousePosition = new paper.Point(event.offsetX, event.offsetY);var viewPosition = paper.view.viewToProject(mousePosition);// 应用缩放paper.view.scale(delta, viewPosition);});// 鼠标拖动事件处理移动const tool = new paper.Tool();var lastPoint = null; // 上一次鼠标位置var dragging = false;var lastViewCenter;tool.onMouseDown = (event) => {lastPoint = event.point;dragging = true;};tool.onMouseDrag = (event) => {if (dragging && lastPoint) {lastViewCenter = paper.view.center;const delta = lastPoint.subtract(event.point);paper.view.center = paper.view.center.add(delta);lastPoint = event.point.add(paper.view.center.subtract(lastViewCenter));}};tool.onMouseUp = () => {// 结束拖动dragging = false;};}window.onload = () => {if (navigator.mediaDevices) {navigator.mediaDevices.enumerateDevices().then(gotDevices).then(getStream).catch(handleError);}videoSelect.onchange = getStream;// 监听媒体设备变化事件if (navigator.mediaDevices) {navigator.mediaDevices.addEventListener("devicechange", function (event) {// 尝试重新获取媒体流以检查摄像头是否仍然可用navigator.mediaDevices.getUserMedia({ video: true }).then(function () {navigator.mediaDevices.enumerateDevices().then(gotDevices).then(getStream).catch(handleError);// startVideoStream(); // 摄像头已连接,重新开始视频流}).catch(function () {stopVideoStream(); // 摄像头已断开,停止视频流并更新状态// destoryCanvas();});});}initPaperCanvas();};// 拍照功能function takePicture() {let canvas = document.createElement("canvas");canvas.width = video.videoWidth;canvas.height = video.videoHeight;const context = canvas.getContext("2d");context.drawImage(video, 0, 0, canvas.width, canvas.height);displayPictureOnPaper(canvas.toDataURL("image/png"));canvas = null;}function displayPictureOnPaper(imageData) {raster = new paper.Raster({source: imageData,position: paper.view.center,});}function destoryCanvas() {if (raster) {raster.remove();}}function handleError(error) {console.error("无法获取摄像头:", error);cameraStatus.textContent = "摄像头状态:未连接或已被占用";captureButton.style.display = "none";}</script></body>
</html>