推荐用 :invalid:not(:focus) 触发警告并配合 opacity、transform、visibility 过渡;纯 CSS 不可靠,需 JS 在 blur 或 submit 时调用 checkValidity() 并切换 error-shown 类控制显示时机。

用 :invalid 伪类 + transition 实现基础弹出警告
原生表单验证错误框(比如 input:invalid)默认没动画,直接显示/隐藏太生硬。要加弹出过渡,核心是控制 opacity 和 transform,再配 transition。但注意::invalid 是实时触发的,用户一输错就生效,所以得配合 :focus 或 JS 控制时机,否则光标一进就闪一下。
- 只靠
:invalid+transition不够可靠——浏览器可能跳过中间状态,尤其 Safari 对:invalid的重绘不敏感 - 推荐用
:invalid:not(:focus)做「失焦后才触发警告」,避免干扰输入过程 -
transition必须写在「常态」选择器上(比如input),不能只写在:invalid里,否则进入时有动画、退出时没有
为什么 display: none 不能和 transition 一起用
很多人想「错误时不显示,有错时 display: block 并加过渡」,这行不通。display 是离散属性,没有中间值,transition 对它完全无效。浏览器要么瞬间显示,要么瞬间消失。
- 必须改用
opacity(0→1)、visibility(hidden→visible)、或max-height(0→xxx)这类可插值的属性 - 最稳妥组合是
opacity+transform: scale()+visibility:先设visibility: hidden; opacity: 0; transform: scale(0.95),再在:invalid:not(:focus)里改成visibility: visible; opacity: 1; transform: scale(1) - 记得给
visibility加过渡——虽然它本身不可动画,但配合opacity能防止元素突然占位或闪动
JS 主动触发验证并控制警告时机
纯 CSS 容易误触,比如用户还没输完,placeholder 消失就报错。更可控的做法是:提交时或失焦时用 JS 触发 checkValidity(),再手动加 class 控制样式。
- 监听
input事件不做实时验证,改用blur或submit时调用element.checkValidity() - 验证失败后,给元素加
class="error-shown",CSS 写input.error-shown而不是依赖:invalid - 这样能避开浏览器对
:invalid的怪异触发逻辑(比如空值、type="email" 但内容为空时是否算 invalid) - 示例关键代码:
input.addEventListener('blur', () => {<br> if (!input.checkValidity()) {<br> input.classList.add('error-shown');<br> } else {<br> input.classList.remove('error-shown');<br> }<br>});
兼容性与移动端特别注意点
iOS Safari 对 transform + transition 在表单元素上的渲染有延迟,尤其键盘弹起后第一次触发容易卡顿或跳帧。
立即学习“前端免费学习笔记(深入)”;
- 加
will-change: transform到动画元素上,提前提示浏览器优化 - 避免在
input上直接做 scale 动画——改用包裹层(比如<div class="input-wrapper">)来承载动画,让input本身只负责内容 - Android WebView 有些版本不支持
:invalid伪类,JS 方案在这里是刚需,不是可选项 - 别忘了设置
outline: none配合自定义警告,否则原生焦点框和你的弹出警告会打架










