概述
由于我在写主页的时候vue一直炸 索性就写了个纯JS实现的弹框
快速开始
1. 引入样式和字体图标
在 HTML 的 <head> 中添加:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.toast-container {
position: fixed;
top: 100px;
right: 20px;
z-index: 9999;
pointer-events: none;
}
.toast-wrapper {
display: flex;
flex-direction: column;
gap: 10px;
pointer-events: none;
}
.toast {
position: relative;
display: flex;
align-items: flex-start;
min-width: 300px;
max-width: 400px;
padding: 16px;
background: white;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
border-left: 4px solid;
pointer-events: all;
animation: slideInRight 0.3s ease;
}
.toast-closeable {
padding-right: 40px;
}
.toast-success {
border-left-color: #10b981;
background: linear-gradient(135deg, #f0fdf4, #ecfdf5);
}
.toast-error {
border-left-color: #ef4444;
background: linear-gradient(135deg, #fef2f2, #fef2f2);
}
.toast-warning {
border-left-color: #f59e0b;
background: linear-gradient(135deg, #fffbeb, #fffbeb);
}
.toast-info {
border-left-color: #3b82f6;
background: linear-gradient(135deg, #eff6ff, #eff6ff);
}
.toast-icon {
flex-shrink: 0;
width: 24px;
height: 24px;
margin-right: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
}
.toast-success .toast-icon {
color: #10b981;
}
.toast-error .toast-icon {
color: #ef4444;
}
.toast-warning .toast-icon {
color: #f59e0b;
}
.toast-info .toast-icon {
color: #3b82f6;
}
.toast-content {
flex: 1;
min-width: 0;
}
.toast-title {
font-weight: 600;
font-size: 0.95rem;
margin-bottom: 4px;
color: #1f2937;
}
.toast-message {
font-size: 0.875rem;
line-height: 1.4;
color: #6b7280;
}
.toast-close {
position: absolute;
top: 12px;
right: 12px;
width: 24px;
height: 24px;
border: none;
background: none;
color: #9ca3af;
cursor: pointer;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.toast-close:hover {
background: rgba(0, 0, 0, 0.05);
color: #374151;
}
.toast-progress {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 3px;
background: rgba(0, 0, 0, 0.1);
border-radius: 0 0 12px 12px;
overflow: hidden;
}
.toast-progress-bar {
height: 100%;
background: currentColor;
animation: progress linear forwards;
transform-origin: left;
}
.toast-success .toast-progress-bar {
background: #10b981;
}
.toast-error .toast-progress-bar {
background: #ef4444;
}
.toast-warning .toast-progress-bar {
background: #f59e0b;
}
.toast-info .toast-progress-bar {
background: #3b82f6;
}
/* 动画 */
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes progress {
from {
transform: scaleX(1);
}
to {
transform: scaleX(0);
}
}
.toast-enter {
animation: slideInRight 0.3s ease;
}
.toast-exit {
animation: slideInRight 0.3s ease reverse;
}
/* 响应式设计 */
@media (max-width: 640px) {
.toast-container {
right: 10px;
left: 10px;
top: 80px;
}
.toast {
min-width: auto;
max-width: none;
}
}
</style>2. 添加 HTML 容器
在 <body> 末尾添加:
<div id="toast-container" class="toast-container"></div>3. JS代码
记得引入
// utils/toast.js
class ToastManager {
constructor() {
this.container = document.getElementById('toast-container')
this.toasts = new Map()
this.toastId = 0
if (!this.container) {
this.createContainer()
}
}
createContainer() {
this.container = document.createElement('div')
this.container.className = 'toast-container'
this.container.id = 'toast-container'
document.body.appendChild(this.container)
}
show(options) {
const id = this.toastId++
const toast = this.createToastElement(id, options)
this.container.appendChild(toast.element)
this.toasts.set(id, toast)
if (options.duration && options.duration > 0) {
toast.timeout = setTimeout(() => {
this.remove(id)
}, options.duration)
}
return id
}
createToastElement(id, options) {
const toast = document.createElement('div')
const type = options.type || 'info'
const closeable = options.closeable !== false
toast.className = `toast toast-${type}${closeable ? ' toast-closeable' : ''} toast-enter`
toast.innerHTML = this.getToastHTML(options, closeable)
if (closeable) {
const closeBtn = toast.querySelector('.toast-close')
closeBtn.addEventListener('click', () => this.remove(id))
}
let pauseTimer
toast.addEventListener('mouseenter', () => {
if (toast.timeout) {
clearTimeout(toast.timeout)
toast.timeout = null
}
})
toast.addEventListener('mouseleave', () => {
if (options.duration && options.duration > 0 && !toast.timeout) {
toast.timeout = setTimeout(() => {
this.remove(id)
}, options.duration)
}
})
return {
element: toast,
timeout: null
}
}
getToastHTML(options, closeable) {
const icon = this.getIcon(options.type)
return `
<div class="toast-icon">
<i class="${icon}"></i>
</div>
<div class="toast-content">
${options.title ? `<div class="toast-title">${options.title}</div>` : ''}
<div class="toast-message">${options.message}</div>
</div>
${closeable ? '<button class="toast-close"><i class="fas fa-times"></i></button>' : ''}
${options.duration && options.duration > 0 ? `
<div class="toast-progress">
<div class="toast-progress-bar" style="animation-duration: ${options.duration}ms"></div>
</div>
` : ''}
`
}
getIcon(type) {
const icons = {
success: 'fas fa-check-circle',
error: 'fas fa-exclamation-circle',
warning: 'fas fa-exclamation-triangle',
info: 'fas fa-info-circle'
}
return icons[type] || icons.info
}
remove(id) {
const toast = this.toasts.get(id)
if (toast) {
if (toast.timeout) {
clearTimeout(toast.timeout)
}
toast.element.classList.remove('toast-enter')
toast.element.classList.add('toast-exit')
setTimeout(() => {
if (toast.element.parentNode) {
toast.element.parentNode.removeChild(toast.element)
}
this.toasts.delete(id)
}, 300)
}
}
clearAll() {
this.toasts.forEach((toast, id) => {
this.remove(id)
})
}
success(message, title = '成功') {
return this.show({ type: 'success', title, message, duration: 5000 })
}
error(message, title = '错误') {
return this.show({ type: 'error', title, message, duration: 5000 })
}
warning(message, title = '警告') {
return this.show({ type: 'warning', title, message, duration: 5000 })
}
info(message, title = '提示') {
return this.show({ type: 'info', title, message, duration: 3000 })
}
}
// 创建全局实例
const toast = new ToastManager()
// 导出供使用
export default toast基本用法
显示简单提示
javascript
// 成功提示
toast.success('操作成功!');
// 错误提示
toast.error('操作失败!');
// 警告提示
toast.warning('请注意操作安全');
// 信息提示
toast.info('这是一条普通信息');显示带标题的提示
javascript
toast.success('数据保存成功!', '保存成功');
toast.error('网络连接失败,请检查网络', '连接错误');高级用法
自定义配置
javascript
toast.show({
type: 'success', // 类型:success | error | warning | info
title: '自定义标题', // 可选标题
message: '这是一条自定义消息',
duration: 5000, // 显示时长(毫秒),0 表示不自动关闭
closeable: true // 是否显示关闭按钮
});手动控制提示框
// 显示提示并获取 ID
const toastId = toast.success('这条消息可以手动关闭');
// 3秒后手动关闭
setTimeout(() => {
toast.remove(toastId);
}, 3000);
// 关闭所有提示框
toast.clearAll();API 参考
ToastManager 类
方法
配置选项
show() 方法参数
特性
多类型
success: 绿色,用于成功操作
error: 红色,用于错误情况
warning: 橙色,用于警告信息
info: 蓝色,用于普通信息
自动关闭
默认 5 秒自动关闭(info 类型 3 秒)
鼠标悬停时暂停计时
鼠标离开后恢复计时
交互功能
可关闭的提示框
平滑的进入/退出动画
进度条显示剩余时间
支持多提示框同时显示
响应式设计
移动端适配
自动调整位置和大小
支持触摸操作
使用示例
在表单提交中使用
// 表单提交成功
function onSubmitSuccess() {
toast.success('表单提交成功!', '操作成功');
}
// 表单提交失败
function onSubmitError(error) {
toast.error(error.message, '提交失败');
}
// 表单验证警告
function onValidationWarning() {
toast.warning('请填写所有必填字段', '表单不完整');
}在网络请求中使用
async function fetchData() {
try {
const data = await api.getData();
toast.success('数据加载成功');
return data;
} catch (error) {
toast.error('数据加载失败,请重试');
throw error;
}
}在文件操作中使用
function onFileUpload(progress) {
if (progress === 100) {
toast.success('文件上传完成', '上传成功');
} else if (progress === 0) {
toast.info('开始上传文件...', '上传中');
}
}浏览器兼容性
Chrome 60+
Firefox 55+
Safari 12+
Edge 79+
版本历史
v1.0.0 (2025-11-23)
初始版本发布
支持四种基本类型
自动关闭和手动关闭
响应式设计