先记住这个答案
:valid 和 :invalid 只看当前值是否满足 HTML 约束(required、pattern、min、max 等),页面一加载就判定。所以一个空的 required 输入框,用户还没碰就会被 :invalid 匹配到。:user-valid 和 :user-invalid 多了一个前提:用户必须已经修改过这个控件的值,或者尝试过提交表单,伪类才开始生效。这样就能做到「用户改过再提示错误」,不需要写 JS。
:valid和:invalid页面一加载就生效:user-invalid在交互后提示错误min、max范围用:in-range判断- 不用 JS 就能实现「先别报错,改过再说」的体验
页面加载时就标红的问题
想象一个注册表单,邮箱框加了 required。如果你用 :invalid 给它加红色边框,用户刚打开页面就看到一个红框,还没来得及输入就被「批评」了。这就是 :invalid 的问题:它不关心用户有没有交互过。
/* 不同时启用 input:invalid 的红框,否则初始空值仍会标红 */
input:user-invalid {
border-color: red;
}
/* 用户交互后且值有效时才变绿 */
input:user-valid {
border-color: green;
}:user-invalid 的判定逻辑是:值不满足约束,并且用户已经与这个控件交互过(改过值、失去过焦点,或者点过提交按钮)。两个条件同时满足才匹配。
数字和日期的范围校验
对于 <input type="number"> 或 <input type="date"> 这类有 min 和 max 的输入,CSS 还提供了 :in-range 和 :out-of-range 两个伪类。它们和 :valid 类似,也是页面加载就判定的,但只关心值是否在范围内,不管 required 这些其他约束。
<style>
input:in-range { background: #e8f5e9; }
input:out-of-range { background: #ffebee; }
</style>
<input type="number" min="1" max="100" value="150" />
<!-- 因为 150 超出了 max=100,背景色会变成浅红 -->这里的 value="150" 一加载就超出了范围,所以 :out-of-range 立刻匹配。用户改成 50 之后就切换到 :in-range。
容易答错的地方
- 以为
:user-valid只是:valid的别名 - 两者的判定逻辑完全不同。
:valid只看值,:user-valid还看用户有没有交互过。一个空的required框,:valid不匹配,:user-valid也不匹配,但原因不一样:前者是因为值不满足约束,后者是因为用户还没交互。 - 把
:out-of-range和:invalid混为一谈 - 一个
<input type="number" min="1" max="100">如果值为空,:invalid不匹配(没有required所以空值合法),:out-of-range也不匹配(空值不算超出范围)。只有用户填了一个超出min或max的数字,:out-of-range才匹配。
面试官还会怎么问?
不支持 :user-valid 的老浏览器怎么办?
可以给输入框加一个 interacted 的 class。按产品确定的时机监听 blur 或 input,交互后添加 class,再用 .interacted:invalid 写样式。这是自定义的提示时机,不保证和浏览器的 :user-invalid 判定完全一致;要特别测试用户未输入就离开、提交失败和重置表单三种情况。
:valid 能匹配整个 <form> 吗?
可以。:valid 作用在 <form> 上时,只有表单内所有控件都满足约束,form:valid 才匹配。任何一个控件不满足,form:invalid 就会匹配。这可以用来控制提交按钮的样式。
用 novalidate 关掉校验后,这些伪类还生效吗?
novalidate 只阻止浏览器在提交时弹出校验气泡,不影响伪类的匹配。加了 novalidate 的表单里,:invalid 和 :user-invalid 照常工作,样式正常生效。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。