Validator Realtime
<section class="p-6 bg-white rounded-lg shadow-sm">
<div class="max-w-md mx-auto space-y-6">
<h3 class="text-lg font-medium text-gray-900">Real-time Form Validation</h3>
<form class="space-y-6" id="validation-form" novalidate>
<!-- 电子邮件验证 -->
<div class="form-control">
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">Email Address</label>
<div class="relative">
<input id="email" type="email" required placeholder="[email protected]"
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
</svg>
</div>
<div class="hidden validation-icon absolute inset-y-0 right-0 flex items-center pr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="error-icon hidden h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="success-icon hidden h-5 w-5 text-green-500" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg>
</div>
</div>
<p class="error-message mt-1 text-xs text-red-500 hidden">Please enter a valid email address</p>
</div>
<!-- 密码验证 -->
<div class="form-control">
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">Password</label>
<div class="relative">
<input id="password" type="password" required minlength="8"
pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}"
placeholder="Enter your password"
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd" />
</svg>
</div>
<div class="hidden validation-icon absolute inset-y-0 right-0 flex items-center pr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="error-icon hidden h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="success-icon hidden h-5 w-5 text-green-500" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg>
</div>
</div>
<div class="mt-2">
<div class="password-strength-meter">
<div class="space-y-1">
<div class="flex items-center">
<div class="flex-1 flex space-x-1">
<div class="strength-segment h-1 flex-1 rounded-full bg-gray-200"></div>
<div class="strength-segment h-1 flex-1 rounded-full bg-gray-200"></div>
<div class="strength-segment h-1 flex-1 rounded-full bg-gray-200"></div>
<div class="strength-segment h-1 flex-1 rounded-full bg-gray-200"></div>
</div>
<div class="ml-2 text-xs text-gray-500 w-16 password-strength-text">Too weak</div>
</div>
</div>
</div>
<div class="grid grid-cols-2 gap-x-4 gap-y-2 mt-2">
<div class="text-xs flex items-center text-gray-600">
<span class="validation-check mr-1 text-gray-300">✓</span> 8+ characters
</div>
<div class="text-xs flex items-center text-gray-600">
<span class="validation-check mr-1 text-gray-300">✓</span> 1+ uppercase letter
</div>
<div class="text-xs flex items-center text-gray-600">
<span class="validation-check mr-1 text-gray-300">✓</span> 1+ lowercase letter
</div>
<div class="text-xs flex items-center text-gray-600">
<span class="validation-check mr-1 text-gray-300">✓</span> 1+ number
</div>
</div>
</div>
</div>
<!-- 密码确认 -->
<div class="form-control">
<label for="confirm-password" class="block text-sm font-medium text-gray-700 mb-1">Confirm Password</label>
<div class="relative">
<input id="confirm-password" type="password" required
placeholder="Confirm your password"
class="w-full px-3 py-2 pl-10 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M2.166 4.999A11.954 11.954 0 0010 1.944 11.954 11.954 0 0017.834 5c.11.65.166 1.32.166 2.001 0 5.225-3.34 9.67-8 11.317C5.34 16.67 2 12.225 2 7c0-.682.057-1.35.166-2.001zm11.541 3.708a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg>
</div>
<div class="hidden validation-icon absolute inset-y-0 right-0 flex items-center pr-3">
<svg xmlns="http://www.w3.org/2000/svg" class="error-icon hidden h-5 w-5 text-red-500" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="success-icon hidden h-5 w-5 text-green-500" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
</svg>
</div>
</div>
<p class="error-message mt-1 text-xs text-red-500 hidden">Passwords do not match</p>
</div>
<!-- 提交按钮 -->
<div class="pt-2">
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
disabled>
Create Account
</button>
</div>
</form>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('validation-form');
const emailInput = document.getElementById('email');
const passwordInput = document.getElementById('password');
const confirmPasswordInput = document.getElementById('confirm-password');
const submitButton = form.querySelector('button[type="submit"]');
// 验证电子邮件
function validateEmail() {
const isValid = emailInput.validity.valid;
updateValidationUI(emailInput, isValid);
return isValid;
}
// 验证密码强度
function validatePassword() {
const password = passwordInput.value;
const hasLength = password.length >= 8;
const hasLowerCase = /[a-z]/.test(password);
const hasUpperCase = /[A-Z]/.test(password);
const hasNumber = /\d/.test(password);
// 更新密码要求检查
updatePasswordRequirements(hasLength, hasUpperCase, hasLowerCase, hasNumber);
// 计算强度得分
const score = [hasLength, hasLowerCase, hasUpperCase, hasNumber].filter(Boolean).length;
updatePasswordStrengthMeter(score);
const isValid = hasLength && hasLowerCase && hasUpperCase && hasNumber;
updateValidationUI(passwordInput, isValid);
return isValid;
}
// 验证确认密码
function validateConfirmPassword() {
const isValid = confirmPasswordInput.value === passwordInput.value && confirmPasswordInput.value !== '';
updateValidationUI(confirmPasswordInput, isValid);
if (!isValid && confirmPasswordInput.value !== '') {
confirmPasswordInput.nextElementSibling.nextElementSibling.classList.remove('hidden');
}
return isValid;
}
// 更新密码强度计量表
function updatePasswordStrengthMeter(score) {
const segments = document.querySelectorAll('.strength-segment');
const strengthText = document.querySelector('.password-strength-text');
// 重置所有段
segments.forEach(segment => {
segment.classList.remove('bg-red-500', 'bg-yellow-500', 'bg-green-400', 'bg-green-500');
segment.classList.add('bg-gray-200');
});
// 根据分数设置文字
const strengthLabels = ['Too weak', 'Weak', 'Medium', 'Strong'];
const strengthColors = ['text-red-500', 'text-yellow-500', 'text-green-400', 'text-green-500'];
// 重置颜色类
strengthText.classList.remove('text-red-500', 'text-yellow-500', 'text-green-400', 'text-green-500');
// 如果有密码则更新UI
if (passwordInput.value) {
// 设置文本
strengthText.textContent = strengthLabels[score - 1] || 'Too weak';
strengthText.classList.add(strengthColors[score - 1] || 'text-red-500');
// 设置段颜色
for (let i = 0; i < score; i++) {
if (i === 0) segments[i].classList.add('bg-red-500');
else if (i === 1) segments[i].classList.add('bg-yellow-500');
else if (i === 2) segments[i].classList.add('bg-green-400');
else if (i === 3) segments[i].classList.add('bg-green-500');
}
} else {
strengthText.textContent = 'Too weak';
strengthText.classList.add('text-gray-500');
}
}
// 更新密码要求UI
function updatePasswordRequirements(hasLength, hasUpperCase, hasLowerCase, hasNumber) {
const checks = document.querySelectorAll('.validation-check');
// 更新每个要求的状态
updateRequirement(checks[0], hasLength);
updateRequirement(checks[1], hasUpperCase);
updateRequirement(checks[2], hasLowerCase);
updateRequirement(checks[3], hasNumber);
}
function updateRequirement(element, isValid) {
if (isValid) {
element.classList.remove('text-gray-300');
element.classList.add('text-green-500');
} else {
element.classList.remove('text-green-500');
element.classList.add('text-gray-300');
}
}
// 更新验证UI
function updateValidationUI(input, isValid) {
const container = input.parentElement;
const validationIcon = container.querySelector('.validation-icon');
const errorIcon = container.querySelector('.error-icon');
const successIcon = container.querySelector('.success-icon');
const errorMessage = container.nextElementSibling;
validationIcon.classList.remove('hidden');
if (isValid && input.value !== '') {
input.classList.remove('border-red-500');
input.classList.add('border-green-500');
errorIcon.classList.add('hidden');
successIcon.classList.remove('hidden');
if (errorMessage && errorMessage.classList.contains('error-message')) {
errorMessage.classList.add('hidden');
}
} else if (input.value !== '') {
input.classList.remove('border-green-500');
input.classList.add('border-red-500');
errorIcon.classList.remove('hidden');
successIcon.classList.add('hidden');
if (errorMessage && errorMessage.classList.contains('error-message')) {
errorMessage.classList.remove('hidden');
}
} else {
input.classList.remove('border-red-500', 'border-green-500');
validationIcon.classList.add('hidden');
if (errorMessage && errorMessage.classList.contains('error-message')) {
errorMessage.classList.add('hidden');
}
}
}
// 更新提交按钮状态
function updateSubmitButton() {
const isEmailValid = validateEmail();
const isPasswordValid = validatePassword();
const isConfirmPasswordValid = validateConfirmPassword();
if (isEmailValid && isPasswordValid && isConfirmPasswordValid) {
submitButton.disabled = false;
} else {
submitButton.disabled = true;
}
}
// 添加输入事件监听器
emailInput.addEventListener('input', function() {
validateEmail();
updateSubmitButton();
});
passwordInput.addEventListener('input', function() {
validatePassword();
if (confirmPasswordInput.value !== '') {
validateConfirmPassword();
}
updateSubmitButton();
});
confirmPasswordInput.addEventListener('input', function() {
validateConfirmPassword();
updateSubmitButton();
});
// 表单提交处理
form.addEventListener('submit', function(e) {
e.preventDefault();
const isValid = validateEmail() &&
validatePassword() &&
validateConfirmPassword();
if (isValid) {
alert('Form submitted successfully!');
// 这里可以发送表单数据或执行其他操作
}
});
});
</script>
</section>
Copied to clipboard