[前端]HTML模拟实现一个基于摄像头的手势识别交互页面

发布于:2025-06-13 ⋅ 阅读:(15) ⋅ 点赞:(0)

这个HTML文件实现了一个智能手语识别系统,主要包含以下功能:

  1. 摄像头控制功能

    • 可以开启/关闭摄像头
    • 摄像头控制按钮可以切换状态(开启/关闭)
    • 页面加载时会预先获取摄像头权限(减少首次使用时提示)
  2. 手语识别功能

    • 开始识别按钮启动识别过程
    • 停止识别按钮停止识别
    • 识别结果显示在右侧区域
    • 识别历史记录保存在列表中
  3. 结果复制功能

    • 复制结果按钮可以将当前显示的识别结果复制到剪贴板
    • 复制成功后会显示提示信息
    • 兼容现代浏览器和旧版浏览器的复制方法
  4. 设置功能

    • 设置按钮打开设置弹窗
    • 可以调整识别灵敏度、输出语言和输出格式
    • 设置保存后可以应用到系统中
  5. 用户界面功能

    • 顶部导航栏包含系统名称和功能按钮
    • 左侧视频区域显示摄像头画面
    • 右侧结果区域显示识别结果和历史记录
    • 底部状态栏显示系统状态信息
  6. 其他功能

    • 帮助按钮(目前没有具体实现)
    • 设置弹窗可以关闭(点击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">&times;</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;
}

感兴趣的小伙伴可以在此基础之上丰富实现一下~~~