组件详解#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);
}

关键技术点

  1. 非阻塞设计:用position:fixed脱离文档流,不干扰页面交互
  2. 动画驱动生命周期:通过animationend事件精确控制移除时机
  3. 堆叠管理:容器用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

  1. Non-blocking: position:fixed removes from document flow, no interaction interference
  2. Animation-driven lifecycle: animationend event precisely controls removal timing
  3. Stack management: Container uses flex-direction:column;gap:8px for auto-stacking

Common Pitfalls

  • Memory leak: Forgetting to clear setTimeout before removal. Save the timer ID and clear it on remove
  • Animation flicker: Manually removing before animationend causes flicker. Use classList control instead of direct remove()
  • 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



← 返回首页