MutationObserver 可监听表单校验提示的 DOM 变化并触发动画:精准监听父容器的子节点增删、class/hidden 属性变更及文本更新,过滤语义有效变更,添加临时 class 执行轻量 CSS 动画并自动清理。

在 H5 页面中,利用 MutationObserver 监听表单校验提示(如错误文案、红框、图标等)的 DOM 变化,并在其出现/更新时触发动画(如淡入、滑动、抖动),是一种轻量、解耦且响应及时的实现方式。关键在于精准监听目标节点的增删、class 变更或文本内容变化,避免过度触发或漏掉首次渲染。
监听校验提示元素的插入与属性变更
校验提示通常以 <span class="error-tip">请输入正确手机号</span> 等形式动态插入,或通过切换 hidden、class(如 show/error)控制显隐。应监听其父容器(如表单项 <div class="form-item">)的子节点变动和属性变化:
<ul>
<li>配置 <code>childList: true 捕获提示元素的新增/移除
attributes: true + attributeFilter: ['class', 'hidden'] 捕获显隐状态变更characterData: true 和 subtree: true 监听文本节点变化(注意性能)过滤出真正需要动画的变更
不是每次 DOM 变化都要执行动画——例如重复插入同一提示、或仅修改无关 class。建议在回调中做语义判断:
生成可在 deepbi 沙箱 iframe 内运行或作为独立文件运行的交互式 HTML 页面,使用 `window.deepbi` JS SDK 实现数据查询、空间管理、文件浏览及用户操作。当用户提出以下需求时启用该能力: - 创建 KPI 仪表盘、数据表格、文件浏览器、成员列表、管理页面; - 明确要求“转成 dhtml”、“转译成 HTML”、“把 XX 页面转成……”、“控制台页面”、“管理后台”、“DeepBI 页面迁移”; - 提及位于 `apps/console/src/featur
- 检查新增节点是否匹配
.error-tip, .validate-error, [data-validate]等选择器 - 对已存在节点,比对变更前后的
className或hidden属性:仅当从隐藏变显示、或新增 error 类时才触发 - 用
Set缓存已处理过的节点 ID 或引用,防止同一提示多次动画
执行轻量、可中断的 CSS 动画
推荐用 CSS 动画而非 JS 定时器,更流畅且易维护。给提示元素添加临时 class 触发动画,例如:
// JS 中添加
el.classList.add('error-tip--animate');
// CSS 中定义
.error-tip--animate {
animation: fadeInUp 0.3s ease-out;
}
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
- 动画结束后用
el.classList.remove('error-tip--animate')清理 class,避免干扰后续状态 - 若用户快速连续输入导致频繁校验,可用
getAnimations().forEach(a => a.cancel())中断前序动画(兼容性需检查) - 移动端注意开启硬件加速:
transform: translateZ(0)或will-change: transform
完整初始化示例(精简版)
以监听整个表单区域为例:
const formContainer = document.querySelector('.form-wrapper');
const observer = new MutationObserver(records => {
records.forEach(record => {
// 处理新增节点
record.addedNodes.forEach(node => {
if (node.nodeType === 1 && node.matches('.error-tip')) {
animateTip(node);
}
});
// 处理属性变更
record.attributeName && record.target.matches('.error-tip') &&
(record.oldValue !== record.target.getAttribute(record.attributeName)) &&
animateTip(record.target);
});
});
function animateTip(el) {
if (el.classList.contains('error-tip--animating')) return;
el.classList.add('error-tip--animating', 'error-tip--animate');
el.addEventListener('animationend', () => {
el.classList.remove('error-tip--animating', 'error-tip--animate');
}, { once: true });
}
observer.observe(formContainer, {
childList: true,
attributes: true,
attributeFilter: ['class', 'hidden'],
subtree: true,
attributeOldValue: true
});

















