Greasy Fork

Greasy Fork is available in English.

Toast组件模块

可被其他油猴脚本引用的toast提示组件(修复版)

当前为 2025-09-09 提交的版本,查看 最新版本

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

// ==UserScript==
// @name         Toast组件模块
// @namespace    http://tampermonkey.net/
// @version      1.0.1
// @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)
    };

    // 存储活跃的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 - 文字颜色
     */
    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;
        }

        // 创建Toast元素
        const toast = document.createElement('div');
        toast.className = 'tm-toast';
        
        // 合并默认样式和自定义样式
        const bgColor = options.backgroundColor || '#333';
        const color = options.color || 'white';
        
        toast.style.cssText = `
            position: fixed;
            top: ${TOAST_CONFIG.baseOffset}px;
            left: 50%;
            transform: translateX(-50%);
            background: ${bgColor};
            color: ${color};
            padding: 10px 20px;
            border-radius: 5px;
            z-index: 999999;
            opacity: 0;
            transition: all ${TOAST_CONFIG.animationDuration}ms ease;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            pointer-events: auto;
            max-width: 80%;
            word-wrap: break-word;
        `;
        toast.textContent = message;

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

        // 强制重绘后显示动画
        setTimeout(() => {
            toast.style.opacity = '1';
        }, 10);

        // 记录到活跃列表
        const timer = setTimeout(() => {
            removeToast(message);
        }, duration);
        activeToasts.set(message, { element: toast, timer, options });

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

        // 鼠标悬停暂停计时
        toast.addEventListener('mouseenter', () => {
            const toastData = activeToasts.get(message);
            if (toastData && toastData.timer) {
                clearTimeout(toastData.timer);
                toastData.timer = null;
                toast.style.background = shadeColor(bgColor, 20); // 提亮背景色作为反馈
            }
        });

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

    /**
     * 移除指定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;
        });
    }

    /**
     * 调整颜色明暗度
     * @param {string} color - 十六进制颜色
     * @param {number} percent - 百分比,正数变亮,负数变暗
     * @returns {string} 调整后的颜色
     */
    function shadeColor(color, percent) {
        let R = parseInt(color.substring(1, 3), 16);
        let G = parseInt(color.substring(3, 5), 16);
        let B = parseInt(color.substring(5, 7), 16);

        R = parseInt(R * (100 + percent) / 100);
        G = parseInt(G * (100 + percent) / 100);
        B = parseInt(B * (100 + percent) / 100);

        R = (R < 255) ? R : 255;
        G = (G < 255) ? G : 255;
        B = (B < 255) ? B : 255;

        R = Math.round(R);
        G = Math.round(G);
        B = Math.round(B);

        const RR = ((R.toString(16).length === 1) ? "0" + R.toString(16) : R.toString(16));
        const GG = ((G.toString(16).length === 1) ? "0" + G.toString(16) : G.toString(16));
        const BB = ((B.toString(16).length === 1) ? "0" + B.toString(16) : B.toString(16));

        return `#${RR}${GG}${BB}`;
    }

    /**
     * 清除所有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);
    }

    // 暴露公共API
    window.MonkeyToast = {
        show: showToast,
        remove: removeToast,
        clearAll: clearAllToasts,
        config: configToast
    };

})(window);