首页 > 编程笔记 > CSS笔记

CSS :valid和:invalid的用法

先看一段 HTML 代码:
验证码:<input required pattern="\w{4,6}">
这是一个验证码输入框,这个输入框必填,同时要求验证码为 4~6 个常规字符。现在有如下 CSS 代码:
input:valid {
   background-color: green;
   color: #fff;
}
input:invalid {
   border: 2px solid red;
}
则默认状态下,由于输入框中没有值,与 required 属性必填验证不符,将触发 :invalid 伪类匹配,输入框表现为 2px 大小的红色边框,如下图所示。


图 1 :invalid伪类匹配下的红色边框

如果我们在输入框中输入任意 4 个数字,匹配 pattern 属性值中的正则表达式,则会触发 :valid 伪类匹配,输入框的背景色表现为绿色,如下图所示。


图 2 :valid伪类匹配下的绿色背景

以上就是 :valid 伪类和 :invalid 伪类的作用,乍一看它们好像挺实用的,但实际上这两个特性并没有想象中那么好用,因为 :valid 伪类的匹配在页面加载时就会被触发,这对用户而言其实是不友好的。举个例子,用户刚进入一个登录界面,还没进行任何操作,就出现大大的红色警告,显示输入不合法,这样会吓着用户的。

鉴于以上原因,现在新出现了一个 :user-invalid 伪类,它需要用户的交互才触发匹配,不过目前 :user-invalid 伪类仅被 Firefox88+ 浏览器支持,在 Chrome 浏览器和 Safari 浏览器中无法使用,但没关系,我们可以辅助 JavaScript 优化 :invalid 伪类的验证体验。

请看下面这个可以实际开发应用的示例,其HTML代码如下:
<form id="csForm" novalidate>
   <p>
      验证码:<input class="cs-input" placeholder=" " required pattern="\w{4,6}">
      <span class="cs-valid-tips"></span>
   </p>
   <input type="submit" value="提交">
</form>
上述示例的实现逻辑为:默认不开启验证,当用户产生提交表单的行为后,通过给表单元素添加特定类名,触发浏览器内置验证开启,同时借助 :placeholder-shown 伪类细化提示文案。

JavaScript 示意代码如下:
csForm.addEventListener('submit', function (event) {
   this.classList.add('valid');
   event.preventDefault();
});

CSS 代码如下:
.cs-input {
   border: 1px solid gray;
}
/* 验证不合法时边框为红色 */
.valid .cs-input:invalid {
   border-color: red;
}
/* 验证全部通过标记 */
.valid .cs-input:valid + .cs-valid-tips::before {
   content: "√";
   color: green;
}
/* 验证不合法提示 */
.valid .cs-input:invalid + .cs-valid-tips::before {
   content: "不符合要求";
   color: red;
}
/* 空值提示 */
.valid .cs-input:placeholder-shown + .cs-valid-tips::before {
   content: "尚未输入值";
}
于是可以看到下图所示的一系列状态变化。


图 3 :invalid伪类验证各种状态效果示意

这个验证过程和状态变化都没有 JavaScript 的参与,JavaScript 的唯一作用就是赋予一个开始验证的标志量类名。

有人可能会产生疑问:如何才能知道所有表单元素都验证通过呢?可以使用 <form> 元素原生的 checkValidity() 方法,返回整个表单是否验证通过的布尔值。
csForm.addEventListener('submit', function (event) {
   this.classList.add('valid');
   // 判断表单是否全部验证通过
   if (this.checkValidity && this.checkValidity() == true) {
      console.log('表单验证通过');
      // 这里可以运行表单ajax提交了
   }
   event.preventDefault();
});
另外,如果希望表单元素的验证效果是即时的,而非在表单提交后再验证,那么可以给 <form> 元素绑定 'input' 输入事件,并给对应的 target 对象设置启动 CSS 验证标志量。例如:
csForm.addEventListener('input', function (event) {
   event.target.classList.add('valid');
});
IE 浏览器有一个严重的渲染 bug,对于输入框元素,:invalid 等伪类只会即时匹配输入框元素本身,而不会重绘输入框后面的兄弟元素样式,于是我们会发现,明明输入的值已经合法了,输入框的红色边框也消失了,但是输入框后面的错误提示文字一直显示,如下图所示。


图 4 IE渲染bug示意

IE 浏览器下这类重绘 bug 屡见不鲜,但修复方法很简单,触发重绘即可。可以改变父元素的样式,或者设置无关紧要的类名。下面是我写的补丁,将它放在页面的任意位置即可:
// IE触发重绘的补丁
if (typeof document.msHidden != 'undefined' || !history.pushState) {
   document.addEventListener('input', function (event) {
      if (event.target && /^input|textarea$/i.test(event.target.tagName)) {
         event.target.parentElement.className = event.target.parentElement.className;
      }
   });
}
图 5 展示的就是放置了修复补丁后的渲染效果,可以看到输入框的值合法时,输入框后面的提示信息同步变化了。


图 5 修复IE渲染bug后的效果示意

最后一个小知识点是,:invalid 伪类还可以直接匹配 <form> 元素。例如:
form::invalid {
   outline: 1px solid red;
}
但是 IE 浏览器并不支持 :invalid 伪类匹配 <form> 元素。

另外,:valid 伪类和 :invalid 伪类还可以用来区分 IE10 及以上版本的浏览器:
.cs-cl { /* IE9及IE9- */ }
.cs-cl, div:valid { /* IE10及IE10+ */}

推荐阅读