Skip to content

3.2 输入控件

学习目标

  • 掌握HTML5表单中各种输入控件的用法
  • 理解不同输入类型的特点和适用场景
  • 学会合理选择和使用输入控件
  • 掌握输入控件的属性和事件处理

3.2.1 文本输入(input type="text")

基本语法

html
<input type="text" name="username" id="username">

详细介绍

文本输入框是最基础和常用的输入控件,用于接收用户输入的单行文本信息。

重要属性

1. 基础属性

html
<input type="text" 
       name="username" 
       id="username" 
       value="默认值"
       placeholder="请输入用户名"
       maxlength="20"
       minlength="3"
       size="30"
       readonly
       disabled
       required>

属性说明:

  • name: 表单提交时的字段名
  • id: 元素的唯一标识符
  • value: 默认值
  • placeholder: 提示文本
  • maxlength: 最大字符长度
  • minlength: 最小字符长度
  • size: 输入框的可见宽度
  • readonly: 只读,用户无法修改
  • disabled: 禁用状态
  • required: 必填字段

2. 实际应用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>文本输入示例</title>
    <style>
        .form-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        input[type="text"] {
            width: 100%;
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-size: 14px;
        }
        input[type="text"]:focus {
            outline: none;
            border-color: #007bff;
            box-shadow: 0 0 0 2px rgba(0,123,255,0.25);
        }
    </style>
</head>
<body>
    <form>
        <div class="form-group">
            <label for="fullname">姓名:</label>
            <input type="text" id="fullname" name="fullname" 
                   placeholder="请输入您的全名" required>
        </div>
        
        <div class="form-group">
            <label for="nickname">昵称:</label>
            <input type="text" id="nickname" name="nickname" 
                   placeholder="请输入昵称" maxlength="10" minlength="2">
        </div>
        
        <div class="form-group">
            <label for="company">公司:</label>
            <input type="text" id="company" name="company" 
                   value="示例公司" readonly>
        </div>
    </form>
</body>
</html>

易错点提醒

  1. 不要忘记设置name属性:没有name属性的输入框不会被提交
  2. placeholder与value的区别:placeholder是提示文本,value是实际值
  3. maxlength与minlength的限制:只对用户输入有效,不影响JavaScript设置的值

3.2.2 密码输入(input type="password")

基本语法

html
<input type="password" name="password" id="password">

详细介绍

密码输入框会自动隐藏用户输入的字符,通常显示为星号或圆点,保护用户隐私。

重要特性

1. 基础使用

html
<form>
    <div class="form-group">
        <label for="password">密码:</label>
        <input type="password" id="password" name="password" 
               placeholder="请输入密码" required>
    </div>
    
    <div class="form-group">
        <label for="confirm-password">确认密码:</label>
        <input type="password" id="confirm-password" name="confirm-password" 
               placeholder="请再次输入密码" required>
    </div>
</form>

2. 密码强度验证示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>密码输入示例</title>
    <style>
        .password-container {
            position: relative;
            max-width: 300px;
        }
        
        .password-input {
            width: 100%;
            padding: 8px 40px 8px 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
        }
        
        .password-toggle {
            position: absolute;
            right: 10px;
            top: 50%;
            transform: translateY(-50%);
            cursor: pointer;
            user-select: none;
        }
        
        .password-strength {
            margin-top: 5px;
            font-size: 12px;
        }
        
        .weak { color: #dc3545; }
        .medium { color: #ffc107; }
        .strong { color: #28a745; }
    </style>
</head>
<body>
    <div class="password-container">
        <input type="password" id="password" class="password-input" 
               placeholder="请输入密码" oninput="checkPasswordStrength()">
        <span class="password-toggle" onclick="togglePassword()">👁️</span>
        <div id="password-strength" class="password-strength"></div>
    </div>

    <script>
        function togglePassword() {
            const passwordInput = document.getElementById('password');
            const toggleBtn = document.querySelector('.password-toggle');
            
            if (passwordInput.type === 'password') {
                passwordInput.type = 'text';
                toggleBtn.textContent = '🙈';
            } else {
                passwordInput.type = 'password';
                toggleBtn.textContent = '👁️';
            }
        }
        
        function checkPasswordStrength() {
            const password = document.getElementById('password').value;
            const strengthDiv = document.getElementById('password-strength');
            
            if (password.length === 0) {
                strengthDiv.textContent = '';
                return;
            }
            
            let strength = 0;
            
            // 检查长度
            if (password.length >= 8) strength++;
            // 检查数字
            if (/\d/.test(password)) strength++;
            // 检查小写字母
            if (/[a-z]/.test(password)) strength++;
            // 检查大写字母
            if (/[A-Z]/.test(password)) strength++;
            // 检查特殊字符
            if (/[^a-zA-Z0-9]/.test(password)) strength++;
            
            if (strength < 2) {
                strengthDiv.textContent = '密码强度:弱';
                strengthDiv.className = 'password-strength weak';
            } else if (strength < 4) {
                strengthDiv.textContent = '密码强度:中等';
                strengthDiv.className = 'password-strength medium';
            } else {
                strengthDiv.textContent = '密码强度:强';
                strengthDiv.className = 'password-strength strong';
            }
        }
    </script>
</body>
</html>

安全性考虑

  1. 不要在客户端验证密码:真正的验证必须在服务器端进行
  2. 使用HTTPS:确保密码传输过程中的安全性
  3. 不要在代码中硬编码密码:避免在HTML或JavaScript中直接写入密码

3.2.3 单选按钮(input type="radio")

基本语法

html
<input type="radio" name="gender" value="male" id="male">
<input type="radio" name="gender" value="female" id="female">

详细介绍

单选按钮用于在多个选项中选择一个,具有相同name属性的单选按钮组成一个选择组。

重要特性

1. 基础使用

html
<form>
    <fieldset>
        <legend>选择您的性别:</legend>
        <input type="radio" name="gender" value="male" id="male">
        <label for="male">男性</label>
        
        <input type="radio" name="gender" value="female" id="female">
        <label for="female">女性</label>
        
        <input type="radio" name="gender" value="other" id="other">
        <label for="other">其他</label>
    </fieldset>
</form>

2. 实际应用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>单选按钮示例</title>
    <style>
        fieldset {
            border: 1px solid #ccc;
            border-radius: 4px;
            padding: 15px;
            margin-bottom: 20px;
        }
        
        legend {
            font-weight: bold;
            padding: 0 10px;
        }
        
        .radio-group {
            display: flex;
            flex-direction: column;
            gap: 10px;
        }
        
        .radio-item {
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .radio-item input[type="radio"] {
            margin: 0;
        }
        
        .radio-item label {
            cursor: pointer;
            user-select: none;
        }
        
        .radio-item:hover {
            background-color: #f8f9fa;
            padding: 5px;
            border-radius: 3px;
        }
    </style>
</head>
<body>
    <form>
        <fieldset>
            <legend>选择您的年龄段:</legend>
            <div class="radio-group">
                <div class="radio-item">
                    <input type="radio" name="age" value="under18" id="under18">
                    <label for="under18">18岁以下</label>
                </div>
                <div class="radio-item">
                    <input type="radio" name="age" value="18-30" id="age18-30">
                    <label for="age18-30">18-30岁</label>
                </div>
                <div class="radio-item">
                    <input type="radio" name="age" value="31-50" id="age31-50">
                    <label for="age31-50">31-50岁</label>
                </div>
                <div class="radio-item">
                    <input type="radio" name="age" value="over50" id="over50">
                    <label for="over50">50岁以上</label>
                </div>
            </div>
        </fieldset>
        
        <fieldset>
            <legend>选择您的职业:</legend>
            <div class="radio-group">
                <div class="radio-item">
                    <input type="radio" name="job" value="student" id="student" checked>
                    <label for="student">学生</label>
                </div>
                <div class="radio-item">
                    <input type="radio" name="job" value="developer" id="developer">
                    <label for="developer">开发者</label>
                </div>
                <div class="radio-item">
                    <input type="radio" name="job" value="designer" id="designer">
                    <label for="designer">设计师</label>
                </div>
                <div class="radio-item">
                    <input type="radio" name="job" value="other" id="job-other">
                    <label for="job-other">其他</label>
                </div>
            </div>
        </fieldset>
    </form>
    
    <script>
        // 监听单选按钮变化
        document.querySelectorAll('input[type="radio"]').forEach(radio => {
            radio.addEventListener('change', function() {
                console.log(`选择了 ${this.name}: ${this.value}`);
            });
        });
    </script>
</body>
</html>

易错点提醒

  1. name属性必须相同:同一组的单选按钮必须有相同的name属性
  2. value属性很重要:每个单选按钮都应该有不同的value值
  3. label的关联:使用for属性或者包裹input元素
  4. 默认选中:使用checked属性设置默认选中项

3.2.4 复选框(input type="checkbox")

基本语法

html
<input type="checkbox" name="hobby" value="reading" id="reading">

详细介绍

复选框允许用户选择多个选项,每个复选框都是独立的,可以同时选中多个。

重要特性

1. 基础使用

html
<form>
    <fieldset>
        <legend>选择您的爱好:</legend>
        <input type="checkbox" name="hobby" value="reading" id="reading">
        <label for="reading">阅读</label>
        
        <input type="checkbox" name="hobby" value="music" id="music">
        <label for="music">音乐</label>
        
        <input type="checkbox" name="hobby" value="sports" id="sports">
        <label for="sports">运动</label>
    </fieldset>
</form>

2. 实际应用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>复选框示例</title>
    <style>
        .checkbox-container {
            max-width: 400px;
            margin: 20px auto;
        }
        
        fieldset {
            border: 1px solid #ccc;
            border-radius: 4px;
            padding: 15px;
            margin-bottom: 20px;
        }
        
        legend {
            font-weight: bold;
            padding: 0 10px;
        }
        
        .checkbox-group {
            display: flex;
            flex-direction: column;
            gap: 10px;
        }
        
        .checkbox-item {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 5px;
            border-radius: 3px;
            transition: background-color 0.2s;
        }
        
        .checkbox-item:hover {
            background-color: #f8f9fa;
        }
        
        .checkbox-item input[type="checkbox"] {
            margin: 0;
        }
        
        .checkbox-item label {
            cursor: pointer;
            user-select: none;
            flex: 1;
        }
        
        .select-all {
            font-weight: bold;
            border-bottom: 1px solid #eee;
            padding-bottom: 10px;
            margin-bottom: 10px;
        }
        
        .summary {
            margin-top: 20px;
            padding: 10px;
            background-color: #f8f9fa;
            border-radius: 4px;
        }
    </style>
</head>
<body>
    <div class="checkbox-container">
        <fieldset>
            <legend>选择您感兴趣的技术:</legend>
            
            <div class="checkbox-item select-all">
                <input type="checkbox" id="select-all">
                <label for="select-all">全选</label>
            </div>
            
            <div class="checkbox-group">
                <div class="checkbox-item">
                    <input type="checkbox" name="tech" value="html" id="html">
                    <label for="html">HTML5</label>
                </div>
                <div class="checkbox-item">
                    <input type="checkbox" name="tech" value="css" id="css">
                    <label for="css">CSS3</label>
                </div>
                <div class="checkbox-item">
                    <input type="checkbox" name="tech" value="javascript" id="javascript">
                    <label for="javascript">JavaScript</label>
                </div>
                <div class="checkbox-item">
                    <input type="checkbox" name="tech" value="react" id="react">
                    <label for="react">React</label>
                </div>
                <div class="checkbox-item">
                    <input type="checkbox" name="tech" value="vue" id="vue">
                    <label for="vue">Vue.js</label>
                </div>
                <div class="checkbox-item">
                    <input type="checkbox" name="tech" value="nodejs" id="nodejs">
                    <label for="nodejs">Node.js</label>
                </div>
            </div>
        </fieldset>
        
        <div class="summary">
            <h4>已选择的技术:</h4>
            <p id="selected-summary">暂未选择</p>
        </div>
    </div>
    
    <script>
        const selectAllCheckbox = document.getElementById('select-all');
        const techCheckboxes = document.querySelectorAll('input[name="tech"]');
        const summaryElement = document.getElementById('selected-summary');
        
        // 全选/取消全选功能
        selectAllCheckbox.addEventListener('change', function() {
            techCheckboxes.forEach(checkbox => {
                checkbox.checked = this.checked;
            });
            updateSummary();
        });
        
        // 监听各个技术复选框的变化
        techCheckboxes.forEach(checkbox => {
            checkbox.addEventListener('change', function() {
                // 更新全选状态
                const checkedCount = Array.from(techCheckboxes).filter(cb => cb.checked).length;
                selectAllCheckbox.checked = checkedCount === techCheckboxes.length;
                selectAllCheckbox.indeterminate = checkedCount > 0 && checkedCount < techCheckboxes.length;
                
                updateSummary();
            });
        });
        
        // 更新摘要
        function updateSummary() {
            const selectedTechs = Array.from(techCheckboxes)
                .filter(cb => cb.checked)
                .map(cb => cb.nextElementSibling.textContent);
            
            if (selectedTechs.length === 0) {
                summaryElement.textContent = '暂未选择';
            } else {
                summaryElement.textContent = selectedTechs.join(', ');
            }
        }
    </script>
</body>
</html>

高级特性

1. 三态复选框(indeterminate)

html
<style>
    .tree-checkbox {
        margin-left: 20px;
    }
</style>

<div>
    <input type="checkbox" id="parent-checkbox">
    <label for="parent-checkbox">前端技术</label>
    
    <div class="tree-checkbox">
        <input type="checkbox" name="frontend" value="html" id="child-html">
        <label for="child-html">HTML</label>
        
        <input type="checkbox" name="frontend" value="css" id="child-css">
        <label for="child-css">CSS</label>
        
        <input type="checkbox" name="frontend" value="js" id="child-js">
        <label for="child-js">JavaScript</label>
    </div>
</div>

<script>
    const parentCheckbox = document.getElementById('parent-checkbox');
    const childCheckboxes = document.querySelectorAll('input[name="frontend"]');
    
    parentCheckbox.addEventListener('change', function() {
        childCheckboxes.forEach(child => {
            child.checked = this.checked;
        });
    });
    
    childCheckboxes.forEach(child => {
        child.addEventListener('change', function() {
            const checkedCount = Array.from(childCheckboxes).filter(cb => cb.checked).length;
            
            if (checkedCount === 0) {
                parentCheckbox.checked = false;
                parentCheckbox.indeterminate = false;
            } else if (checkedCount === childCheckboxes.length) {
                parentCheckbox.checked = true;
                parentCheckbox.indeterminate = false;
            } else {
                parentCheckbox.checked = false;
                parentCheckbox.indeterminate = true;
            }
        });
    });
</script>

易错点提醒

  1. 复选框的value问题:未选中时不会提交到服务器
  2. name属性的设置:相关的复选框可以使用相同的name(需要服务器端处理数组)
  3. indeterminate状态:只能通过JavaScript设置,不能通过HTML属性设置

3.2.5 文件上传(input type="file")

基本语法

html
<input type="file" name="file" id="file">

详细介绍

文件上传控件允许用户选择本地文件并上传到服务器。

重要属性

1. 基础属性

html
<input type="file" 
       name="file" 
       id="file"
       accept="image/*,.pdf,.doc,.docx"
       multiple
       required>

属性说明:

  • accept: 限制文件类型
  • multiple: 允许选择多个文件
  • required: 必须选择文件

2. 实际应用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>文件上传示例</title>
    <style>
        .upload-container {
            max-width: 500px;
            margin: 20px auto;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 8px;
        }
        
        .upload-area {
            border: 2px dashed #ccc;
            border-radius: 4px;
            padding: 20px;
            text-align: center;
            cursor: pointer;
            transition: all 0.3s ease;
        }
        
        .upload-area:hover {
            border-color: #007bff;
            background-color: #f8f9fa;
        }
        
        .upload-area.dragover {
            border-color: #007bff;
            background-color: #e3f2fd;
        }
        
        .file-input {
            display: none;
        }
        
        .upload-button {
            background-color: #007bff;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            margin-top: 10px;
        }
        
        .file-list {
            margin-top: 20px;
        }
        
        .file-item {
            display: flex;
            align-items: center;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            margin-bottom: 10px;
        }
        
        .file-info {
            flex: 1;
            margin-left: 10px;
        }
        
        .file-name {
            font-weight: bold;
        }
        
        .file-size {
            color: #666;
            font-size: 14px;
        }
        
        .file-remove {
            background-color: #dc3545;
            color: white;
            border: none;
            padding: 5px 10px;
            border-radius: 3px;
            cursor: pointer;
        }
        
        .preview-image {
            max-width: 100px;
            max-height: 100px;
            object-fit: cover;
            border-radius: 4px;
        }
    </style>
</head>
<body>
    <div class="upload-container">
        <h3>文件上传示例</h3>
        
        <div class="upload-area" onclick="document.getElementById('file-input').click()">
            <p>点击或拖拽文件到此区域上传</p>
            <p>支持图片、PDF、Word文档</p>
            <input type="file" id="file-input" class="file-input" 
                   accept="image/*,.pdf,.doc,.docx" multiple>
            <button type="button" class="upload-button">选择文件</button>
        </div>
        
        <div class="file-list" id="file-list"></div>
    </div>
    
    <script>
        const fileInput = document.getElementById('file-input');
        const uploadArea = document.querySelector('.upload-area');
        const fileList = document.getElementById('file-list');
        let selectedFiles = [];
        
        // 文件选择处理
        fileInput.addEventListener('change', function(e) {
            handleFiles(e.target.files);
        });
        
        // 拖拽上传
        uploadArea.addEventListener('dragover', function(e) {
            e.preventDefault();
            uploadArea.classList.add('dragover');
        });
        
        uploadArea.addEventListener('dragleave', function(e) {
            e.preventDefault();
            uploadArea.classList.remove('dragover');
        });
        
        uploadArea.addEventListener('drop', function(e) {
            e.preventDefault();
            uploadArea.classList.remove('dragover');
            handleFiles(e.dataTransfer.files);
        });
        
        // 处理文件
        function handleFiles(files) {
            Array.from(files).forEach(file => {
                // 检查文件类型
                if (!isValidFile(file)) {
                    alert(`文件 "${file.name}" 类型不支持`);
                    return;
                }
                
                // 检查文件大小 (限制为10MB)
                if (file.size > 10 * 1024 * 1024) {
                    alert(`文件 "${file.name}" 大小超过10MB`);
                    return;
                }
                
                selectedFiles.push(file);
                displayFile(file);
            });
        }
        
        // 验证文件类型
        function isValidFile(file) {
            const validTypes = [
                'image/jpeg', 'image/png', 'image/gif', 'image/webp',
                'application/pdf',
                'application/msword',
                'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
            ];
            return validTypes.includes(file.type);
        }
        
        // 显示文件
        function displayFile(file) {
            const fileItem = document.createElement('div');
            fileItem.className = 'file-item';
            
            let preview = '';
            if (file.type.startsWith('image/')) {
                const reader = new FileReader();
                reader.onload = function(e) {
                    const img = fileItem.querySelector('.preview-image');
                    if (img) {
                        img.src = e.target.result;
                    }
                };
                reader.readAsDataURL(file);
                preview = '<img class="preview-image" src="" alt="预览">';
            } else {
                preview = '<div style="width: 100px; height: 100px; background-color: #f0f0f0; display: flex; align-items: center; justify-content: center; border-radius: 4px;">📄</div>';
            }
            
            fileItem.innerHTML = `
                ${preview}
                <div class="file-info">
                    <div class="file-name">${file.name}</div>
                    <div class="file-size">${formatFileSize(file.size)}</div>
                </div>
                <button class="file-remove" onclick="removeFile('${file.name}')">删除</button>
            `;
            
            fileList.appendChild(fileItem);
        }
        
        // 格式化文件大小
        function formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes';
            const k = 1024;
            const sizes = ['Bytes', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }
        
        // 删除文件
        function removeFile(fileName) {
            selectedFiles = selectedFiles.filter(file => file.name !== fileName);
            const fileItems = fileList.querySelectorAll('.file-item');
            fileItems.forEach(item => {
                if (item.querySelector('.file-name').textContent === fileName) {
                    item.remove();
                }
            });
        }
    </script>
</body>
</html>

高级用法

1. 文件上传进度条

html
<div class="upload-progress" style="display: none;">
    <div class="progress-bar">
        <div class="progress-fill" style="width: 0%;"></div>
    </div>
    <div class="progress-text">0%</div>
</div>

<script>
function uploadFile(file) {
    const formData = new FormData();
    formData.append('file', file);
    
    const xhr = new XMLHttpRequest();
    
    // 上传进度
    xhr.upload.addEventListener('progress', function(e) {
        if (e.lengthComputable) {
            const percentComplete = (e.loaded / e.total) * 100;
            updateProgress(percentComplete);
        }
    });
    
    // 上传完成
    xhr.addEventListener('load', function() {
        if (xhr.status === 200) {
            console.log('上传成功');
        } else {
            console.error('上传失败');
        }
    });
    
    xhr.open('POST', '/upload');
    xhr.send(formData);
}

function updateProgress(percent) {
    const progressBar = document.querySelector('.progress-fill');
    const progressText = document.querySelector('.progress-text');
    
    progressBar.style.width = percent + '%';
    progressText.textContent = Math.round(percent) + '%';
}
</script>

易错点提醒

  1. 表单enctype设置:文件上传必须设置enctype="multipart/form-data"
  2. 文件大小限制:需要同时在前端和后端进行限制
  3. 文件类型验证:不能仅依赖accept属性,需要JavaScript验证
  4. 安全性考虑:上传的文件需要在服务器端进行安全检查

3.2.6 隐藏域(input type="hidden")

基本语法

html
<input type="hidden" name="user_id" value="12345">

详细介绍

隐藏域用于存储不需要用户看到但需要提交到服务器的数据。

使用场景

1. 用户ID传递

html
<form action="/update-profile" method="POST">
    <input type="hidden" name="user_id" value="12345">
    <input type="hidden" name="csrf_token" value="abc123def456">
    
    <label for="username">用户名:</label>
    <input type="text" id="username" name="username" value="张三">
    
    <button type="submit">更新资料</button>
</form>

2. 多步骤表单

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>多步骤表单示例</title>
    <style>
        .form-container {
            max-width: 400px;
            margin: 20px auto;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 8px;
        }
        
        .step {
            display: none;
        }
        
        .step.active {
            display: block;
        }
        
        .step-indicator {
            display: flex;
            justify-content: space-between;
            margin-bottom: 20px;
        }
        
        .step-number {
            width: 30px;
            height: 30px;
            border-radius: 50%;
            background-color: #ddd;
            display: flex;
            align-items: center;
            justify-content: center;
            font-weight: bold;
        }
        
        .step-number.active {
            background-color: #007bff;
            color: white;
        }
        
        .step-number.completed {
            background-color: #28a745;
            color: white;
        }
        
        .form-group {
            margin-bottom: 15px;
        }
        
        .form-group label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        
        .form-group input {
            width: 100%;
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
        }
        
        .button-group {
            display: flex;
            justify-content: space-between;
            margin-top: 20px;
        }
        
        .btn {
            padding: 10px 20px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
        }
        
        .btn-primary {
            background-color: #007bff;
            color: white;
        }
        
        .btn-secondary {
            background-color: #6c757d;
            color: white;
        }
    </style>
</head>
<body>
    <div class="form-container">
        <div class="step-indicator">
            <div class="step-number active" id="step-1-indicator">1</div>
            <div class="step-number" id="step-2-indicator">2</div>
            <div class="step-number" id="step-3-indicator">3</div>
        </div>
        
        <form id="multi-step-form">
            <!-- 隐藏域存储表单数据 -->
            <input type="hidden" name="step1_name" id="hidden-name">
            <input type="hidden" name="step1_email" id="hidden-email">
            <input type="hidden" name="step2_company" id="hidden-company">
            <input type="hidden" name="step2_position" id="hidden-position">
            <input type="hidden" name="current_step" value="1">
            
            <!-- 第一步:个人信息 -->
            <div class="step active" id="step-1">
                <h3>第1步:个人信息</h3>
                <div class="form-group">
                    <label for="name">姓名:</label>
                    <input type="text" id="name" name="name" required>
                </div>
                <div class="form-group">
                    <label for="email">邮箱:</label>
                    <input type="email" id="email" name="email" required>
                </div>
                <div class="button-group">
                    <div></div>
                    <button type="button" class="btn btn-primary" onclick="nextStep(1)">下一步</button>
                </div>
            </div>
            
            <!-- 第二步:工作信息 -->
            <div class="step" id="step-2">
                <h3>第2步:工作信息</h3>
                <div class="form-group">
                    <label for="company">公司:</label>
                    <input type="text" id="company" name="company" required>
                </div>
                <div class="form-group">
                    <label for="position">职位:</label>
                    <input type="text" id="position" name="position" required>
                </div>
                <div class="button-group">
                    <button type="button" class="btn btn-secondary" onclick="prevStep(2)">上一步</button>
                    <button type="button" class="btn btn-primary" onclick="nextStep(2)">下一步</button>
                </div>
            </div>
            
            <!-- 第三步:确认信息 -->
            <div class="step" id="step-3">
                <h3>第3步:确认信息</h3>
                <div id="confirmation-info"></div>
                <div class="button-group">
                    <button type="button" class="btn btn-secondary" onclick="prevStep(3)">上一步</button>
                    <button type="submit" class="btn btn-primary">提交</button>
                </div>
            </div>
        </form>
    </div>
    
    <script>
        let currentStep = 1;
        
        function nextStep(step) {
            // 验证当前步骤
            if (!validateStep(step)) {
                return;
            }
            
            // 保存当前步骤数据到隐藏域
            if (step === 1) {
                document.getElementById('hidden-name').value = document.getElementById('name').value;
                document.getElementById('hidden-email').value = document.getElementById('email').value;
            } else if (step === 2) {
                document.getElementById('hidden-company').value = document.getElementById('company').value;
                document.getElementById('hidden-position').value = document.getElementById('position').value;
            }
            
            // 切换到下一步
            document.getElementById(`step-${step}`).classList.remove('active');
            document.getElementById(`step-${step + 1}`).classList.add('active');
            
            // 更新步骤指示器
            document.getElementById(`step-${step}-indicator`).classList.remove('active');
            document.getElementById(`step-${step}-indicator`).classList.add('completed');
            document.getElementById(`step-${step + 1}-indicator`).classList.add('active');
            
            currentStep = step + 1;
            
            // 如果是最后一步,显示确认信息
            if (currentStep === 3) {
                showConfirmation();
            }
            
            // 更新当前步骤隐藏域
            document.querySelector('input[name="current_step"]').value = currentStep;
        }
        
        function prevStep(step) {
            document.getElementById(`step-${step}`).classList.remove('active');
            document.getElementById(`step-${step - 1}`).classList.add('active');
            
            document.getElementById(`step-${step}-indicator`).classList.remove('active');
            document.getElementById(`step-${step - 1}-indicator`).classList.remove('completed');
            document.getElementById(`step-${step - 1}-indicator`).classList.add('active');
            
            currentStep = step - 1;
            document.querySelector('input[name="current_step"]').value = currentStep;
        }
        
        function validateStep(step) {
            const inputs = document.querySelectorAll(`#step-${step} input[required]`);
            for (let input of inputs) {
                if (!input.value.trim()) {
                    alert(`请填写${input.previousElementSibling.textContent}`);
                    input.focus();
                    return false;
                }
            }
            return true;
        }
        
        function showConfirmation() {
            const name = document.getElementById('hidden-name').value;
            const email = document.getElementById('hidden-email').value;
            const company = document.getElementById('hidden-company').value;
            const position = document.getElementById('hidden-position').value;
            
            const confirmationInfo = document.getElementById('confirmation-info');
            confirmationInfo.innerHTML = `
                <h4>请确认您的信息:</h4>
                <p><strong>姓名:</strong> ${name}</p>
                <p><strong>邮箱:</strong> ${email}</p>
                <p><strong>公司:</strong> ${company}</p>
                <p><strong>职位:</strong> ${position}</p>
            `;
        }
        
        // 表单提交处理
        document.getElementById('multi-step-form').addEventListener('submit', function(e) {
            e.preventDefault();
            
            // 获取所有表单数据
            const formData = new FormData(this);
            const data = {};
            for (let [key, value] of formData.entries()) {
                data[key] = value;
            }
            
            console.log('提交的数据:', data);
            alert('表单提交成功!');
        });
    </script>
</body>
</html>

安全性考虑

  1. 不要存储敏感信息:隐藏域在客户端可见,不要存储密码等敏感信息
  2. CSRF令牌:使用隐藏域传递CSRF令牌防止跨站请求伪造
  3. 数据验证:服务器端必须验证隐藏域的数据

易错点提醒

  1. 隐藏域不是真正隐藏:用户可以通过查看源代码看到隐藏域的值
  2. 表单重置问题:表单reset()方法会重置隐藏域的值
  3. JavaScript操作:隐藏域的值可以通过JavaScript修改

本节总结

本节学习了HTML5表单中的各种输入控件:

  1. 文本输入:最基础的输入控件,用于单行文本
  2. 密码输入:自动隐藏输入内容,保护用户隐私
  3. 单选按钮:多个选项中选择一个
  4. 复选框:允许选择多个选项
  5. 文件上传:上传本地文件到服务器
  6. 隐藏域:存储不需要用户看到的数据

每种控件都有其特定的使用场景和注意事项,合理使用这些控件可以创建出用户友好的表单。

下一节预告

下一节我们将学习"HTML5新增输入类型",包括邮箱输入、网址输入、数字输入等HTML5新增的输入类型。


练习建议:

  1. 创建一个完整的用户注册表单,包含所有学过的输入控件
  2. 实现一个带有文件上传和进度条的表单
  3. 制作一个多步骤表单,使用隐藏域保存中间数据