// ==UserScript==
// @name Via Css隐藏规则日志(高斯模糊)
// @namespace https://viayoo.com/
// @version 0.1
// @license MIT
// @description 检测哪些Css规则在Via上生效,并输出匹配日志。支持动态检测开关。显示结果使用 iOS 风格高斯模糊提示框,支持自适应宽度和滚动,匹配结果双击消失。
// @author Copilot & Grok
// @run-at document-end
// @match *://*/*
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function() {
'use strict';
const BUTTON_STORAGE = {
ENABLED: 'floatingButtonEnabled',
LEFT: 'floatingButtonLeft',
TOP: 'floatingButtonTop',
DYNAMIC_OBSERVER_ENABLED: 'dynamicObserverEnabled'
};
const DEFAULT_CSS_FILE_PATH = '/via_inject_blocker.css';
const LONG_PRESS_THRESHOLD = 500;
const OBSERVER_INTERVAL = 2000;
const BATCH_SIZE = 100;
const createStyledElement = (tag, styles, text) => {
const el = document.createElement(tag);
Object.assign(el.style, styles);
if (text) el.textContent = text;
return el;
};
const splitSelectors = cssText => {
const selectors = [];
let current = '';
let inBlock = false;
let bracketDepth = 0;
let parenDepth = 0;
let inQuote = false;
let quoteChar = null;
for (let i = 0; i < cssText.length; i++) {
const char = cssText[i];
if (inQuote) {
current += char;
if (char === quoteChar) inQuote = false;
continue;
}
if (char === '"' || char === "'") {
inQuote = true;
quoteChar = char;
current += char;
continue;
}
if (inBlock) {
if (char === '}') inBlock = false;
continue;
}
if (char === '[') bracketDepth++;
if (char === ']') bracketDepth--;
if (char === '(') parenDepth++;
if (char === ')') parenDepth--;
if (char === '{' && bracketDepth === 0 && parenDepth === 0 && !inQuote) {
if (current.trim()) selectors.push(current.trim());
current = '';
inBlock = true;
continue;
}
if (char === ',' && bracketDepth === 0 && parenDepth === 0 && !inQuote && !inBlock) {
if (current.trim()) selectors.push(current.trim());
current = '';
} else {
current += char;
}
}
if (current.trim() && !inBlock) selectors.push(current.trim());
return selectors.filter(s => s && !s.includes('!important') && !s.startsWith('@'));
};
const checkActiveSelectors = async (cssText) => {
try {
const selectors = splitSelectors(cssText);
const activeRules = [];
const debugInfo = [];
for (let i = 0; i < selectors.length; i += BATCH_SIZE) {
const batch = selectors.slice(i, i + BATCH_SIZE);
for (const selector of batch) {
try {
const elements = document.querySelectorAll(selector);
if (elements.length) {
activeRules.push({
selector: selector.trim(),
count: elements.length
});
}
debugInfo.push({
selector: selector.trim(),
count: elements.length,
exists: elements.length > 0 ? '匹配成功' : '无匹配元素'
});
} catch (e) {
debugInfo.push({
selector: selector.trim(),
count: 0,
exists: `选择器无效: ${e.message}`
});
}
}
await new Promise(resolve => setTimeout(resolve, 0));
}
return {
activeRules,
debugInfo
};
} catch (e) {
console.error(`[Via CSS Logger] 检查选择器失败: ${e.message}`);
return {
activeRules: [],
debugInfo: []
};
}
};
const showBlurToast = (message, duration = 10000, isPrimary = false) => {
const existingToast = document.querySelector('.via-blur-toast');
if (existingToast) existingToast.remove();
const toast = createStyledElement('div', {
position: 'fixed',
left: '50%',
transform: 'translateX(-50%) scale(0.8)', // 初始缩小
maxWidth: 'min(100vw, 600px)',
maxHeight: isPrimary ? '80vh' : '50vh',
padding: '15px 20px',
backgroundColor: 'rgba(255, 255, 255, 0.7)',
backdropFilter: 'blur(12px)',
WebkitBackdropFilter: 'blur(12px)',
borderRadius: '12px',
border: '1px solid rgba(255, 255, 255, 0.3)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)',
color: '#333',
fontSize: '16px',
fontFamily: '-apple-system, BlinkMacSystemFont, sans-serif',
zIndex: '10001',
opacity: '0',
transition: 'all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55)', // 回弹效果曲线
whiteSpace: 'pre-wrap',
textAlign: isPrimary ? 'left' : 'center',
lineHeight: '1.5',
cursor: 'pointer',
boxSizing: 'border-box',
overflowY: isPrimary ? 'auto' : 'hidden',
scrollbarWidth: 'thin',
scrollbarColor: 'rgba(0, 0, 0, 0.3) transparent'
}, message);
// 自定义滚动条样式
const styleSheet = document.createElement('style');
styleSheet.textContent = `
.via-blur-toast::-webkit-scrollbar {
width: 8px;
}
.via-blur-toast::-webkit-scrollbar-track {
background: transparent;
}
.via-blur-toast::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.3);
border-radius: 9px;
}
.via-blur-toast::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.1);
}
`;
document.head.appendChild(styleSheet);
if (isPrimary) {
toast.style.top = '50%';
toast.style.transform = 'translate(-50%, -50%) scale(0.8)';
} else {
toast.style.top = '85%';
}
toast.className = 'via-blur-toast';
document.body.appendChild(toast);
// 入场动画
setTimeout(() => {
toast.style.opacity = '1';
toast.style.transform = isPrimary ? 'translate(-50%, -50%) scale(1)' : 'translateX(-50%) scale(1)';
}, 10);
// 离场动画
const timeout = setTimeout(() => {
toast.style.opacity = '0';
toast.style.transform = isPrimary ? 'translate(-50%, -50%) scale(0.8)' : 'translateX(-50%) scale(0.8)';
setTimeout(() => {
toast.remove();
styleSheet.remove();
}, 500); // 与 transition 持续时间一致
}, duration);
// 双击/单击移除
const removeToast = () => {
clearTimeout(timeout);
toast.style.opacity = '0';
toast.style.transform = isPrimary ? 'translate(-50%, -50%) scale(0.8)' : 'translateX(-50%) scale(0.8)';
setTimeout(() => {
toast.remove();
styleSheet.remove();
}, 500);
};
if (isPrimary) {
toast.addEventListener('dblclick', removeToast);
} else {
toast.addEventListener('click', removeToast);
}
};
const checkCssFile = async (enableDynamic = false) => {
const cssUrl = `http://${window.location.hostname}${DEFAULT_CSS_FILE_PATH}`;
console.log(`[Via CSS Logger] 尝试获取 CSS 文件: ${cssUrl}`);
try {
const response = await fetch(cssUrl, {
cache: 'no-cache'
});
console.log(`[Via CSS Logger] 获取 CSS 文件,状态码: ${response.status}`);
if (!response.ok) throw new Error(`状态码: ${response.status}`);
const rawCss = await response.text();
if (!rawCss.trim()) throw new Error('CSS文件为空');
const checkRules = async () => {
try {
const {
activeRules
} = await checkActiveSelectors(rawCss);
if (activeRules.length) {
const messageLines = [
`🎉 检测完成!共 ${activeRules.length} 条规则生效:`,
'--------------------------------',
...activeRules.map((r, i) =>
`${i + 1}. 规则: ${window.location.hostname}##${r.selector}\n 匹配数: ${r.count}\n`
),
'--------------------------------'
];
const fullMessage = messageLines.join('\n');
console.log(fullMessage);
showBlurToast(fullMessage.slice(0, 2500) + (fullMessage.length > 2500 ? '\n\nℹ️ 日志过长,请查看控制台以获取完整信息' : ''), 10000, true);
} else {
showBlurToast('⚠️ 没有发现生效的CSS规则!', 3000, false);
}
if (enableDynamic && GM_getValue(BUTTON_STORAGE.DYNAMIC_OBSERVER_ENABLED, false)) {
startDynamicObserver(rawCss);
}
} catch (e) {
console.error(`[Via CSS Logger] 规则检查失败: ${e.message}`);
showBlurToast(`❌ 规则检查失败: ${e.message}`, 5000, false);
}
};
if (document.readyState === 'complete') {
await checkRules();
} else {
window.addEventListener('load', async () => {
await checkRules();
}, {
once: true
});
}
} catch (e) {
console.error(`[Via CSS Logger] CSS检查失败: ${e.message}`);
showBlurToast(`❌ 检查CSS文件失败: ${e.message}\nURL: ${cssUrl}`, 5000, false);
}
};
const startDynamicObserver = (cssText) => {
try {
let lastCheck = Date.now();
let hasTriggered = false;
let isChecking = false;
const observer = new MutationObserver(async (mutations) => {
if (hasTriggered || isChecking) return;
const now = Date.now();
if (now - lastCheck >= OBSERVER_INTERVAL) {
isChecking = true;
try {
console.log(`[Via CSS Logger] 动态检查触发: ${new Date().toISOString()}, 变化数: ${mutations.length}`);
const {
activeRules
} = await checkActiveSelectors(cssText);
if (activeRules.length) {
hasTriggered = true;
observer.disconnect();
const messageLines = [
`🎉 动态检测完成!共 ${activeRules.length} 条规则生效:`,
'--------------------------------',
...activeRules.map((r, i) =>
`${i + 1}. 规则: ${window.location.hostname}##${r.selector}\n 匹配数: ${r.count}\n`
),
'--------------------------------'
];
const fullMessage = messageLines.join('\n');
console.log(`[Via CSS Logger] 弹窗输出:`, fullMessage);
showBlurToast(fullMessage.slice(0, 2500), 10000, true); // 主要消息
}
} catch (e) {
console.error(`[Via CSS Logger] 动态检查失败: ${e.message}`);
showBlurToast(`❌ 动态检查失败: ${e.message}`, 5000, false);
} finally {
isChecking = false;
lastCheck = now;
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
} catch (e) {
console.error(`[Via CSS Logger] 动态监控启动失败: ${e.message}`);
showBlurToast(`❌ 动态监控启动失败: ${e.message}`, 5000, false);
}
};
const createFloatingButton = () => {
if (window.self !== window.top) return;
const button = createStyledElement('div', {
position: 'fixed',
zIndex: '10000',
width: '80px',
height: '40px',
backgroundColor: 'rgba(255, 255, 255, 0.2)',
backdropFilter: 'blur(16px)',
WebkitBackdropFilter: 'blur(16px)',
border: '0.5px solid rgba(255, 255, 255, 0.3)',
borderRadius: '12px',
color: '#1C2526',
textAlign: 'center',
lineHeight: '40px',
fontSize: '15px',
fontFamily: 'SF Pro Text, -apple-system, BlinkMacSystemFont, sans-serif',
fontWeight: '500',
boxShadow: '0 4px 16px rgba(0, 0, 0, 0.08), inset 0 1px 1px rgba(255, 255, 255, 0.3)',
cursor: 'pointer',
opacity: '0.95',
transition: 'transform 0.2s cubic-bezier(0.25, 0.1, 0.25, 1.5), opacity 0.2s, box-shadow 0.2s',
touchAction: 'none',
userSelect: 'none',
WebkitUserSelect: 'none'
}, 'CSS日志');
const defaultLeft = window.innerWidth - 100;
const defaultTop = window.innerHeight - 100;
button.style.left = `${GM_getValue(BUTTON_STORAGE.LEFT, defaultLeft)}px`;
button.style.top = `${GM_getValue(BUTTON_STORAGE.TOP, defaultTop)}px`;
document.body.appendChild(button);
let isDragging = false,
startX,
startY,
startLeft,
startTop,
touchStartTime;
button.addEventListener('touchstart', e => {
e.preventDefault();
touchStartTime = Date.now();
isDragging = false;
const touch = e.touches[0];
startX = touch.clientX;
startY = touch.clientY;
startLeft = parseInt(button.style.left) || 0;
startTop = parseInt(button.style.top) || 0;
button.style.transform = 'scale(0.95)';
button.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.1), inset 0 1px 1px rgba(255, 255, 255, 0.3)';
});
button.addEventListener('touchmove', e => {
e.preventDefault();
const touch = e.touches[0];
const deltaX = touch.clientX - startX;
const deltaY = touch.clientY - startY;
if (Date.now() - touchStartTime >= LONG_PRESS_THRESHOLD) {
isDragging = true;
const newLeft = startLeft + deltaX;
const newTop = startTop + deltaY;
const rect = button.getBoundingClientRect();
button.style.left = `${Math.max(0, Math.min(newLeft, window.innerWidth - rect.width))}px`;
button.style.top = `${Math.max(0, Math.min(newTop, window.innerHeight - rect.height))}px`;
}
});
button.addEventListener('touchend', e => {
e.preventDefault();
const touchDuration = Date.now() - touchStartTime;
button.style.transform = 'scale(1)';
button.style.boxShadow = '0 4px 16px rgba(0, 0, 0, 0.08), inset 0 1px 1px rgba(255, 255, 255, 0.3)';
if (isDragging && touchDuration >= LONG_PRESS_THRESHOLD) {
const rect = button.getBoundingClientRect();
const newLeft = rect.left + rect.width / 2 < window.innerWidth / 2 ? 0 : window.innerWidth - rect.width;
button.style.left = `${newLeft}px`;
GM_setValue(BUTTON_STORAGE.LEFT, newLeft);
GM_setValue(BUTTON_STORAGE.TOP, parseInt(button.style.top));
} else if (touchDuration < LONG_PRESS_THRESHOLD) {
showBlurToast('正在匹配对应规则……', 2000, false);
checkCssFile(GM_getValue(BUTTON_STORAGE.DYNAMIC_OBSERVER_ENABLED, false));
}
});
return button;
};
const ensureButtonExists = () => {
if (!document.querySelector("div[style*='CSS日志']")) {
createFloatingButton();
}
};
const resetButtonPosition = () => {
const defaultLeft = window.innerWidth - 100;
const defaultTop = window.innerHeight - 100;
GM_setValue(BUTTON_STORAGE.LEFT, defaultLeft);
GM_setValue(BUTTON_STORAGE.TOP, defaultTop);
const button = document.querySelector("div[style*='CSS日志']");
if (button) {
button.style.left = `${defaultLeft}px`;
button.style.top = `${defaultTop}px`;
}
showBlurToast('✅ 悬浮按钮位置已重置至默认位置!', 3000, false);
};
const init = () => {
try {
const isButtonEnabled = GM_getValue(BUTTON_STORAGE.ENABLED, false);
const isDynamicObserverEnabled = GM_getValue(BUTTON_STORAGE.DYNAMIC_OBSERVER_ENABLED, false);
GM_registerMenuCommand(
isButtonEnabled ? '关闭悬浮按钮' : '开启悬浮按钮',
() => {
GM_setValue(BUTTON_STORAGE.ENABLED, !isButtonEnabled);
showBlurToast(`✅ 悬浮按钮已${isButtonEnabled ? '关闭' : '开启'}!`, 3000, false);
location.reload();
}
);
GM_registerMenuCommand(
isDynamicObserverEnabled ? '关闭动态检测' : '开启动态检测',
() => {
GM_setValue(BUTTON_STORAGE.DYNAMIC_OBSERVER_ENABLED, !isDynamicObserverEnabled);
showBlurToast(`✅ 动态检测已${isDynamicObserverEnabled ? '关闭' : '开启'}!`, 3000, false);
location.reload();
}
);
GM_registerMenuCommand('检测CSS隐藏规则', () => checkCssFile(false));
GM_registerMenuCommand('重置悬浮按钮位置', resetButtonPosition);
if (isButtonEnabled) {
document.readyState === 'loading' ?
document.addEventListener('DOMContentLoaded', ensureButtonExists) :
ensureButtonExists();
}
} catch (e) {
console.error(`[Via CSS Logger] 初始化失败: ${e.message}`);
showBlurToast(`❌ 脚本初始化失败: ${e.message}`, 5000, false);
}
};
init();
})();