200字
简易JS提示框
2025-11-23
2025-11-23

概述

由于我在写主页的时候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(options)

options: Object

number

显示自定义提示框,返回提示框 ID

success(message, title)

message: string, title?: string

number

显示成功提示

error(message, title)

message: string, title?: string

number

显示错误提示

warning(message, title)

message: string, title?: string

number

显示警告提示

info(message, title)

message: string, title?: string

number

显示信息提示

remove(id)

id: number

void

根据 ID 移除指定提示框

clearAll()

-

void

移除所有提示框

配置选项

show() 方法参数

参数

类型

默认值

必填

描述

type

string

'info'

提示类型:success, error, warning, info

title

string

-

提示标题

message

string

-

提示消息内容

duration

number

5000

显示时长(毫秒),0 表示不自动关闭

closeable

boolean

true

是否显示关闭按钮

特性

多类型

  • 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)

    • 初始版本发布

    • 支持四种基本类型

    • 自动关闭和手动关闭

    • 响应式设计

简易JS提示框
作者
小泳
发表于
2025-11-23
License
CC BY-SA 4.0