可行但需满足硬性前提:输入框必须参与原生验证(如加required或pattern),仅靠CSS无法主动触发JS校验失败的抖动;:invalid{animation:shake 0.4s}常失效,主因是未满足触发条件或关键帧不足五帧、未用animation-fill-mode:both重置状态。

纯 CSS 实现表单验证失败时的左右晃动警告,**可行但有硬性前提:必须依赖 :invalid 伪类触发,且输入框需参与原生验证(如加 required 或 pattern)**。仅靠 CSS 无法在 JS 校验失败后主动“拉起”动画,也不能绕过浏览器对 :invalid 的匹配规则。
为什么直接写 :invalid { animation: shake 0.4s; } 常常不生效
常见错误不是动画写得不对,而是触发条件没满足:
-
:invalid不会匹配空的<input type="text">—— 它默认不参与验证,必须显式加required、minlength、pattern或语义化类型(如type="email"且值非法) - 页面刚加载时,空值也会被识别为
:invalid,导致一上来就抖;应配合:not(:placeholder-shown)过滤初始状态 - 动画播完后若未重置,连续失焦/提交可能因浏览器优化而跳过重绘;
animation-fill-mode: forwards会让元素停在最后一帧(translateX(0)),但若下一帧还是0,动画就“静音”了
必须写满 5 帧的 @keyframes shake,且首尾归零
两帧(0% 和 100%)或对称三帧(0% → 50% → 100%)都会被浏览器线性插值,结果是平滑晃动,不是警示性抖动。真实抖动需要突变节奏:
@keyframes shake {
0% { transform: translateX(0); }
25% { transform: translateX(-6px); }
50% { transform: translateX(6px); }
75% { transform: translateX(-3px); }
100% { transform: translateX(0); }
}
- 位移量控制在 ±3px~±6px:太小看不见,太大破坏布局或引发眩晕
- 总时长设为
0.4s:短于 0.3s 显得抽搐,长于 0.5s 削弱紧迫感 - 务必加
animation-fill-mode: both(非forwards),确保动画结束回退到初始态,为下次触发留出空间
如何让 :invalid 动画真正响应每次校验失败
CSS 本身无法“感知”用户点击提交或离开字段的动作,所以必须靠 HTML 属性和选择器组合来模拟时机:
立即学习“前端免费学习笔记(深入)”;
- 给表单加
novalidate属性并用 JS 调用reportValidity()—— 这会强制更新:invalid状态,触发动画重播 - 写法上必须同时覆盖正常态与无效态:
input { animation: none; }+input:invalid:not(:placeholder-shown) { animation: shake 0.4s both; } - 避免用
!important强撑权重,而是靠选择器精度:比如form .form-control:invalid比单纯input:invalid更可靠 - 移动端 Safari 对
:invalid更新有延迟,建议在 JS 中input.reportValidity()后加setTimeout(() => input.focus(), 10)强制重绘
容易被忽略的兼容性与体验细节
这个方案表面“纯 CSS”,实则高度依赖浏览器对表单验证的实现一致性:
-
type="number"在空值时部分浏览器不触发:invalid,加required是最稳解法 - 如果用户开了系统级“减少运动”偏好(
prefers-reduced-motion: reduce),动画应降级为静态边框变色,需加媒体查询:@media (prefers-reduced-motion: reduce) { input:invalid { border-color: #f44336; } } - 父容器若设了
overflow: hidden,抖动位移超出会被裁剪,调试时可临时加outline: 1px solid red查边界 - 动画不能替代辅助功能:必须同步设置
aria-invalid="true"和aria-describedby指向错误文案,否则屏幕阅读器用户完全感知不到抖动


















