Toast组件模块

toast组件

As of 09.09.2025. See ბოლო ვერსია.

ეს სკრიპტი არ უნდა იყოს პირდაპირ დაინსტალირებული. ეს ბიბლიოთეკაა, სხვა სკრიპტებისთვის უნდა ჩართეთ მეტა-დირექტივაში // @require https://update.greasyfork.org/scripts/548898/1657497/Toast%E7%BB%84%E4%BB%B6%E6%A8%A1%E5%9D%97.js.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Toast组件模块
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  toast组件
// @author       Lulu
// @match        *
// @grant        none
// @noframes
// ==/UserScript==

(function (window) {
    'use strict';

    // 防止重复加载
    if (window.MonkeyToast) {
        return;
    }

    const TOAST_CONFIG = {
        maxCount: 5,          // 最大同时显示数量
        baseOffset: 20,       // 基础偏移量(px)
        spacing: 10,          // 每个Toast之间的间距
        defaultDuration: 3000,// 默认显示时长(ms)
        animationDuration: 300// 动画过渡时间(ms)
    };

    const COLORS = {
        default: {
            background: '#2d2d2d',    // 深灰黑色背景(主色调,沉稳不刺眼)
            text: '#f0f0f0',          // 浅灰文字(确保与深色背景的高对比度)
            border: '1px solid #444444',// 深灰边框(增强轮廓,避免与页面融合)
            hoverBackground: '#1a1a1a',// 悬停时更深的背景(明显区分状态)
            hoverText: '#ffffff',     // 悬停时文字更亮(强化交互反馈)
            hoverOpacity: 1           // 保持完全不透明,确保夜间可读性
        }
    };

    // 存储活跃的Toast (message -> {element, timer})
    const activeToasts = new Map();
    // 等待显示的Toast队列
    const toastQueue = [];

    /**
     * 显示Toast提示
     * @param {string} message - 提示内容
     * @param {number} duration - 显示时长(ms),可选
     * @param {Object} options - 额外选项,可选
     * @param {string} options.backgroundColor - 背景颜色
     * @param {string} options.color - 文字颜色
     * @param {string} options.hoverBackground - 悬停背景色
     * @param {number} options.hoverOpacity - 悬停透明度
     */
    function showToast(message, duration = TOAST_CONFIG.defaultDuration, options = {}) {
        // 检查是否已达到最大显示数量
        if (activeToasts.size >= TOAST_CONFIG.maxCount) {
            // 加入队列等待
            toastQueue.push({ message, duration, options });
            return;
        }

        // 检查是否为重复消息
        if (activeToasts.has(message)) {
            return;
        }

        // 合并默认样式和自定义样式
        const bgColor = options.backgroundColor || COLORS.default.background;
        const textColor = options.color || COLORS.default.text;
        const hoverBgColor = options.hoverBackground || COLORS.default.hoverBackground;
        const hoverOpacity = options.hoverOpacity ?? COLORS.default.hoverOpacity; // 使用空值合并运算符处理0的情况

        // 创建Toast元素
        const toast = document.createElement('div');
        toast.className = 'tm-toast';

        toast.style.cssText = `
            position: fixed;
            top: ${TOAST_CONFIG.baseOffset}px;
            left: 50%;
            transform: translateX(-50%);
            background: ${bgColor};
            color: ${textColor};
            padding: 10px 20px;
            border-radius: 5px;
            z-index: 999999;
            opacity: 1;
            transition: all ${TOAST_CONFIG.animationDuration}ms ease;
            box-shadow: 0 2px 8px rgba(0,0,0,0.15);
            pointer-events: auto;
            max-width: 80%;
            word-wrap: break-word;
            font-size: 14px;
            border: ${border};
            transition: background 0.2s ease, color 0.2s ease; 
        `;
        toast.textContent = message;

        // 添加到文档
        const container = document.body || document.documentElement;
        container.appendChild(toast);

        // 入场动画
        setTimeout(() => {
            toast.style.transform = 'translateX(-50%) translateY(0)';
        }, 10);

        // 记录到活跃列表
        const timer = setTimeout(() => {
            removeToast(message);
        }, duration);
        activeToasts.set(message, {
            element: toast,
            timer,
            options,
            originalBg: bgColor,      // 保存原始背景色用于恢复
            hoverBg: hoverBgColor,    // 保存悬停背景色
            hoverOpacity: hoverOpacity // 保存悬停透明度
        });

        // 更新所有Toast位置
        updateToastPositions();

        // 鼠标悬停暂停计时并改变样式
        toast.addEventListener('mouseenter', () => {
            const toastData = activeToasts.get(message);
            if (toastData && toastData.timer) {
                clearTimeout(toastData.timer);
                toastData.timer = null;
                // 应用hover样式:浅灰色背景 + 30%透明度
                toast.style.background = toastData.hoverBg;
                toast.style.opacity = toastData.hoverOpacity;
            }
        });

        // 鼠标离开恢复计时和样式
        toast.addEventListener('mouseleave', () => {
            const toastData = activeToasts.get(message);
            if (toastData && !toastData.timer) {
                toastData.timer = setTimeout(() => {
                    removeToast(message);
                }, duration);
                // 恢复原始样式
                toast.style.background = toastData.originalBg;
                toast.style.opacity = 1;
            }
        });
    }

    /**
     * 移除指定Toast
     * @param {string} message - 要移除的提示内容
     */
    function removeToast(message) {
        const toastData = activeToasts.get(message);
        if (!toastData) return;

        const { element, timer } = toastData;
        if (timer) clearTimeout(timer);

        // 淡出动画
        element.style.opacity = 0;
        element.style.transform = 'translateX(-50%) translateY(-10px)';

        // 动画结束后移除元素
        setTimeout(() => {
            try {
                element.remove();
            } catch (e) { /* 忽略已移除的情况 */ }

            activeToasts.delete(message);

            // 更新位置
            updateToastPositions();

            // 检查队列并显示下一个
            if (toastQueue.length > 0) {
                const nextToast = toastQueue.shift();
                showToast(nextToast.message, nextToast.duration, nextToast.options);
            }
        }, TOAST_CONFIG.animationDuration);
    }

    /**
     * 更新所有活跃Toast的位置,实现自动堆叠
     */
    function updateToastPositions() {
        let currentOffset = TOAST_CONFIG.baseOffset;

        // 按添加顺序遍历并更新位置
        Array.from(activeToasts.values()).forEach(({ element }) => {
            // 设置新位置
            element.style.top = `${currentOffset}px`;

            // 计算下一个位置(当前元素高度 + 间距)
            currentOffset += element.offsetHeight + TOAST_CONFIG.spacing;
        });
    }

    /**
     * 清除所有toast
     */
    function clearAllToasts() {
        // 清除活跃的toast
        Array.from(activeToasts.keys()).forEach(message => {
            removeToast(message);
        });
        // 清空队列
        toastQueue.length = 0;
    }

    /**
     * 配置toast全局参数
     * @param {Object} config - 配置对象
     */
    function configToast(config) {
        Object.assign(TOAST_CONFIG, config);
    }

    /**
     * 配置全局颜色
     * @param {Object} colorConfig - 颜色配置对象
     */
    function configColors(colorConfig) {
        Object.assign(COLORS.default, colorConfig);
    }

    // 暴露公共API
    window.MonkeyToast = {
        show: showToast,
        remove: removeToast,
        clearAll: clearAllToasts,
        config: configToast,
        configColors: configColors // 新增颜色配置API
    };

})(window);