[前端]HTML模拟实现一个基于摄像头的手势识别交互页面
这个HTML文件实现了一个智能手语识别系统,主要包含以下功能:
-
摄像头控制功能:
- 可以开启/关闭摄像头
- 摄像头控制按钮可以切换状态(开启/关闭)
- 页面加载时会预先获取摄像头权限(减少首次使用时提示)
-
手语识别功能:
- 开始识别按钮启动识别过程
- 停止识别按钮停止识别
- 识别结果显示在右侧区域
- 识别历史记录保存在列表中
-
结果复制功能:
- 复制结果按钮可以将当前显示的识别结果复制到剪贴板
- 复制成功后会显示提示信息
- 兼容现代浏览器和旧版浏览器的复制方法
-
设置功能:
- 设置按钮打开设置弹窗
- 可以调整识别灵敏度、输出语言和输出格式
- 设置保存后可以应用到系统中
-
用户界面功能:
- 顶部导航栏包含系统名称和功能按钮
- 左侧视频区域显示摄像头画面
- 右侧结果区域显示识别结果和历史记录
- 底部状态栏显示系统状态信息
-
其他功能:
- 帮助按钮(目前没有具体实现)
- 设置弹窗可以关闭(点击X或外部区域)
这个系统主要是一个手语识别的演示界面,包含了基本的UI交互和功能流程,但实际的手语识别算法部分是用模拟数据代替的(代码中的simulateRecognition函数)。
效果展示
代码
代码
html
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>智能手语识别系统</title><link rel="stylesheet" href="styles.css">
</head>
<body><!-- 顶部导航栏 --><header class="app-header"><div class="logo">手语识别助手</div><nav class="main-nav"><ul><li><a href="#" class="active">首页</a></li><li><a href="#">设置</a></li><li><a href="#">帮助中心</a></li><li><a href="#">关于我们</a></li></ul></nav><div class="user-controls"><button id="settings-btn">设置</button><button id="help-btn">帮助</button></div></header><!-- 主内容区 --><main class="app-content"><!-- 左侧视频区域 --><section class="video-section"><div class="video-container"><video id="capture-video" autoplay muted></video><canvas id="output-canvas" style="display:none;"></canvas></div><div class="video-controls"><button id="start-btn">开始识别</button><button id="stop-btn">停止识别</button><button id="camera-toggle-btn">开启摄像头</button> <!-- 修改为可切换按钮 --><select id="camera-select"><option value="0">默认摄像头</option><!-- 动态加载摄像头选项 --></select></div></section><!-- 右侧结果区域 --><section class="result-section"><h2>识别结果</h2><div class="result-display"><div id="current-result" class="current-result"><p>等待识别...</p></div><div id="history-results" class="history-results"><h3>历史记录</h3><ul id="result-list"></ul></div></div><div class="result-actions"><button id="copy-btn">复制结果</button><button id="speak-btn">语音播报</button></div></section></main><!-- 底部状态栏 --><footer class="app-footer"><div id="status-indicator" class="status-indicator"><span class="status-dot"></span><span id="status-text">系统就绪</span></div><div id="version-info">v1.0.0</div></footer><!-- 设置弹窗 --><div id="settings-modal" class="modal"><div class="modal-content"><span class="close">×</span><h2>系统设置</h2><form id="settings-form"><div class="form-group"><label for="sensitivity">识别灵敏度</label><input type="range" id="sensitivity" min="1" max="10" value="5"></div><div class="form-group"><label for="language">输出语言</label><select id="language"><option value="zh-CN">中文</option><option value="en-US">英文</option></select></div><div class="form-group"><label for="output-format">输出格式</label><select id="output-format"><option value="text">纯文本</option><option value="speech">语音</option><option value="both">文本+语音</option></select></div><button type="submit" class="save-btn">保存设置</button></form></div></div><script src="app.js"></script>
</body>
</html>
js
app.js
document.addEventListener('DOMContentLoaded', function() {// 获取DOM元素const video = document.getElementById('capture-video');const canvas = document.getElementById('output-canvas');const startBtn = document.getElementById('start-btn');const stopBtn = document.getElementById('stop-btn');const cameraToggleBtn = document.getElementById('camera-toggle-btn');const cameraSelect = document.getElementById('camera-select');const statusText = document.getElementById('status-text');const resultDisplay = document.getElementById('current-result');const resultList = document.getElementById('result-list');const settingsBtn = document.getElementById('settings-btn');const settingsModal = document.getElementById('settings-modal');const closeBtn = document.querySelector('.close');const copyBtn = document.getElementById('copy-btn'); // 获取复制按钮let isCameraOn = false;let currentStream = null;// 页面加载时预先获取摄像头权限(静默模式)async function preloadCameraPermission() {try {const stream = await navigator.mediaDevices.getUserMedia({video: { facingMode: 'environment' }});stream.getTracks().forEach(track => track.stop());return true;} catch (err) {return false;}}// 初始化摄像头(实际使用)async function initCamera() {try {const stream = await navigator.mediaDevices.getUserMedia({video: { facingMode: 'environment' }});video.srcObject = stream;currentStream = stream;statusText.textContent = '摄像头已就绪';isCameraOn = true;cameraToggleBtn.textContent = '关闭摄像头';return true;} catch (err) {statusText.textContent = '无法访问摄像头: ' + err.message;console.error('摄像头错误:', err);return false;}}// 复制结果到剪贴板async function copyToClipboard(text) {try {// 使用现代剪贴板APIawait navigator.clipboard.writeText(text);// 显示复制成功提示showCopySuccessMessage();return true;} catch (err) {// 如果现代API失败,使用传统方法console.error('剪贴板API失败:', err);return fallbackCopyToClipboard(text);}}// 传统复制方法(兼容旧浏览器)function fallbackCopyToClipboard(text) {// 创建临时文本区域const textArea = document.createElement('textarea');textArea.value = text;textArea.style.position = 'fixed';textArea.style.left = '-999999px';textArea.style.top = '-999999px';document.body.appendChild(textArea);// 选择文本textArea.select();textArea.setSelectionRange(0, 99999); // 适用于移动设备try {// 执行复制命令const successful = document.execCommand('copy');if (successful) {showCopySuccessMessage();return true;}} catch (err) {console.error('传统复制方法失败:', err);} finally {// 移除临时元素document.body.removeChild(textArea);}return false;}// 显示复制成功提示function showCopySuccessMessage() {// 创建提示元素const message = document.createElement('div');message.className = 'copy-success-message';message.textContent = '复制成功!';// 添加样式message.style.position = 'fixed';message.style.bottom = '20px';message.style.right = '20px';message.style.backgroundColor = '#4CAF50';message.style.color = 'white';message.style.padding = '10px 20px';message.style.borderRadius = '4px';message.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';message.style.zIndex = '1000';// 添加到页面document.body.appendChild(message);// 3秒后自动消失setTimeout(() => {message.style.opacity = '0';message.style.transition = 'opacity 0.5s ease';setTimeout(() => {document.body.removeChild(message);}, 500);}, 3000);}// 可切换的摄像头控制功能cameraToggleBtn.addEventListener('click', async function() {if (isCameraOn) {if (currentStream) {const tracks = currentStream.getTracks();tracks.forEach(track => track.stop());video.srcObject = null;currentStream = null;statusText.textContent = '摄像头已关闭';isCameraOn = false;cameraToggleBtn.textContent = '开启摄像头';}} else {await initCamera();}});// 开始识别startBtn.addEventListener('click', async function() {if (!video.srcObject) {statusText.textContent = '请先开启摄像头';return;}statusText.textContent = '正在识别...';// 这里调用实际的识别API// 示例中使用模拟数据simulateRecognition();});// 停止识别stopBtn.addEventListener('click', function() {statusText.textContent = '识别已停止';// 实际应用中需要停止识别进程});// 模拟识别过程function simulateRecognition() {const sampleResults = ['你好','谢谢','再见','请问','多少','时间'];let index = 0;const interval = setInterval(() => {if (index >= sampleResults.length) {clearInterval(interval);statusText.textContent = '识别完成';return;}const result = sampleResults[index];resultDisplay.innerHTML = `<p>${result}</p>`;addResultToHistory(result);index++;}, 2000);}// 添加结果到历史记录function addResultToHistory(result) {const li = document.createElement('li');li.textContent = result;resultList.appendChild(li);resultList.scrollTop = resultList.scrollHeight;}// 复制按钮点击事件copyBtn.addEventListener('click', function() {// 获取当前显示的结果const currentResult = resultDisplay.querySelector('p');if (currentResult) {const textToCopy = currentResult.textContent;copyToClipboard(textToCopy);} else {alert('没有可复制的内容');}});// 设置按钮点击事件settingsBtn.addEventListener('click', function() {settingsModal.style.display = 'block';});// 关闭设置弹窗closeBtn.addEventListener('click', function() {settingsModal.style.display = 'none';});// 点击弹窗外部关闭window.addEventListener('click', function(event) {if (event.target === settingsModal) {settingsModal.style.display = 'none';}});// 初始化时默认关闭摄像头preloadCameraPermission().then(hasPermission => {if (!hasPermission) {console.warn('未预先获取权限,首次使用时仍会提示');}});
});
css
style.css
/* 基础样式 */
* {margin: 0;padding: 0;box-sizing: border-box;
}body {font-family: "Microsoft YaHei", sans-serif;line-height: 1.6;color: #333;background-color: #f5f5f5;
}/* 头部导航 */
.app-header {display: flex;justify-content: space-between;align-items: center;padding: 1rem 2rem;background-color: #1e88e5;color: white;box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}.logo {font-size: 1.5rem;font-weight: bold;
}.main-nav ul {display: flex;list-style: none;
}.main-nav li {margin: 0 1rem;
}.main-nav a {color: white;text-decoration: none;
}.main-nav a.active {font-weight: bold;border-bottom: 2px solid white;
}/* 主内容区 */
.app-content {display: flex;padding: 2rem;gap: 2rem;
}.video-section, .result-section {flex: 1;background: white;border-radius: 8px;padding: 1.5rem;box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}.video-container {width: 100%;height: 400px;background: #eee;border-radius: 4px;overflow: hidden;position: relative;
}#capture-video {width: 100%;height: 100%;object-fit: cover;
}.video-controls {margin-top: 1rem;display: flex;gap: 0.5rem;
}.video-controls button {padding: 0.5rem 1rem;background: #1e88e5;color: white;border: none;border-radius: 4px;cursor: pointer;
}.video-controls select {padding: 0.5rem;border-radius: 4px;border: 1px solid #ddd;
}/* 结果区域 */
.result-display {margin-top: 1.5rem;
}.current-result {background: #f0f7ff;padding: 1rem;border-radius: 4px;min-height: 100px;border-left: 4px solid #1e88e5;
}.history-results h3 {margin-bottom: 0.5rem;
}#result-list {list-style: none;max-height: 200px;overflow-y: auto;
}#result-list li {padding: 0.5rem;border-bottom: 1px solid #eee;
}.result-actions {margin-top: 1.5rem;display: flex;gap: 0.5rem;
}.result-actions button {padding: 0.5rem 1rem;background: #1e88e5;color: white;border: none;border-radius: 4px;cursor: pointer;
}/* 底部状态栏 */
.app-footer {display: flex;justify-content: space-between;align-items: center;padding: 1rem 2rem;background-color: #f5f5f5;border-top: 1px solid #eee;
}.status-indicator {display: flex;align-items: center;gap: 0.5rem;
}.status-dot {width: 10px;height: 10px;background-color: #4caf50;border-radius: 50%;
}/* 设置弹窗 */
.modal {display: none;position: fixed;z-index: 1000;left: 0;top: 0;width: 100%;height: 100%;background-color: rgba(0,0,0,0.5);
}.modal-content {background-color: white;margin: 15% auto;padding: 2rem;border-radius: 8px;width: 50%;max-width: 500px;
}.close {float: right;font-size: 1.5rem;cursor: pointer;
}.form-group {margin-bottom: 1rem;
}.form-group label {display: block;margin-bottom: 0.5rem;
}.form-group input, .form-group select {width: 100%;padding: 0.5rem;border: 1px solid #ddd;border-radius: 4px;
}.save-btn {padding: 0.5rem 1rem;background: #1e88e5;color: white;border: none;border-radius: 4px;cursor: pointer;
}/* 响应式设计 */
@media (max-width: 768px) {.app-content {flex-direction: column;}.modal-content {width: 90%;margin: 30% auto;}
}/* 在styles.css文件中添加以下样式 */
.copy-success-message {position: fixed;bottom: 20px;right: 20px;background-color: #4CAF50;color: white;padding: 10px 20px;border-radius: 4px;box-shadow: 0 2px 5px rgba(0,0,0,0.2);z-index: 1000;opacity: 1;transition: opacity 0.5s ease;
}
感兴趣的小伙伴可以在此基础之上丰富实现一下~~~