You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

原生JavaScript实现表单提交时光标定位至首个无效输入框求助

问题:表单提交时无法自动聚焦第一个无效输入框

作为学习项目的一部分,我尝试用原生JavaScript实现一个表单,要求提交时自动将光标定位到第一个无效输入框。但尝试多种方案后均未成功,问题似乎与focus()方法和已添加的aria-required="true"属性有关。

原代码问题分析

  1. 提交事件处理中未定义isConfirmPasswordValid变量,导致表单有效性判断逻辑出错
  2. 缺少“定位第一个无效输入框并聚焦”的核心逻辑

解决方案

步骤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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 02:25:22