组件详解#49:Toast通知,3行JS实现非阻塞式消息提示 | Component Deep Dive #49: Toast Notification — Non-Blocking Messages in 3 Lines of JS
组件详解#49:Toast通知,3行JS实现非阻塞式消息提示
你见过最烦的提示是什么?大概率是alert弹窗——挡住屏幕、必须点确认、打断所有操作。Toast通知就是为了干掉它而生的。
Toast是什么
Toast(吐司通知)是一种轻量级消息提示组件:从屏幕边缘滑入,停留几秒后自动消失,不阻塞用户操作,不需要点击关闭。
它的名字来源于”就像吐司从烤面包机里弹出来一样”——短暂出现,然后离开。
效果预览
一个标准的Toast通知应具备:
- 从底部或顶部滑入动画
- 自动消失(默认3秒)
- 支持手动关闭
- 支持多种类型(成功/错误/警告/信息)
- 多条堆叠不重叠
代码拆解
HTML结构
<div id="toast-container" style="position:fixed;bottom:24px;right:24px;z-index:9999;display:flex;flex-direction:column;gap:8px;"></div>
只需要一个容器,Toast元素动态创建。
CSS样式
.toast {
background: #1a1a2e;
color: #fff;
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
display: flex;
align-items: center;
gap: 10px;
min-width: 280px;
max-width: 400px;
animation: toast-in 0.3s ease;
}
.toast.success { border-left: 4px solid #22c55e; }
.toast.error { border-left: 4px solid #ef4444; }
.toast.warning { border-left: 4px solid #f59e0b; }
.toast.info { border-left: 4px solid #3b82f6; }
.toast.removing { animation: toast-out 0.3s ease forwards; }
@keyframes toast-in {
from { transform: translateX(120%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes toast-out {
from { transform: translateX(0); opacity: 1; }
to { transform: translateX(120%); opacity: 0; }
}
JavaScript逻辑
function showToast(message, type = 'info', duration = 3000) {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
const icons = { success: '[OK]', error: '[X]', warning: '[!]', info: '[i]' };
toast.innerHTML = `<span>${icons[type]}</span><span>${message}</span>`;
container.appendChild(toast);
setTimeout(() => {
toast.classList.add('removing');
toast.addEventListener('animationend', () => toast.remove());
}, duration);
}
关键技术点
- 非阻塞设计:用
position:fixed脱离文档流,不干扰页面交互 - 动画驱动生命周期:通过
animationend事件精确控制移除时机 - 堆叠管理:容器用
flex-direction:column;gap:8px自动排列
常见坑点
- 内存泄漏:忘记在移除前清除
setTimeout,导致已销毁元素被操作。解决方案是保存timeout ID并在remove时clear - 动画闪烁:如果Toast在
animationend前被手动移除,会闪烁。用classList控制而非直接remove() - z-index不够:被模态框遮住。Toast的z-index应设为9999+
完整可复制代码
<!DOCTYPE html>
<html>
<head>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:system-ui;padding:40px}
#toast-container{position:fixed;bottom:24px;right:24px;z-index:9999;display:flex;flex-direction:column;gap:8px}
.toast{background:#1a1a2e;color:#fff;padding:12px 20px;border-radius:8px;box-shadow:0 4px 12px rgba(0,0,0,0.3);display:flex;align-items:center;gap:10px;min-width:280px;max-width:400px;animation:toast-in .3s ease;font-size:14px}
.toast.success{border-left:4px solid #22c55e}
.toast.error{border-left:4px solid #ef4444}
.toast.warning{border-left:4px solid #f59e0b}
.toast.info{border-left:4px solid #3b82f6}
.toast.removing{animation:toast-out .3s ease forwards}
@keyframes toast-in{from{transform:translateX(120%);opacity:0}to{transform:translateX(0);opacity:1}}
@keyframes toast-out{from{transform:translateX(0);opacity:1}to{transform:translateX(120%);opacity:0}}
button{padding:10px 20px;margin:5px;border:none;border-radius:6px;cursor:pointer;background:#2563eb;color:#fff;font-size:14px}
button:hover{opacity:.9}
</style>
</head>
<body>
<button onclick="showToast('Saved successfully!','success')">Success</button>
<button onclick="showToast('Something went wrong.','error')">Error</button>
<button onclick="showToast('Storage almost full.','warning')">Warning</button>
<button onclick="showToast('New update available.','info')">Info</button>
<div id="toast-container"></div>
<script>
function showToast(message,type='info',duration=3000){
const container=document.getElementById('toast-container');
const toast=document.createElement('div');
toast.className='toast '+type;
const icons={success:'[OK]',error:'[X]',warning:'[!]',info:'[i]'};
toast.innerHTML='<span>'+icons[type]+'</span><span>'+message+'</span>';
container.appendChild(toast);
const timer=setTimeout(function(){
toast.classList.add('removing');
toast.addEventListener('animationend',function(){toast.remove()});
},duration);
toast.addEventListener('click',function(){
clearTimeout(timer);
toast.classList.add('removing');
toast.addEventListener('animationend',function(){toast.remove()});
});
}
</script>
</body>
</html>
变体拓展
- Promise式Toast:返回Promise,动画结束后resolve,支持
await showToast(...)链式调用 - 进度条Toast:底部加一个CSS动画进度条,可视化剩余时间
- 操作按钮Toast:”撤销”按钮,点击后执行回调并关闭
Component Deep Dive #49: Toast Notification — Non-Blocking Messages in 3 Lines of JS
What’s the most annoying notification you’ve seen? Probably an alert dialog — blocking the screen, requiring a click, interrupting everything. Toast notifications exist to kill it.
What Is a Toast
A Toast is a lightweight notification component: slides in from the screen edge, stays for a few seconds, then auto-dismisses. It doesn’t block user interaction and requires no click to close.
The name comes from “like toast popping out of a toaster” — brief appearance, then gone.
Code Breakdown
HTML
<div id="toast-container" style="position:fixed;bottom:24px;right:24px;z-index:9999;display:flex;flex-direction:column;gap:8px;"></div>
Just one container; Toast elements are created dynamically.
CSS
.toast {
background: #1a1a2e;
color: #fff;
padding: 12px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
animation: toast-in 0.3s ease;
}
.toast.success { border-left: 4px solid #22c55e; }
.toast.error { border-left: 4px solid #ef4444; }
.toast.removing { animation: toast-out 0.3s ease forwards; }
@keyframes toast-in {
from { transform: translateX(120%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
JavaScript
function showToast(message, type = 'info', duration = 3000) {
const container = document.getElementById('toast-container');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.innerHTML = `<span>${message}</span>`;
container.appendChild(toast);
setTimeout(() => {
toast.classList.add('removing');
toast.addEventListener('animationend', () => toast.remove());
}, duration);
}
Key Technical Points
- Non-blocking:
position:fixedremoves from document flow, no interaction interference - Animation-driven lifecycle:
animationendevent precisely controls removal timing - Stack management: Container uses
flex-direction:column;gap:8pxfor auto-stacking
Common Pitfalls
- Memory leak: Forgetting to clear
setTimeoutbefore removal. Save the timer ID and clear it on remove - Animation flicker: Manually removing before
animationendcauses flicker. UseclassListcontrol instead of directremove() - Low z-index: Toast hidden behind modals. Set z-index to 9999+
Variants
- Promise-based Toast: Returns a Promise that resolves after animation, supporting
await showToast(...) - Progress bar Toast: CSS animation bar at bottom visualizes remaining time
- Action button Toast: “Undo” button that executes callback and closes on click
更多组件请访问 Web Component Dictionary