Greasy Fork

Greasy Fork is available in English.

Toast组件模块

可被其他油猴脚本引用的toast提示组件

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/548898/1657505/Toast%E7%BB%84%E4%BB%B6%E6%A8%A1%E5%9D%97.js

// ==UserScript==
// @name         Toast组件模块(深色系)
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  深色系提示的toast组件,适合夜间模式或深色主题页面
// @author       You
// @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: 'rgba(45, 45, 45, 0.8)',   // 深灰黑色背景(主色调,沉稳不刺眼)
            text: '#f0f0f0',          // 浅灰文字(确保高对比度)
            border: '1px solid rgba(68, 68, 68, 0.9)',// 深灰边框(增强轮廓)
            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.border - 边框样式
     * @param {string} options.hoverBackground - 悬停背景色
     * @param {string} options.hoverText - 悬停文字颜色
     * @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 border = options.border || COLORS.default.border;
        const hoverBgColor = options.hoverBackground || COLORS.default.hoverBackground;
        const hoverTextColor = options.hoverText || COLORS.default.hoverText;
        const hoverOpacity = options.hoverOpacity ?? COLORS.default.hoverOpacity;

        // 创建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.3);
            pointer-events: auto;
            max-width: 80%;
            word-wrap: break-word;
            font-size: 14px;
            line-height: 1.4;
        `;
        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,
            originalText: textColor,
            hoverBg: hoverBgColor,
            hoverText: hoverTextColor,
            hoverOpacity: hoverOpacity
        });

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

        // 鼠标悬停暂停计时并改变样式
        toast.addEventListener('mouseenter', () => {
            const toastData = activeToasts.get(message);
            if (toastData && toastData.timer) {
                clearTimeout(toastData.timer);
                toastData.timer = null;
                // 应用hover样式
                toast.style.background = toastData.hoverBg;
                toast.style.color = toastData.hoverText;
                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.color = toastData.originalText;
                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
    };

})(window);