// ==UserScript==
// @name CSDN自用极致净化 v5.2.0
// @description 带按钮开关控制,支持返回顶部/黑暗模式/广告去除
// @version 5.2.0
// @author Finn
// @namespace https://github.com/
// @homepage https://github.com/
// @run-at document-start
// @match *://blog.csdn.net/*/article/details/*
// @match *://*.blog.csdn.net/article/details/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// 用户默认配置
const defaultSettings = {
darkMode: false,
asidePin: false,
debugMode: false,
finnWidth: 1150,
showButtons: false // 新增:控制按钮是否显示
};
// 获取用户配置
let FinnData = new Proxy({...defaultSettings, ...GM_getValue('FinnData', {})}, {
set(target, key, val) {
const B = Reflect.set(target, key, val);
GM_setValue('FinnData', target);
return B;
}
});
// 创建调试面板
const debugPanel = document.createElement('div');
debugPanel.id = 'finn-debug';
debugPanel.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
background: rgba(30,30,46,0.85);
color: white;
padding: 12px 18px;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0,0,0,0.4);
font-family: 'Fira Code', 'Consolas', monospace;
z-index: 99999;
font-size: 13px;
max-width: 400px;
max-height: 380px;
overflow-y: auto;
line-height: 1.5;
border: 1px solid rgba(255,255,255,0.12);
transform: ${FinnData.debugMode ? 'translateX(0)' : 'translateX(120%)'};
opacity: ${FinnData.debugMode ? '1' : '0'};
transition: transform 0.3s ease, opacity 0.2s ease;
`;
debugPanel.innerHTML = `
<div style="position: sticky; top: 0; background: rgba(30,30,46,0.9); padding: 8px 0; margin-bottom: 10px; border-bottom: 1px solid rgba(255,255,255,0.15);">
<strong>CSDN 增强调试面板 [v2.5.0]</strong>
<button id="closeDebug" style="float:right; background: #ff4d4d; border: none; color: white; padding: 2px 10px; border-radius: 4px; cursor: pointer;">×</button>
</div>
<div id="debugLog"></div>
`;
document.documentElement.appendChild(debugPanel);
// 日志函数
function logDebug(message, forceLog = false) {
if (!FinnData.debugMode && !forceLog) return;
const timestamp = new Date().toLocaleTimeString();
const logEntry = document.createElement('div');
logEntry.innerHTML = `<span style="color: #4ecca3">[${timestamp}]</span> ${message}`;
const logBox = debugPanel.querySelector('#debugLog');
logBox.appendChild(logEntry);
// 自动滚动到底部
logBox.scrollTop = logBox.scrollHeight;
}
// 调试模式切换
function toggleDebugMode(forceState = null) {
FinnData.debugMode = forceState !== null ? forceState : !FinnData.debugMode;
debugPanel.style.transform = FinnData.debugMode
? 'translateX(0)'
: 'translateX(120%)';
debugPanel.style.opacity = FinnData.debugMode ? '1' : '0';
logDebug(`调试模式 ${FinnData.debugMode ? '开启' : '关闭'}`, true);
}
// 初始日志
logDebug('脚本初始化完成', true);
logDebug(`当前设置: 黑暗模式=${FinnData.darkMode} 侧边栏固定=${FinnData.asidePin} 调试模式=${FinnData.debugMode} 宽度=${FinnData.finnWidth}px 按钮显示=${FinnData.showButtons}`, true);
// 添加菜单命令
GM_registerMenuCommand('⇧ + 🅱 显示神秘按钮');
GM_registerMenuCommand('⇧ + 🅳 显示调试面板');
GM_registerMenuCommand('⇧ + ⬆️ 或 ⬇️ 调整文章宽度');
//GM_registerMenuCommand('⭐ 切换调试面板', toggleDebugMode);
/*GM_registerMenuCommand('🔘 切换按钮显示', () => {
FinnData.showButtons = !FinnData.showButtons;
updateButtonsVisibility();
logDebug(`按钮显示状态: ${FinnData.showButtons ? '显示' : '隐藏'}`, true);
});
GM_registerMenuCommand('⚙️ 重置用户设置', () => {
FinnData = {...defaultSettings};
updateButtonsVisibility();
logDebug('已恢复默认设置', true);
});*/
// 平滑滚动函数
function smoothScrollToTop(duration = 400) {
const start = window.scrollY;
const startTime = performance.now();
function scrollStep(timestamp) {
const elapsed = timestamp - startTime;
const progress = Math.min(elapsed / duration, 1);
window.scrollTo(0, start * (1 - cubicEase(progress)));
if (progress < 1) {
window.requestAnimationFrame(scrollStep);
} else {
logDebug('滚动完成');
}
}
function cubicEase(t) {
return t < 0.5
? 4 * t * t * t
: (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
}
window.requestAnimationFrame(scrollStep);
}
// 样式模板
const getStyle = (finnWidth) => `
:root {
--finn-width: ${finnWidth}px;
}
[data-finn] body {
--debug-color: #4ecca3;
}
#content_views,#content_views * {
user-select: auto !important;
}
[darkMode],[darkMode] #darkBtn,[darkMode] code.hljs,[darkMode] img,[darkMode] pre.prettyprint {
filter: invert(1) hue-rotate(180deg);
transition: all .5s;
}
[darkMode] .markdown_views.prism-github-gist .prettyprint,
[darkMode] .markdown_views.prism-github-gist .prettyprint .pre-numbering,
[darkMode] .markdown_views.prism-github-gist pre code,
[darkMode] .markdown_views.prism-github-gist pre.prettyprint {
background-color: #0d1117 !important;
transition: all 0.1s;
}
[darkMode] body {
background: #ebebeb !important;
transition:background .7s;
}
#darkBtn {
position: fixed;
top: 8px;
left: 50px;
width: 32px;
height: 32px;
z-index: 9999;
background: gold;
cursor: pointer;
border-radius: 50%;
transition: all .5s;
display: ${FinnData.showButtons ? 'block' : 'none'};
}
[darkMode] #darkBtn {
background: transparent;
box-shadow: -.5em .3em 0 0 gold;
left: 60px;
top: 4px;
}
#darkBtn:after {
content: "打开夜间模式";
width: 100px;
position: absolute;
right: -120px;
top: 4px;
font-size: 14px;
font-weight: 600;
transition: all .5s;
display: none;
}
[darkMode] #darkBtn:after {
content: "关闭夜间模式";
right: -110px;
top: 8px;
filter: invert(1) hue-rotate(180deg);
font-weight: 600;
}
#darkBtn:hover:after {
display: block;
}
/* 返回顶部按钮样式 */
#FinnTop {
position: fixed;
right: 40px;
bottom: 80px;
width: 45px;
height: 45px;
background: #2e7d32;
color: white;
border-radius: 50%;
cursor: pointer;
display: none;
justify-content: center;
align-items: center;
box-shadow: 0 0 15px rgba(0,0,0,0.2);
transition: all 0.3s ease;
z-index: 9999;
}
#FinnTop.show {
display: flex;
}
#FinnTop:hover {
transform: translateY(-5px);
background: #1b5e20;
box-shadow: 0 5px 20px rgba(0,0,0,0.3);
}
#FinnTop::before {
content: "↑";
font-size: 24px;
font-weight: bold;
}
/* 侧边栏固定按钮 */
#pinBtn {
color: #ccc;
position: fixed;
height: 26px;
width: 19px;
top: 12px;
left: 18px;
z-index: 9999;
cursor: pointer;
display: ${FinnData.showButtons ? 'block' : 'none'};
}
#pinBtn::before {
content: "";
position: absolute;
width: 100%;
height: 100%;
border: 2px solid;
border-right: none;
}
#pinBtn::after {
content: "";
position: absolute;
left: -2px;
height: 23px;
border: 1px solid;
transition: left 0.3s ease;
}
[asidePin] #pinBtn::after {
left: 3px;
}
/* 隐藏不需要的元素 */
#asideArchive, .aside-box-footer, #asideCategory,
#asideHotArticle, #asideNewComments, #asideNewNps,
#asideSearchArticle+.box-shadow.mb8, #blogColumnPayAdvert,
#csdn-toolbar .toolbar-advert, #csdn-toolbar .toolbar-container-left,
#csdn-toolbar .toolbar-container-right, #dmp_ad_58, #footerRightAds,
#passportbox, #placeholder, #rightAside, #recommendNps,
.blog-footer-bottom, .csdn-shop-window-common,
.csdn-side-toolbar, .hide-article-box.hide-article-pos.text-center,
.leftPop, .login-mark, .opt-box.text-center, .template-box,
.toolbar-search-drop-menu.toolbar-search-half,
::-webkit-input-placeholder, .passport-login-mark,
.passport-login-container, .hide-preCode-box, #marketingBox,
.icon-fire, #toolBarBox .tool-hover-tip+.tool-active-list,
.passport-login-tip-container, #remuneration, #asideWriteGuide,
#asideSearchArticle, #tool-share, #treeSkill,
#swiper-remuneration-container, .swiper-slide-box-remuneration,
#toolbar-c-box-button,.c-box, .recommend-item-box, .blog_container_aside,
.more-toolbox-new more-toolbar, .article-info-box,#blogHuaweiyunAdvert,
.blog-extension-box, .more-toolbar,.more-toolbox-new, .toolbar-container,
#toolbarBox,.article-resource-info-box, #blogVoteBox,
.ai-abstract-box, .btn-code-notes.ckeditor {
display: none !important;
margin: 0;
color: transparent;
visibility: hidden;
height: 0
}
.toolbar-search.onlySearch {
transition: all .3s ease
}
body #csdn-toolbar {
box-shadow: 0 2px 10px 0 rgba(0,0,0,.15);
position: fixed !important;
top: 0;
left: 0;
width: 100%;
z-index: 1993;
}
.toolbar-search.onlySearch:focus-within {
max-width: var(--finn-width) !important;
width: var(--finn-width) !important;
}
#asidedirectory, .d-flex {
display: block !important;
}
.main_father, pre.set-code-hide {
height: auto !important;
}
main {
cursor: auto;
width: 100% !important;
box-shadow: 0 0 30px rgb(0 0 0 / 25%);
margin-bottom: 0 !important;
}
#mainBox {
position: relative;
margin: 10px auto 30px;
width: var(--finn-width) !important;
padding: 0 10px;
box-sizing: content-box;
cursor: e-resize;
}
.comment-list-box {
max-height: none !important;
}
#commentPage, .toolbar-container-middle {
display: block !important;
}
.toolbar-container {
min-width: 100% !important;
}
#article_content {
height: auto !important;
}
.comment-list-container {
padding: 4px 0 !important;
}
.article-header-box {
padding-top: 18px !important;
}
main .comment-box {
padding: 0;
box-shadow: 0 0 10px rgba(0,0,0,.05);
margin: 8px 0;
}
.blog_container_aside {
width: 300px !important;
height: calc(100% - 100px);
overflow-y: auto;
overflow-x: hidden;
border: solid #fff;
border-width: 20px 4px 0 4px;
background: #fff;
box-sizing: content-box;
position: fixed;
top: initial !important;
left: -307px !important;
transition: all .35s;
box-shadow: 2px 0 10px 0 rgba(0,0,0,.15);
z-index: 1111 !important;
cursor: auto;
}
.blog_container_aside:hover {
left: 0 !important;
}
[asidePin] aside.blog_container_aside {
left: 0 !important;
}
aside.blog_container_aside:hover:before,
[asidePin] aside.blog_container_aside:before {
width: 308px;
height: 18px;
padding: 4px 2px;
writing-mode: rl-tb;
font-size: 14px;
top: 66px;
border-radius: 0
}
html body {
min-width: 100%;
background: #eee;
}
/* 简单博客样式 */
@media screen and (max-width: 768px) {
#mainBox {
width: 95% !important;
max-width: calc(100% - 20px);
}
#FinnTop {
right: 15px;
bottom: 60px;
}
}
.no-select {
user-select: none;
}
`;
// 插入样式的函数
const insertStylesAndHTML = () => {
// 应用初始设置
if (FinnData.darkMode) {
document.documentElement.toggleAttribute('darkMode', true);
}
if (FinnData.asidePin) {
document.documentElement.toggleAttribute('asidePin', true);
}
// 创建样式和DOM元素
const styleTag = document.createElement('style');
styleTag.textContent = getStyle(FinnData.finnWidth);
document.head.appendChild(styleTag);
const markup = `
<div data-finn id="darkBtn" title="切换黑暗模式"></div>
<div data-finn id="pinBtn" title="固定侧边栏"></div>
<div data-finn id="FinnTop" title="返回顶部"></div>
`;
document.body.insertAdjacentHTML('afterbegin', markup);
logDebug('样式和按钮已注入');
};
// 更新按钮可见性
function updateButtonsVisibility() {
const darkBtn = document.getElementById('darkBtn');
const pinBtn = document.getElementById('pinBtn');
if (darkBtn) darkBtn.style.display = FinnData.showButtons ? 'block' : 'none';
if (pinBtn) pinBtn.style.display = FinnData.showButtons ? 'block' : 'none';
logDebug(`按钮可见性更新: ${FinnData.showButtons ? '显示' : '隐藏'}`);
}
// 初始化按钮事件
function initButtonEvents() {
logDebug('绑定按钮事件');
const darkBtn = document.getElementById('darkBtn');
const pinBtn = document.getElementById('pinBtn');
const topBtn = document.getElementById('FinnTop');
if (darkBtn) {
darkBtn.addEventListener('click', () => {
FinnData.darkMode = !FinnData.darkMode;
document.documentElement.toggleAttribute('darkMode', FinnData.darkMode);
logDebug(`黑暗模式: ${FinnData.darkMode}`);
});
}
if (pinBtn) {
pinBtn.addEventListener('click', () => {
FinnData.asidePin = !FinnData.asidePin;
document.documentElement.toggleAttribute('asidePin', FinnData.asidePin);
logDebug(`固定侧边栏: ${FinnData.asidePin}`);
});
}
if (topBtn) {
topBtn.addEventListener('click', smoothScrollToTop);
// 滚动事件处理
window.addEventListener('scroll', () => {
topBtn.className = window.scrollY > 500 ? 'show' : '';
});
}
// 宽度调整
const mainBox = document.getElementById('mainBox');
if (mainBox) {
mainBox.addEventListener('mousedown', startResize);
}
// 调试面板关闭按钮
const closeDebugBtn = debugPanel.querySelector('#closeDebug');
if (closeDebugBtn) {
closeDebugBtn.addEventListener('click', () => {
toggleDebugMode(false);
});
}
}
// 宽度调整函数
function startResize(e) {
const mainBox = document.getElementById('mainBox');
if (!mainBox || e.target !== mainBox) return;
const startX = e.clientX;
const startWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--finn-width'));
const maxWidth = window.innerWidth * 0.95;
document.body.classList.add('no-select');
function resize(e) {
const newWidth = Math.max(850, Math.min(startWidth + (e.clientX - startX) * 1.5, maxWidth));
document.documentElement.style.setProperty('--finn-width', `${newWidth}px`);
logDebug(`内容宽度调整: ${newWidth}px`);
}
function stopResize() {
document.removeEventListener('mousemove', resize);
document.removeEventListener('mouseup', stopResize);
FinnData.finnWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--finn-width'));
document.body.classList.remove('no-select');
logDebug(`最终宽度设置: ${FinnData.finnWidth}px`);
}
document.addEventListener('mousemove', resize);
document.addEventListener('mouseup', stopResize);
}
// 键盘快捷键
document.addEventListener('keydown', (e) => {
// Shift+D 切换调试面板
if (e.shiftKey && e.key === 'D') {
e.preventDefault();
toggleDebugMode();
}
// Shift+B 切换按钮显示
if (e.shiftKey && e.key === 'B') {
e.preventDefault();
FinnData.showButtons = !FinnData.showButtons;
updateButtonsVisibility();
logDebug(`按钮显示状态: ${FinnData.showButtons ? '显示' : '隐藏'}`, true);
}
// Shift+↑/↓ 调整宽度
if (e.shiftKey && (e.key === 'ArrowUp' || e.key === 'ArrowDown')) {
e.preventDefault();
const step = e.key === 'ArrowUp' ? 50 : -50;
const curWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--finn-width'));
const newWidth = Math.max(850, Math.min(curWidth + step, window.innerWidth * 0.95));
document.documentElement.style.setProperty('--finn-width', `${newWidth}px`);
FinnData.finnWidth = newWidth;
logDebug(`宽度快速调整: ${newWidth}px`);
}
});
// 主初始化流程
function initAll() {
try {
insertStylesAndHTML();
initButtonEvents();
updateButtonsVisibility();
// 初始滚动位置检查
window.dispatchEvent(new Event('scroll'));
logDebug('脚本功能已加载。按 Shift+D 切换调试面板,Shift+B 切换按钮显示', true);
} catch (err) {
console.error('[CSDN-Focus-TRACE]', err);
if (err.stack) {
logDebug(`初始化错误: ${err.message}
${err.stack.split('\n').slice(0,3).join('\n')}`, true);
} else {
logDebug(`初始化错误: ${err.message}`, true);
}
}
}
// 监控DOM准备状态
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initAll);
} else {
initAll();
}
})();