移动端抖动必须用 transform: translatex(),禁用 margin 和 left;@keyframes shake 需五帧且首尾为 translatex(0);js 触发前须清旧动画并强制重排;键盘弹起时提示框需动态定位。

移动端抖动必须用 transform: translateX(),禁用 margin 和 left
iOS 和 Android WebView 中,margin 或 left 触发的动画会强制重排(reflow),连续抖动时极易卡顿甚至掉帧。transform: translateX() 走 GPU 合成,无布局计算,是唯一稳妥选择。若元素已有其他 transform(比如 scale(1)),不能单独加 translateX(4px),得合并写成 transform: scale(1) translateX(4px),否则后者被覆盖。
@keyframes shake 必须包含至少四帧,且首尾都为 translateX(0)
只写 0% 和 100% 两个关键帧,浏览器会线性插值,结果是平滑位移,不是“弹”;用 ease-in-out 缓动还会削弱警示强度。标准写法必须包含:0% → 25% → 50% → 75% → 100%,对应 translateX(0) → -6px → +6px → -6px → translateX(0)。幅度控制在 ±6px 内,时间设为 0.4s,太小用户注意不到,太大像 UI 抖动而非错误反馈。
JS 触发前必须清旧动画 + 强制重排,否则连续失败不抖或抖不动
给整个 <form></form> 加 shake 类,视觉干扰大,还掩盖具体问题字段;更糟的是,连续提交失败若只反复 add 同一个 class,浏览器可能跳过重绘——因为类名没变,CSS 认为无需重播动画。
- 目标元素应是出错的
<input>、<select></select>,或其直接包裹容器(如<div class="field">) <li>每次触发前先执行 <code>element.classList.remove('shake') - 再执行
void element.offsetWidth强制重排 - 最后
element.classList.add('shake') - 监听
animationend清理 class,但 Safari 有时漏触发,建议加500ms超时兜底 - 错误提示容器父级必须设
position: relative,且每个字段独立包裹,不能共用一个定位上下文 - 提交校验后,用
element.getBoundingClientRect()实时读取输入框位置,动态设置提示框的top和left - 必要时降级为
position: fixed,配合window.scrollY和getBoundingClientRect().top手动修正
移动端键盘弹起时提示框定位失效,position: absolute 不可靠
iOS 键盘弹出后视口高度剧变,top: 100% 计算的位置会严重偏移,提示框飘到屏幕外或遮挡输入框。











