原生JavaScript实现表单提交时光标定位至首个无效输入框求助
问题:表单提交时无法自动聚焦第一个无效输入框
作为学习项目的一部分,我尝试用原生JavaScript实现一个表单,要求提交时自动将光标定位到第一个无效输入框。但尝试多种方案后均未成功,问题似乎与focus()方法和已添加的aria-required="true"属性有关。
原代码问题分析
- 提交事件处理中未定义
isConfirmPasswordValid变量,导致表单有效性判断逻辑出错 - 缺少“定位第一个无效输入框并聚焦”的核心逻辑
解决方案
步骤1:修复变量未定义问题
在表单提交的验证逻辑中,补充checkConfirmPassword()的调用,确保所有字段验证结果都被纳入表单有效性判断。
步骤2:添加聚焦第一个无效输入框的逻辑
在表单验证不通过时,按照表单中输入框的顺序,找到第一个验证失败的输入框,调用其focus()方法。可以通过维护输入框与对应验证函数的顺序数组,遍历后找到第一个无效的输入框。
修改后的关键代码
form.addEventListener('submit', function (e) { e.preventDefault(); // 执行所有字段验证,补充confirmPassword的验证 let isUsernameValid = checkUsername(), isEmailValid = checkEmail(), isPasswordValid = checkPassword(), isConfirmPasswordValid = checkConfirmPassword(), isFirstnameValid = checkFirstname(), isLastnameValid = checkLastname(), isPhoneValid = checkPhone(); // 表单有效性判断 let isFormValid = isUsernameValid && isEmailValid && isPasswordValid && isConfirmPasswordValid && isFirstnameValid && isLastnameValid && isPhoneValid; // 提交到服务器(如果表单有效) if (isFormValid) { // 这里添加表单提交逻辑 return; } // 找到第一个无效的输入框并聚焦 const fieldValidations = [ { el: usernameEl, valid: isUsernameValid }, { el: firstnameEl, valid: isFirstnameValid }, { el: lastnameEl, valid: isLastnameValid }, { el: emailEl, valid: isEmailValid }, { el: passwordEl, valid: isPasswordValid }, { el: confirmPasswordEl, valid: isConfirmPasswordValid }, { el: phoneEl, valid: isPhoneValid } ]; const firstInvalidField = fieldValidations.find(item => !item.valid); if (firstInvalidField) { firstInvalidField.el.focus(); // 可选:滚动到该字段,确保在视口中可见 firstInvalidField.el.scrollIntoView({ behavior: 'smooth', block: 'center' }); } });
完整修改后的代码
/* 选择所有输入字段和表单 */ const usernameEl = document.querySelector('#username'); const emailEl = document.querySelector('#email'); const passwordEl = document.querySelector('#password'); const confirmPasswordEl = document.querySelector('#confirm-password'); const firstnameEl = document.querySelector('#firstname'); const lastnameEl = document.querySelector('#lastname'); const phoneEl = document.querySelector('#phone'); const form = document.querySelector('#signup'); // 验证用户名字段 const checkUsername = () => { let valid = false; const min = 8, max = 20; const username = usernameEl.value.trim(); if (!isRequired(username)) { showError(usernameEl, '用户名不能为空。'); } else if (!isBetween(username.length, min, max)) { showError(usernameEl, `用户名长度必须在${min}-${max}个字符之间。`) } else { showSuccess(usernameEl); valid = true; } return valid; }; // 验证邮箱字段 const checkEmail = () => { let valid = false; const email = emailEl.value.trim(); if (!isRequired(email)) { showError(emailEl, '邮箱不能为空。'); } else if (!isEmailValid(email)) { showError(emailEl, '邮箱格式无效。') } else { showSuccess(emailEl); valid = true; } return valid; }; // 验证密码字段 const checkPassword = () => { let valid = false; const password = passwordEl.value.trim(); if (!isRequired(password)) { showError(passwordEl, '密码不能为空。'); } else if (!isPasswordSecure(password)) { showError(passwordEl, '密码必须至少8位,包含大小写字母、数字和特殊字符(!@#$%^&*)。'); } else { showSuccess(passwordEl); valid = true; } return valid; }; // 验证确认密码字段 const checkConfirmPassword = () => { let valid = false; const confirmPassword = confirmPasswordEl.value.trim(); const password = passwordEl.value.trim(); if (!isRequired(confirmPassword)) { showError(confirmPasswordEl, '请再次输入密码'); } else if (password !== confirmPassword) { showError(confirmPasswordEl, '两次输入的密码不一致'); } else { showSuccess(confirmPasswordEl); valid = true; } return valid; }; // 验证名字字段 const checkFirstname = () => { let valid = false; const min = 2, max = 30; const firstname = firstnameEl.value.trim(); if (!isRequired(firstname)) { showError(firstnameEl, '名字不能为空。'); } else if (!isBetween(firstname.length, min, max)) { showError(firstnameEl, `名字长度必须在${min}-${max}个字符之间。`) } else { showSuccess(firstnameEl); valid = true; } return valid; }; // 验证姓氏字段 const checkLastname = () => { let valid = false; const min = 2, max = 30; const lastname = lastnameEl.value.trim(); if (!isRequired(lastname)) { showError(lastnameEl, '姓氏不能为空。'); } else if (!isBetween(lastname.length, min, max)) { showError(lastnameEl, `姓氏长度必须在${min}-${max}个字符之间。`) } else { showSuccess(lastnameEl); valid = true; } return valid; }; // 验证电话字段 const checkPhone = () => { let valid = false; const min = 14, max = 30; const phone = phoneEl.value.trim(); if (!isRequired(phone)) { showError(phoneEl, '电话不能为空。'); } else if (!isBetween(phone.length, min, max)) { showError(phoneEl, `电话长度必须在${min}-${max}个字符之间。`) } else { showSuccess(phoneEl); valid = true; } return valid; }; // 正则表达式验证工具 const isEmailValid = (email) => { const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return re.test(email); }; const isPasswordSecure = (password) => { const re = new RegExp("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])(?=.{8,})"); return re.test(password); }; // 通用验证工具 const isRequired = value => value.trim() !== ''; const isBetween = (length, min, max) => length >= min && length <= max; // 错误提示函数 const showError = (input, message) => { const formField = input.parentElement; formField.classList.remove('success'); formField.classList.add('error'); const error = formField.querySelector('small'); error.textContent = message; }; // 成功提示函数 const showSuccess = (input) => { const formField = input.parentElement; formField.classList.remove('error'); formField.classList.add('success'); const error = formField.querySelector('small'); error.textContent = ''; } // 表单提交事件处理 form.addEventListener('submit', function (e) { e.preventDefault(); // 执行所有字段验证 let isUsernameValid = checkUsername(), isEmailValid = checkEmail(), isPasswordValid = checkPassword(), isConfirmPasswordValid = checkConfirmPassword(), isFirstnameValid = checkFirstname(), isLastnameValid = checkLastname(), isPhoneValid = checkPhone(); // 判断表单是否有效 let isFormValid = isUsernameValid && isEmailValid && isPasswordValid && isConfirmPasswordValid && isFirstnameValid && isLastnameValid && isPhoneValid; // 表单有效则提交 if (isFormValid) { // 这里添加表单提交逻辑,比如form.submit(); return; } // 按表单字段顺序找到第一个无效字段并聚焦 const fieldValidations = [ { el: usernameEl, valid: isUsernameValid }, { el: firstnameEl, valid: isFirstnameValid }, { el: lastnameEl, valid: isLastnameValid }, { el: emailEl, valid: isEmailValid }, { el: passwordEl, valid: isPasswordValid }, { el: confirmPasswordEl, valid: isConfirmPasswordValid }, { el: phoneEl, valid: isPhoneValid } ]; const firstInvalidField = fieldValidations.find(item => !item.valid); if (firstInvalidField) { firstInvalidField.el.focus(); // 可选:滚动到字段位置,确保用户可见 firstInvalidField.el.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }); // 可选:手机号格式化输入 // document.getElementById("phone").addEventListener("input", function (e) { // var x = e.target.value.replace(/\D/g, "").match(/(\d{0,3})(\d{0,3})(\d{0,4})/); // e.target.value = !x[2] ? x[1] : "(" + x[1] + ") " + x[2] + (x[3] ? "-" + x[3] : ""); // });
内容的提问来源于stack exchange,提问作者JappperCat
相关产品推荐
相关产品推荐

