// ==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();
});
}
})();