Better web animation 网页动画改进

为所有网页的新加载、变化、移动和消失的内容提供可配置的平滑显现和动画效果,包括图片和瞬间变化的元素。优化性能,实现元素位置变化的平滑过渡。

目前为 2024-11-05 提交的版本。查看 最新版本

// ==UserScript==
// @name         Better web animation 网页动画改进
// @namespace    http://tampermonkey.net/
// @version      6.0
// @description  为所有网页的新加载、变化、移动和消失的内容提供可配置的平滑显现和动画效果,包括图片和瞬间变化的元素。优化性能,实现元素位置变化的平滑过渡。
// @match        *://*/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_notification
// @license      CC BY-NC 4.0
// ==/UserScript==

(function() {
    'use strict';

    // 多语言支持
    const translations = {
        en: {
            settingsTitle: 'Animation Effect Settings',
            fadeInDuration: 'Fade-in Duration (seconds):',
            fadeOutDuration: 'Fade-out Duration (seconds):',
            transitionDuration: 'Transition Duration (seconds):',
            positionTransitionDuration: 'Position Transition Duration (seconds):',
            animationTypes: 'Animation Types:',
            fade: 'Fade',
            zoom: 'Zoom',
            rotate: 'Rotate',
            slide: 'Slide',
            excludedTags: 'Excluded Tags (separated by commas):',
            observeAttributes: 'Observe Attribute Changes',
            observeCharacterData: 'Observe Text Changes',
            detectFrequentChanges: 'Detect Frequently Changing Elements',
            changeThreshold: 'Frequent Change Threshold (times):',
            detectionDuration: 'Detection Duration (milliseconds):',
            enableInitialFadeIn: 'Enable Initial Fade-in Effect',
            complexityThreshold: 'Complexity Threshold (elements):',
            mutationThreshold: 'Mutation Threshold (mutations/sec):',
            enableComplexityDetection: 'Enable Complexity Detection',
            enableMutationDetection: 'Enable Mutation Rate Detection',
            saveConfig: 'Save Settings',
            cancelConfig: 'Cancel',
            settings: 'Settings',
            animations: 'Animations',
            enabled: 'Enabled',
            disabled: 'Disabled',
            animationPresets: 'Animation Presets:',
            defaultPreset: 'Default',
            gentlePreset: 'Gentle',
            dynamicPreset: 'Dynamic',
            customPreset: 'Custom',
            enablePositionTransition: 'Enable Position Transition',
        },
        zh: {
            settingsTitle: '动画效果设置',
            fadeInDuration: '渐显持续时间(秒):',
            fadeOutDuration: '渐隐持续时间(秒):',
            transitionDuration: '属性过渡持续时间(秒):',
            positionTransitionDuration: '位置过渡持续时间(秒):',
            animationTypes: '动画类型:',
            fade: '淡入/淡出(Fade)',
            zoom: '缩放(Zoom)',
            rotate: '旋转(Rotate)',
            slide: '滑动(Slide)',
            excludedTags: '排除的标签(用逗号分隔):',
            observeAttributes: '观察属性变化',
            observeCharacterData: '观察文本变化',
            detectFrequentChanges: '检测频繁变化的元素',
            changeThreshold: '频繁变化阈值(次):',
            detectionDuration: '检测持续时间(毫秒):',
            enableInitialFadeIn: '启用加载时的渐入效果',
            complexityThreshold: '复杂度阈值(元素数量):',
            mutationThreshold: '突变阈值(突变/秒):',
            enableComplexityDetection: '启用复杂度检测',
            enableMutationDetection: '启用突变率检测',
            saveConfig: '保存设置',
            cancelConfig: '取消',
            settings: '设置',
            animations: '动画',
            enabled: '已启用',
            disabled: '已禁用',
            animationPresets: '动画预设:',
            defaultPreset: '默认',
            gentlePreset: '柔和',
            dynamicPreset: '动感',
            customPreset: '自定义',
            enablePositionTransition: '启用位置过渡',
        }
    };

    const userLang = navigator.language.startsWith('zh') ? 'zh' : 'en';
    const t = translations[userLang];

    // 默认配置
    const defaultConfig = {
        fadeInDuration: 0.5, // 渐显持续时间(秒)
        fadeOutDuration: 0.5, // 渐隐持续时间(秒)
        transitionDuration: 0.5, // 属性过渡持续时间(秒)
        positionTransitionDuration: 0.5, // 位置过渡持续时间(秒)
        animationTypes: ['fade'], // 动画类型:'fade', 'zoom', 'rotate', 'slide'
        excludedTags: ['script'], // 排除的标签
        observeAttributes: true, // 观察属性变化
        observeCharacterData: true, // 观察文本变化
        detectFrequentChanges: true, // 检测频繁变化
        changeThreshold: 10, // 频繁变化阈值(次)
        detectionDuration: 500, // 检测持续时间(毫秒)
        enableInitialFadeIn: true, // 启用加载时的渐入效果
        complexityThreshold: 50000, // 复杂度阈值(元素数量)
        mutationThreshold: 1000, // 突变阈值(突变/秒)
        enableComplexityDetection: true, // 启用复杂度检测
        enableMutationDetection: true, // 启用突变率检测
        animationPreset: 'default', // 动画预设
        enablePositionTransition: true, // 启用位置过渡
    };

    // 加载用户配置
    let userConfig = Object.assign({}, defaultConfig, GM_getValue('userConfig', {}));

    // 初始化频繁变化检测的记录
    const changeRecords = new WeakMap();

    // 动画启用状态
    let animationsEnabled = true;

    // 添加菜单命令
    GM_registerMenuCommand(t.settings, showConfigPanel);

    // 添加动画开关菜单
    updateAnimationMenuCommand();

    function updateAnimationMenuCommand() {
        const label = `${t.animations}: ${animationsEnabled ? t.enabled : t.disabled}`;
        GM_registerMenuCommand(label, toggleAnimations);
    }

    function toggleAnimations() {
        animationsEnabled = !animationsEnabled;
        if (animationsEnabled) {
            observer.disconnect();
            startObserving();
            applyAnimationsToExistingImages();
        } else {
            observer.disconnect();
        }
        updateAnimationMenuCommand();
    }

    // 检测复杂网页
    function checkComplexity() {
        if (!userConfig.enableComplexityDetection) return;

        const totalElements = document.getElementsByTagName('*').length;
        if (totalElements > userConfig.complexityThreshold) {
            animationsEnabled = false;
            console.warn('Animations have been disabled due to high complexity of the webpage.');
            GM_notification(t.animations + ' ' + t.disabled + ' - ' + 'High complexity detected.', 'Better Web Animation');
            updateAnimationMenuCommand();
        }
    }

    checkComplexity();

    // 添加全局样式
    function addGlobalStyles() {
        // 移除之前的样式
        const existingStyle = document.getElementById('global-animation-styles');
        if (existingStyle) existingStyle.remove();

        // 动态生成动画样式
        let animations = '';

        // 根据预设设置动画参数
        switch (userConfig.animationPreset) {
            case 'gentle':
                userConfig.fadeInDuration = 1;
                userConfig.fadeOutDuration = 1;
                userConfig.transitionDuration = 1;
                userConfig.positionTransitionDuration = 1;
                break;
            case 'dynamic':
                userConfig.fadeInDuration = 0.3;
                userConfig.fadeOutDuration = 0.3;
                userConfig.transitionDuration = 0.3;
                userConfig.positionTransitionDuration = 0.3;
                break;
            case 'custom':
                // 保持用户自定义设置
                break;
            default:
                // 默认设置
                userConfig.fadeInDuration = defaultConfig.fadeInDuration;
                userConfig.fadeOutDuration = defaultConfig.fadeOutDuration;
                userConfig.transitionDuration = defaultConfig.transitionDuration;
                userConfig.positionTransitionDuration = defaultConfig.positionTransitionDuration;
                break;
        }

        // 渐显效果
        if (userConfig.animationTypes.includes('fade')) {
            animations += `
            .fade-in-effect {
                animation: fadeIn ${userConfig.fadeInDuration}s forwards;
            }
            @keyframes fadeIn {
                from { opacity: 0; }
                to { opacity: var(--original-opacity, 1); }
            }
            `;
        }

        // 缩放效果
        if (userConfig.animationTypes.includes('zoom')) {
            animations += `
            .zoom-in-effect {
                animation: zoomIn ${userConfig.fadeInDuration}s forwards;
            }
            @keyframes zoomIn {
                from { transform: scale(0); }
                to { transform: scale(1); }
            }
            `;
        }

        // 旋转效果
        if (userConfig.animationTypes.includes('rotate')) {
            animations += `
            .rotate-in-effect {
                animation: rotateIn ${userConfig.fadeInDuration}s forwards;
            }
            @keyframes rotateIn {
                from { transform: rotate(-360deg); }
                to { transform: rotate(0deg); }
            }
            `;
        }

        // 滑动效果
        if (userConfig.animationTypes.includes('slide')) {
            animations += `
            .slide-in-effect {
                animation: slideIn ${userConfig.fadeInDuration}s forwards;
            }
            @keyframes slideIn {
                from { transform: translateY(100%); }
                to { transform: translateY(0); }
            }
            `;
        }

        // 属性变化过渡效果
        animations += `
        .property-change-effect {
            transition: all ${userConfig.transitionDuration}s ease-in-out;
        }
        `;

        // 渐隐效果
        animations += `
        .fade-out-effect {
            animation: fadeOut ${userConfig.fadeOutDuration}s forwards;
        }
        @keyframes fadeOut {
            from { opacity: var(--original-opacity, 1); }
            to { opacity: 0; }
        }
        `;

        // 位置过渡效果
        if (userConfig.enablePositionTransition) {
            animations += `
            .position-transition-effect {
                transition: transform ${userConfig.positionTransitionDuration}s ease-in-out;
            }
            `;
        }

        // 添加样式到页面
        const style = document.createElement('style');
        style.id = 'global-animation-styles';
        style.textContent = animations;
        document.head.appendChild(style);
    }

    addGlobalStyles();

    // 页面加载时,为整个页面应用平滑显现效果
    function applyInitialFadeIn() {
        if (!userConfig.enableInitialFadeIn) return;
        document.body.style.opacity = '0';
        document.body.style.transition = `opacity ${userConfig.fadeInDuration}s`;
        window.addEventListener('load', () => {
            document.body.style.opacity = '';
        });
    }

    applyInitialFadeIn();

    // 检查元素是否可见
    function isElementVisible(element) {
        return element.offsetWidth > 0 && element.offsetHeight > 0 && window.getComputedStyle(element).visibility !== 'hidden' && window.getComputedStyle(element).display !== 'none';
    }

    // 检查是否为要排除的 Bilibili 元素
    let bilibiliExcludedElement = null;

    function isBilibiliVideoPage() {
        return window.location.href.startsWith('https://www.bilibili.com/video/');
    }

    if (isBilibiliVideoPage()) {
        const xpath = '//*[@id="bilibili-player"]/div/div/div[1]/div[1]/div[4]';
        const result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
        bilibiliExcludedElement = result.singleNodeValue;
    }

    // 应用进入动画效果
    function applyEnterAnimations(element) {
        if (!animationsEnabled) return;

        // 检查是否在排除列表中
        if (userConfig.excludedTags.includes(element.tagName.toLowerCase())) return;
        // 检查元素是否可见
        if (!isElementVisible(element)) return;
        // 检查是否为要排除的 Bilibili 元素
        if (element === bilibiliExcludedElement) return;

        // 检查初始透明度
        const computedStyle = window.getComputedStyle(element);
        const initialOpacity = computedStyle.opacity;

        // 保存原始透明度
        element.style.setProperty('--original-opacity', initialOpacity);

        // 清除之前的动画类
        element.classList.remove('fade-in-effect', 'zoom-in-effect', 'rotate-in-effect', 'slide-in-effect');

        // 添加动画类
        if (userConfig.animationTypes.includes('fade')) {
            element.classList.add('fade-in-effect');
        }
        if (userConfig.animationTypes.includes('zoom')) {
            element.classList.add('zoom-in-effect');
        }
        if (userConfig.animationTypes.includes('rotate')) {
            element.classList.add('rotate-in-effect');
        }
        if (userConfig.animationTypes.includes('slide')) {
            element.classList.add('slide-in-effect');
        }

        // 监听动画结束,移除动画类,恢复元素状态
        function handleAnimationEnd() {
            element.classList.remove('fade-in-effect', 'zoom-in-effect', 'rotate-in-effect', 'slide-in-effect');
            element.style.removeProperty('--original-opacity');
            element.removeEventListener('animationend', handleAnimationEnd);
        }
        element.addEventListener('animationend', handleAnimationEnd);
    }

    // 应用属性变化过渡效果
    function applyTransitionEffect(element) {
        if (!animationsEnabled) return;

        // 检查是否在排除列表中
        if (userConfig.excludedTags.includes(element.tagName.toLowerCase())) return;
        // 检查元素是否可见
        if (!isElementVisible(element)) return;
        // 检查是否为要排除的 Bilibili 元素
        if (element === bilibiliExcludedElement) return;

        if (!element.classList.contains('property-change-effect')) {
            element.classList.add('property-change-effect');

            // 监听过渡结束,移除过渡类,恢复元素状态
            const removeTransitionClass = () => {
                element.classList.remove('property-change-effect');
                element.removeEventListener('transitionend', removeTransitionClass);
            };
            element.addEventListener('transitionend', removeTransitionClass);
        }
    }

    // 应用位置变化过渡效果
    function applyPositionTransition(elements) {
        if (!animationsEnabled || !userConfig.enablePositionTransition) return;

        // 过滤不可见元素
        elements = elements.filter(element => isElementVisible(element));

        // 检查元素数量,避免性能问题
        if (elements.length > 100) return;

        // 记录初始位置
        const firstRects = new Map();
        elements.forEach(element => {
            firstRects.set(element, element.getBoundingClientRect());
        });

        // 在下一次渲染后计算新位置并应用过渡
        requestAnimationFrame(() => {
            elements.forEach(element => {
                const firstRect = firstRects.get(element);
                const lastRect = element.getBoundingClientRect();

                // 计算位置变化
                const deltaX = firstRect.left - lastRect.left;
                const deltaY = firstRect.top - lastRect.top;

                // 如果位置发生变化
                if (deltaX !== 0 || deltaY !== 0) {
                    // 设置初始变换
                    element.style.transform = `translate(${deltaX}px, ${deltaY}px)`;

                    // 强制重绘
                    element.offsetWidth;

                    // 应用过渡效果
                    element.classList.add('position-transition-effect');
                    element.style.transform = '';

                    // 监听过渡结束,移除过渡类
                    const handleTransitionEnd = () => {
                        element.classList.remove('position-transition-effect');
                        element.removeEventListener('transitionend', handleTransitionEnd);
                    };
                    element.addEventListener('transitionend', handleTransitionEnd);
                }
            });
        });
    }

    // 应用离开动画效果
    function applyExitAnimations(element) {
        if (!animationsEnabled) return;

        // 检查是否在排除列表中
        if (userConfig.excludedTags.includes(element.tagName.toLowerCase())) return;
        // 检查元素是否可见
        if (!isElementVisible(element)) return;
        // 检查是否为要排除的 Bilibili 元素
        if (element === bilibiliExcludedElement) return;

        // 如果元素已经有离开动画,直接返回
        if (element.classList.contains('fade-out-effect')) return;

        // 获取元素的原始透明度
        const computedStyle = window.getComputedStyle(element);
        const initialOpacity = computedStyle.opacity;
        element.style.setProperty('--original-opacity', initialOpacity);

        // 添加渐隐类
        element.classList.add('fade-out-effect');

        // 在动画结束后,从DOM中移除元素
        function handleAnimationEnd() {
            element.removeEventListener('animationend', handleAnimationEnd);
            if (element.parentNode) {
                element.parentNode.removeChild(element);
            }
        }
        element.addEventListener('animationend', handleAnimationEnd);
    }

    // 使用 MutationObserver 监听 DOM 变化
    const observer = new MutationObserver(mutations => {
        if (!animationsEnabled) return;

        if (userConfig.enableMutationDetection) {
            // 统计突变次数
            mutationCount += mutations.length;
            const now = Date.now();
            const elapsed = now - mutationStartTime;
            if (elapsed >= 1000) { // 1秒
                const mutationsPerSecond = mutationCount / (elapsed / 1000);
                mutationCount = 0;
                mutationStartTime = now;

                if (mutationsPerSecond > userConfig.mutationThreshold) {
                    animationsEnabled = false;
                    console.warn('Animations have been disabled due to high mutation rate.');
                    GM_notification(t.animations + ' ' + t.disabled + ' - ' + 'High mutation rate detected.', 'Better Web Animation');
                    updateAnimationMenuCommand();
                    observer.disconnect();
                    return;
                }
            }
        }

        // 使用 requestAnimationFrame 优化回调
        requestAnimationFrame(() => {
            let movedElements = new Set();
            mutations.forEach(mutation => {
                if (mutation.type === 'childList') {
                    // 在节点被添加时应用进入动画
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            applyEnterAnimations(node);
                        }
                    });

                    // 在节点被移除前应用离开动画
                    mutation.removedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            applyExitAnimations(node);
                        }
                    });

                    // 添加父节点到移动元素集合
                    if (mutation.target && mutation.target.nodeType === Node.ELEMENT_NODE) {
                        movedElements.add(mutation.target);
                    }
                } else if ((mutation.type === 'attributes' && userConfig.observeAttributes) ||
                           (mutation.type === 'characterData' && userConfig.observeCharacterData)) {
                    const target = mutation.target;
                    if (target.nodeType === Node.ELEMENT_NODE) {
                        applyTransitionEffect(target);
                    }
                }
            });

            // 应用位置过渡效果
            if (userConfig.enablePositionTransition && movedElements.size > 0) {
                applyPositionTransition(Array.from(movedElements));
            }
        });
    });

    // 突变计数器
    let mutationCount = 0;
    let mutationStartTime = Date.now();

    // 开始观察
    function startObserving() {
        observer.observe(document.body, {
            childList: true,
            attributes: userConfig.observeAttributes,
            characterData: userConfig.observeCharacterData,
            subtree: true,
            attributeFilter: ['src', 'style', 'class'], // 观察属性变化,尤其是图片的'src'变化
        });
    }

    if (animationsEnabled) {
        startObserving();
    }

    // 对现有的图片元素应用动画
    function applyAnimationsToExistingImages() {
        document.querySelectorAll('img').forEach(img => {
            if (!img.complete) {
                img.addEventListener('load', () => {
                    applyEnterAnimations(img);
                });
            } else {
                applyEnterAnimations(img);
            }
        });
    }

    applyAnimationsToExistingImages();

    // 配置面板
    function showConfigPanel() {
        // 检查是否已存在配置面板
        if (document.getElementById('animation-config-panel')) return;

        // 创建遮罩层
        const overlay = document.createElement('div');
        overlay.id = 'animation-config-overlay';
        overlay.style.position = 'fixed';
        overlay.style.top = '0';
        overlay.style.left = '0';
        overlay.style.width = '100%';
        overlay.style.height = '100%';
        overlay.style.backgroundColor = 'rgba(0,0,0,0.5)';
        overlay.style.zIndex = '9998';
        document.body.appendChild(overlay);

        // 创建配置面板的HTML结构
        const panel = document.createElement('div');
        panel.id = 'animation-config-panel';
        panel.style.position = 'fixed';
        panel.style.top = '50%';
        panel.style.left = '50%';
        panel.style.transform = 'translate(-50%, -50%)';
        panel.style.backgroundColor = '#fff';
        panel.style.border = '1px solid #ccc';
        panel.style.padding = '20px';
        panel.style.zIndex = '9999';
        panel.style.maxWidth = '400px';
        panel.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)';
        panel.style.overflowY = 'auto';
        panel.style.maxHeight = '80%';

        panel.innerHTML = `
        <h2>${t.settingsTitle}</h2>
        <label>
            ${t.animationPresets}
            <select id="animationPreset">
                <option value="default" ${userConfig.animationPreset === 'default' ? 'selected' : ''}>${t.defaultPreset}</option>
                <option value="gentle" ${userConfig.animationPreset === 'gentle' ? 'selected' : ''}>${t.gentlePreset}</option>
                <option value="dynamic" ${userConfig.animationPreset === 'dynamic' ? 'selected' : ''}>${t.dynamicPreset}</option>
                <option value="custom" ${userConfig.animationPreset === 'custom' ? 'selected' : ''}>${t.customPreset}</option>
            </select>
        </label>
        <br>
        <div id="customSettings" style="display: ${userConfig.animationPreset === 'custom' ? 'block' : 'none'};">
            <label>
                ${t.fadeInDuration}
                <input type="number" id="fadeInDuration" value="${userConfig.fadeInDuration}" step="0.1" min="0">
            </label>
            <br>
            <label>
                ${t.fadeOutDuration}
                <input type="number" id="fadeOutDuration" value="${userConfig.fadeOutDuration}" step="0.1" min="0">
            </label>
            <br>
            <label>
                ${t.transitionDuration}
                <input type="number" id="transitionDuration" value="${userConfig.transitionDuration}" step="0.1" min="0">
            </label>
            <br>
            <label>
                ${t.positionTransitionDuration}
                <input type="number" id="positionTransitionDuration" value="${userConfig.positionTransitionDuration}" step="0.1" min="0">
            </label>
            <br>
        </div>
        <label>
            ${t.animationTypes}
            <br>
            <input type="checkbox" id="animationFade" ${userConfig.animationTypes.includes('fade') ? 'checked' : ''}> ${t.fade}
            <br>
            <input type="checkbox" id="animationZoom" ${userConfig.animationTypes.includes('zoom') ? 'checked' : ''}> ${t.zoom}
            <br>
            <input type="checkbox" id="animationRotate" ${userConfig.animationTypes.includes('rotate') ? 'checked' : ''}> ${t.rotate}
            <br>
            <input type="checkbox" id="animationSlide" ${userConfig.animationTypes.includes('slide') ? 'checked' : ''}> ${t.slide}
        </label>
        <br>
        <label>
            ${t.excludedTags}
            <input type="text" id="excludedTags" value="${userConfig.excludedTags.join(',')}">
        </label>
        <br>
        <label>
            <input type="checkbox" id="observeAttributes" ${userConfig.observeAttributes ? 'checked' : ''}> ${t.observeAttributes}
        </label>
        <br>
        <label>
            <input type="checkbox" id="observeCharacterData" ${userConfig.observeCharacterData ? 'checked' : ''}> ${t.observeCharacterData}
        </label>
        <br>
        <label>
            <input type="checkbox" id="detectFrequentChanges" ${userConfig.detectFrequentChanges ? 'checked' : ''}> ${t.detectFrequentChanges}
        </label>
        <br>
        <label>
            ${t.changeThreshold}
            <input type="number" id="changeThreshold" value="${userConfig.changeThreshold}" min="1">
        </label>
        <br>
        <label>
            ${t.detectionDuration}
            <input type="number" id="detectionDuration" value="${userConfig.detectionDuration}" min="100">
        </label>
        <br>
        <label>
            <input type="checkbox" id="enableInitialFadeIn" ${userConfig.enableInitialFadeIn ? 'checked' : ''}> ${t.enableInitialFadeIn}
        </label>
        <br>
        <label>
            <input type="checkbox" id="enablePositionTransition" ${userConfig.enablePositionTransition ? 'checked' : ''}> ${t.enablePositionTransition}
        </label>
        <br>
        <label>
            <input type="checkbox" id="enableComplexityDetection" ${userConfig.enableComplexityDetection ? 'checked' : ''}> ${t.enableComplexityDetection}
        </label>
        <br>
        <label>
            ${t.complexityThreshold}
            <input type="number" id="complexityThreshold" value="${userConfig.complexityThreshold}" min="0">
        </label>
        <br>
        <label>
            <input type="checkbox" id="enableMutationDetection" ${userConfig.enableMutationDetection ? 'checked' : ''}> ${t.enableMutationDetection}
        </label>
        <br>
        <label>
            ${t.mutationThreshold}
            <input type="number" id="mutationThreshold" value="${userConfig.mutationThreshold}" min="0">
        </label>
        <br><br>
        <button id="saveConfig">${t.saveConfig}</button>
        <button id="cancelConfig">${t.cancelConfig}</button>
        `;

        document.body.appendChild(panel);

        // 根据预设切换自定义设置的显示
        document.getElementById('animationPreset').addEventListener('change', (e) => {
            const customSettings = document.getElementById('customSettings');
            if (e.target.value === 'custom') {
                customSettings.style.display = 'block';
            } else {
                customSettings.style.display = 'none';
            }
        });

        // 添加事件监听
        document.getElementById('saveConfig').addEventListener('click', () => {
            // 保存配置
            userConfig.animationPreset = document.getElementById('animationPreset').value;

            if (userConfig.animationPreset === 'custom') {
                userConfig.fadeInDuration = parseFloat(document.getElementById('fadeInDuration').value) || defaultConfig.fadeInDuration;
                userConfig.fadeOutDuration = parseFloat(document.getElementById('fadeOutDuration').value) || defaultConfig.fadeOutDuration;
                userConfig.transitionDuration = parseFloat(document.getElementById('transitionDuration').value) || defaultConfig.transitionDuration;
                userConfig.positionTransitionDuration = parseFloat(document.getElementById('positionTransitionDuration').value) || defaultConfig.positionTransitionDuration;
            }

            const animationTypes = [];
            if (document.getElementById('animationFade').checked) animationTypes.push('fade');
            if (document.getElementById('animationZoom').checked) animationTypes.push('zoom');
            if (document.getElementById('animationRotate').checked) animationTypes.push('rotate');
            if (document.getElementById('animationSlide').checked) animationTypes.push('slide');
            userConfig.animationTypes = animationTypes.length > 0 ? animationTypes : defaultConfig.animationTypes;

            const excludedTags = document.getElementById('excludedTags').value.split(',').map(tag => tag.trim().toLowerCase()).filter(tag => tag);
            userConfig.excludedTags = excludedTags.length > 0 ? excludedTags : defaultConfig.excludedTags;

            userConfig.observeAttributes = document.getElementById('observeAttributes').checked;
            userConfig.observeCharacterData = document.getElementById('observeCharacterData').checked;
            userConfig.detectFrequentChanges = document.getElementById('detectFrequentChanges').checked;
            userConfig.changeThreshold = parseInt(document.getElementById('changeThreshold').value) || defaultConfig.changeThreshold;
            userConfig.detectionDuration = parseInt(document.getElementById('detectionDuration').value) || defaultConfig.detectionDuration;

            userConfig.enableInitialFadeIn = document.getElementById('enableInitialFadeIn').checked;

            userConfig.enablePositionTransition = document.getElementById('enablePositionTransition').checked;

            userConfig.enableComplexityDetection = document.getElementById('enableComplexityDetection').checked;
            userConfig.complexityThreshold = parseInt(document.getElementById('complexityThreshold').value) || defaultConfig.complexityThreshold;

            userConfig.enableMutationDetection = document.getElementById('enableMutationDetection').checked;
            userConfig.mutationThreshold = parseInt(document.getElementById('mutationThreshold').value) || defaultConfig.mutationThreshold;

            // 保存到本地存储
            GM_setValue('userConfig', userConfig);

            // 更新样式和观察器
            addGlobalStyles();
            observer.disconnect();
            if (animationsEnabled) {
                startObserving();
            }

            // 对现有的图片重新应用动画
            applyAnimationsToExistingImages();

            // 移除配置面板
            panel.remove();
            overlay.remove();
        });

        document.getElementById('cancelConfig').addEventListener('click', () => {
            // 移除配置面板
            panel.remove();
            overlay.remove();
        });

        overlay.addEventListener('click', () => {
            panel.remove();
            overlay.remove();
        });
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址