承接上一篇郵箱驗證,本文實作註冊表單中其餘核心欄位的即時驗證:暱稱(長度、字元限制)、密碼(強度、規則)、確認密碼(一致性)。這些驗證多為純前端邏輯,無需 AJAX 請求,回應極速,是表單優化的關鍵環節。
| 欄位 | 規則 | 錯誤訊息範例 |
|---|---|---|
| 暱稱 | 必填、1-16 字元、允許中英數底線 | 暱稱不能為空 / 不要超過 16 字元 / 僅限中英數底線 |
| 密碼 | 必填、6-16 字元、至少包含字母和數字 | 密碼不能為空 / 密碼長度 6-16 字元 / 必須包含字母和數字 |
| 確認密碼 | 必填、與密碼完全一致 | 請確認密碼 / 兩次輸入不一致 |
<form id="registerForm" action="register.php" method="post" novalidate>
<!-- 郵箱欄位(上一篇) -->
<div class="form-group">
<label for="nickname">暱稱:</label>
<input type="text" id="nickname" name="nickname"
onblur="checkNickname(this.value)"
oninput="checkNickname(this.value)"
placeholder="1-16 字元,中英數底線">
<span id="nicknameError" class="error-msg"></span>
</div>
<div class="form-group">
<label for="password">密碼:</label>
<input type="password" id="password" name="password"
onblur="checkPassword(this.value)"
oninput="checkPassword(this.value)"
placeholder="6-16 字元,含字母和數字">
<span id="passwordError" class="error-msg"></span>
<div id="passwordStrength" class="strength-bar"><div class="strength-fill"></div></div>
</div>
<div class="form-group">
<label for="confirmPassword">確認密碼:</label>
<input type="password" id="confirmPassword" name="confirm_password"
onblur="checkConfirmPassword(this.value)"
oninput="checkConfirmPassword(this.value)"
placeholder="再次輸入密碼">
<span id="confirmPasswordError" class="error-msg"></span>
</div>
<button type="submit" id="submitBtn" disabled>註冊</button>
</form>
<script>
// 狀態管理
const validationState = {
email: false,
nickname: false,
password: false,
confirmPassword: false
};
// DOM 元素快取
const els = {
nicknameError: document.getElementById('nicknameError'),
passwordError: document.getElementById('passwordError'),
confirmPasswordError: document.getElementById('confirmPasswordError'),
passwordStrength: document.getElementById('passwordStrength'),
submitBtn: document.getElementById('submitBtn')
};
// 統一更新提交按鈕狀態
function updateSubmitState() {
const allValid = Object.values(validationState).every(v => v === true);
els.submitBtn.disabled = !allValid;
els.submitBtn.style.opacity = allValid ? '1' : '0.6';
els.submitBtn.style.cursor = allValid ? 'pointer' : 'not-allowed';
}
// 顯示錯誤/成功訊息
function showMessage(el, message, isError = true) {
el.innerHTML = `${message}`;
}
function checkNickname(value) {
const trimmed = value.trim();
if (!trimmed) {
showMessage(els.nicknameError, '* 暱稱不能為空!');
validationState.nickname = false;
} else if (trimmed.length > 16) {
showMessage(els.nicknameError, '* 暱稱不能超過 16 個字元!');
validationState.nickname = false;
} else if (!/^[\u4e00-\u9fa5a-zA-Z0-9_]+$/.test(trimmed)) {
showMessage(els.nicknameError, '* 暱稱僅限中文、英文、數字和底線!');
validationState.nickname = false;
} else {
showMessage(els.nicknameError, '* 暱稱可用!', false);
validationState.nickname = true;
}
updateSubmitState();
}
function checkPassword(value) {
// 重置確認密碼狀態(密碼變更需重新確認)
validationState.confirmPassword = false;
showMessage(els.confirmPasswordError, '* 請確認密碼');
document.getElementById('confirmPassword').value = '';
if (!value) {
showMessage(els.passwordError, '* 密碼不能為空!');
validationState.password = false;
updateStrengthBar(0);
} else if (value.length < 6 || value.length > 16) {
showMessage(els.passwordError, '* 密碼長度必須為 6-16 個字元!');
validationState.password = false;
updateStrengthBar(1);
} else if (!/[a-zA-Z]/.test(value) || !/[0-9]/.test(value)) {
showMessage(els.passwordError, '* 密碼必須包含字母和數字!');
validationState.password = false;
updateStrengthBar(2);
} else {
showMessage(els.passwordError, '* 密碼可用!', false);
validationState.password = true;
updateStrengthBar(calculateStrength(value));
}
updateSubmitState();
}
/**
* 密碼強度計算(0-4 級)
* @param {string} pwd
* @returns {number} 0:極弱 1:弱 2:中 3:強 4:極強
*/
function calculateStrength(pwd) {
let score = 0;
// 長度
if (pwd.length >= 8) score++;
if (pwd.length >= 12) score++;
// 字元多樣性
if (/[a-z]/.test(pwd)) score++;
if (/[A-Z]/.test(pwd)) score++;
if (/[0-9]/.test(pwd)) score++;
if (/[^a-zA-Z0-9]/.test(pwd)) score++; // 特殊字元
return Math.min(score, 4);
}
/**
* 更新強度條 UI
* @param {number} level 0-4
*/
function updateStrengthBar(level) {
const colors = ['#e74c3c', '#e67e22', '#f39c12', '#2ecc71', '#27ae60'];
const labels = ['極弱', '弱', '中等', '強', '極強'];
const fill = document.querySelector('.strength-fill');
if (fill) {
fill.style.width = `${(level / 4) * 100}%`;
fill.style.backgroundColor = colors[level] || colors[0];
fill.setAttribute('title', labels[level] || '');
}
}
function checkConfirmPassword(value) {
const password = document.getElementById('password').value;
if (!value) {
showMessage(els.confirmPasswordError, '* 請確認密碼!');
validationState.confirmPassword = false;
} else if (value !== password) {
showMessage(els.confirmPasswordError, '* 兩次輸入的密碼不一致!');
validationState.confirmPassword = false;
} else {
showMessage(els.confirmPasswordError, '* 密碼輸入一致!', false);
validationState.confirmPassword = true;
}
updateSubmitState();
}
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; }
.form-group input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
.form-group input:focus { border-color: #3498db; outline: none; box-shadow: 0 0 5px rgba(52,152,219,0.3); }
.error-msg { display: block; margin-top: 5px; font-size: 12px; min-height: 16px; }
.strength-bar { height: 4px; background: #ecf0f1; border-radius: 2px; margin-top: 8px; overflow: hidden; }
.strength-fill { height: 100%; width: 0%; transition: width 0.3s, background-color 0.3s; }
button[type="submit"] { width: 100%; padding: 12px; background: #3498db; color: white; border: none; border-radius: 4px; font-size: 16px; transition: opacity 0.3s; }
button[type="submit"]:hover:not(:disabled) { background: #2980b9; }
button[type="submit"]:disabled { opacity: 0.6; cursor: not-allowed; }
鐵律:前端驗證只為用戶體驗,後端驗證才是安全防線!
<?php
// register.php 核心驗證邏輯
function validateRegistration(array $data): array {
$errors = [];
// 郵箱(含唯一性檢查)
if (empty($data['email'])) {
$errors['email'] = '郵箱不能為空';
} elseif (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$errors['email'] = '郵箱格式不正確';
} elseif (strlen($data['email']) > 100) {
$errors['email'] = '郵箱過長';
} else {
// 檢查資料庫唯一性...
}
// 暱稱
if (empty($data['nickname'])) {
$errors['nickname'] = '暱稱不能為空';
} elseif (strlen($data['nickname']) > 16) {
$errors['nickname'] = '暱稱不能超過 16 字元';
} elseif (!preg_match('/^[\p{Han}a-zA-Z0-9_]+$/u', $data['nickname'])) {
$errors['nickname'] = '暱稱僅限中英數底線';
}
// 密碼
if (empty($data['password'])) {
$errors['password'] = '密碼不能為空';
} elseif (strlen($data['password']) < 6 || strlen($data['password']) > 16) {
$errors['password'] = '密碼長度必須 6-16 字元';
} elseif (!preg_match('/[a-zA-Z]/', $data['password']) || !preg_match('/[0-9]/', $data['password'])) {
$errors['password'] = '密碼必須包含字母和數字';
}
// 確認密碼
if ($data['password'] !== ($data['confirm_password'] ?? '')) {
$errors['confirm_password'] = '兩次密碼輸入不一致';
}
return $errors;
}
// 使用範例
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$errors = validateRegistration($_POST);
if (empty($errors)) {
// 密碼雜湊
$hash = password_hash($_POST['password'], PASSWORD_BCRYPT);
// 寫入資料庫...
echo '註冊成功!';
} else {
// 回傳錯誤給前端顯示
foreach ($errors as $field => $msg) {
echo "";
}
}
}
掌握這套驗證體系,能構建既安全又友好的註冊/登入表單,為用戶體驗與系統安全雙贏奠定基礎!