通知条需语义化结构(role="alert"、button关闭)、transform+opacity动画、独立定时器、transitionend清理、焦点管理及暗色模式适配。

通知条的 DOM 结构要满足可批量控制和无障碍访问
通知条不能只靠 div 堆砌,否则后续 JS 控制多条、焦点管理、屏幕阅读器播报都会出问题。结构上必须包含语义化容器、可清除按钮、内容区,并预留动画触发点。
推荐用 role="alert" 标识动态插入的通知,用 aria-live="polite" 保证屏幕阅读器能读出新消息;关闭按钮必须是 button 元素,不能用 span + click 事件模拟。
示例结构:
<div class="notification" role="alert" aria-live="polite"> <span class="notification__content">操作成功</span> <button type="button" class="notification__close" aria-label="关闭通知">×</button> </div>
- 外层
div不要用display: none隐藏,而应通过添加/移除.is-hidden类配合 CSS 过渡 - 每条通知需有唯一 ID(如
id="notif-123"),方便 JS 精确移除或暂停自动关闭 - 避免在通知内部放链接或交互控件——除非明确需要,否则会干扰自动消失逻辑和键盘焦点流
CSS 动画必须用 transform + opacity 实现,禁用 height 过渡
用 height: 0 → height: auto 做展开动画不可行:CSS 无法对 auto 值做平滑过渡,会导致突兀跳变或完全无动画。真正可靠的是 transform: translateY() + opacity 组合。
立即学习“前端免费学习笔记(深入)”;
关键点在于:初始状态设为 transform: translateY(-20px) + opacity: 0,显示时改为 transform: translateY(0) + opacity: 1,隐藏时再反向过渡。这样既轻量,又不触发重排。
示例关键样式:
.notification {
opacity: 0;
transform: translateY(-20px);
transition: opacity 0.3s ease, transform 0.3s ease;
}
.notification--visible {
opacity: 1;
transform: translateY(0);
}
.notification--hiding {
opacity: 0;
transform: translateY(-20px);
}
- 过渡时间统一用
0.3s,太长用户觉得卡顿,太短(如0.1s)感知不到动画效果 - 不要给
.notification设固定height,内容高度应由padding和字体撑开,否则换行或长文本会溢出 - 如果通知支持多行文本,确保
max-height不参与过渡——它只用于限制极端情况,不是动画变量
JS 自动消失逻辑必须区分手动关闭与超时关闭
用户点了关闭按钮,和 3 秒后自动消失,是两种不同行为,后续处理不能混为一谈。比如:自动消失后不应再调用 remove() 导致 DOM 突然消失(可能中断动画),而应等 transitionend 事件后再清理节点。
核心逻辑是:插入通知 → 启动定时器 → 用户手动关闭则清除定时器 → 动画结束再从 DOM 移除节点。
示例关键代码片段:
const notify = (message) => {
const el = document.createElement('div');
el.className = 'notification';
el.innerHTML = `<span class="notification__content">${message}</span>
<button type="button" class="notification__close">×</button>`;
document.body.appendChild(el);
el.classList.add('notification--visible');
const timer = setTimeout(() => {
el.classList.remove('notification--visible');
el.classList.add('notification--hiding');
}, 3000);
el.querySelector('.notification__close').addEventListener('click', () => {
clearTimeout(timer);
el.classList.remove('notification--visible');
el.classList.add('notification--hiding');
});
el.addEventListener('transitionend', (e) => {
if (e.propertyName === 'opacity' && el.classList.contains('notification--hiding')) {
el.remove();
}
});
};
- 务必监听
transitionend而非setTimeout回调来执行remove(),否则动画未完成就删节点,视觉上会“闪退” - 多个通知同时存在时,每个都必须有独立定时器和事件监听,不能共用一个
timer变量 - 如果页面支持暗色模式,通知背景色应在 CSS 中用
color-scheme: light dark或@media (prefers-color-scheme: dark)控制,而不是 JS 写死
移动端触摸区域和焦点管理容易被忽略
在手机上,button 默认点击热区太小,用户容易点空;而键盘用户 Tab 到通知时,若没正确管理焦点,关闭后焦点会丢失到页面顶部,体验断裂。
解决方案很直接:给关闭按钮加 min-width: 36px 和 min-height: 36px,并用 outline 或 box-shadow 明确聚焦样式;关闭后,把焦点手动还给触发通知的操作按钮(如果有),或至少 fallback 到 document.body。
- 不要依赖
autofocus属性——它在动态插入的元素上无效 - 如果通知是从表单提交后弹出,关闭时应
focus()回原提交按钮,而不是任由焦点漂移 - 在 iOS Safari 上,
transform动画偶尔有渲染毛刺,可加will-change: transform, opacity微调,但仅在必要时启用



















