这个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 {
// 使用现代剪贴板API
await 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;
}
感兴趣的小伙伴可以在此基础之上丰富实现一下~~~