// ==UserScript== // @name 阅读模式 (覆盖层·纯净版) // @namespace https://viayoo.com/hqzw7k // @version 19.1.0 // @description 在当前页面之上叠加一层纯净阅读界面,原页面锁定于下方,退出后自动恢复。自动识别正文、标题及翻页链接,过滤视频、样式、脚本、按钮等干扰,支持背景色切换、字号调节、新标签页打开。段落两端对齐、段间距一行,智能保留原网页首行缩进,链接蓝色无下划线,自动换行。默认保留图片,可自定义移除干扰区域。 // @author Grok & DeepSeek // @license MIT // @match *://*/* // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @run-at document-end // ==/UserScript== (function() { 'use strict'; // ==================== 配置区 ==================== const CONTENT_SELECTORS = [ '#chapter-content','#chaptercontent','#ChapterContent','#nr1','#nr_title','#booktext', '#novel_content','#read-content','#chapter-body','#txtContent','#BookText', '#chapter','#contentBox','#book_read','.read-content','.chapter-content', '#J_read','#reader','.article-content','.content-area','.post-body', '#js-read__content','#readbox','#zhengwen','#kui-page-read-txt','#chapter_cont', '#BookTextRead','#book_text','#readtext','#readcon','#TextContent','#text_c', '#txt_td','#TXT','#txt','#zjneirong','.novel_content','.readmain_inner', '.noveltext','.booktext','.yd_text2','#contentTxt','#oldtext','#a_content', '#contents','#content2','#contentts','#content1','#novelcontent','#text', '.card-list','.m-post','.novel-content','pre.novel-content-txt','.share-v2-chapter-content', '.content_ul','.markdown-body', '#article','#articleBody','#article-body','#post-content','#entry-content', '#main-content','#content-main','#post','.article','.article-body', '.post-content','.entry-content','.post-body','.entry-body', '.article-content','.content-inner','.articleText','#js_content', '#baikan-content','.news-content','#content','.content', 'article','[role="main"]','[role="article"]', '[itemprop="articleBody"]','[itemprop="text"]','.post','.blog-post', '.story','.content-wrapper','.readable-content','.post-article' ]; const TITLE_SELECTORS = [ 'h1.article-title','h1.entry-title','h1.post-title','h1.title', 'h1#title','h1#articleTitle','h1[itemprop="headline"]', '.nr_function>h1','h1','.article-title','.entry-title','.post-title', '.title','#title','#article-title','#post-title','.title-color','h2.big.o' ]; const NAV_SELECTORS = { prev: [ 'a:contains(上一页)','a:contains(上一章)','a:contains(上一篇)', 'a.prev','a.previous','.prev','.previous','#prev','#previous', 'a[rel="prev"]','a[rel="previous"]','a[data-prev]' ], next: [ 'a:contains(下一页)','a:contains(下一章)','a:contains(下一篇)', 'a.next','.next','#next','a[rel="next"]','a[data-next]' ], index: [ 'a:contains(目录)','a:contains(章节目录)','a:contains(返回目录)', 'a:contains(书籍页)','a:contains(章节列表)', 'a.index','.index','#index','a.toc','.toc','#toc', 'a[rel="index"]' ] }; const EXTRA_REMOVE_SELECTORS = ['.aritcle_card','.projects-header']; const SHOULD_ACTIVATE_NEW_TAB = window.location.hash === '#readermode'; // ==================== 全局变量 ==================== let isReaderMode = false; let buttonHideTimeout = null; let mainButton = null; let originalUrl = ''; let menuCommandId = null; let readerOverlay = null; let newTabOverlay = null; let contentObserver = null; let originalContentElement = null; let originalBodyOverflow = ''; let originalBodyPosition = ''; let originalBodyTop = ''; let originalBodyWidth = ''; let scrollY = 0; const config = { backgroundOptions: { '默认白': '#ffffff', '豆沙绿': '#c7edcc', '护眼绿': '#e3f2e1', '护眼黑': '#1a1a1a' }, fontSizeOptions: { '小': '16px', '中': '18px', '大': '20px', '特大': '22px' } }; // ==================== 辅助函数 ==================== function isValidHref(h) { if (!h || h.trim() === '' || h === '#') return false; try { const url = new URL(h, window.location.href); return url.protocol === 'http:' || url.protocol === 'https:'; } catch (e) { return false; } } function removeSimulatedButtons(c) { c.querySelectorAll('[role="button"]:not(a)').forEach(e => e.remove()); c.querySelectorAll('[rl-type="stop"]:not(a)').forEach(e => e.remove()); const kw = ['btn','button','copy','share','feedback','thumb','like','dislike','close','popup','modal','toolbar','interact','action']; const s = kw.map(k => `[class*="${k}"]:not(a)`).join(','); c.querySelectorAll(s).forEach(e => e.remove()); } function cleanContent(c) { for (const s of EXTRA_REMOVE_SELECTORS) { c.querySelectorAll(s).forEach(e => e.remove()); } c.querySelectorAll('video, style, link[rel="stylesheet"], script, button').forEach(e => e.remove()); c.querySelectorAll('input[type="button"], input[type="submit"], input[type="reset"]').forEach(e => e.remove()); removeSimulatedButtons(c); c.querySelectorAll('*').forEach(e => { e.removeAttribute('style'); e.removeAttribute('class'); }); ['mark','span','ml-search'].forEach(t => { c.querySelectorAll(t).forEach(e => { const p = e.parentNode; if (p) { while (e.firstChild) { p.insertBefore(e.firstChild, e); } p.removeChild(e); } }); }); c.querySelectorAll('a').forEach(a => { const h = a.getAttribute('href'); if (!h || !isValidHref(h)) { const p = a.parentNode; if (p) { while (a.firstChild) { p.insertBefore(a.firstChild, a); } p.removeChild(a); } return; } const attrs = a.attributes; for (let i = attrs.length - 1; i >= 0; i--) { const attr = attrs[i]; if (attr.name !== 'href') a.removeAttribute(attr.name); } }); c.querySelectorAll('img').forEach(img => { const src = img.getAttribute('src'); const alt = img.getAttribute('alt') || ''; const attrs = img.attributes; for (let i = attrs.length - 1; i >= 0; i--) { const attr = attrs[i]; if (attr.name !== 'src' && attr.name !== 'alt') img.removeAttribute(attr.name); } if (!src) img.remove(); }); } function detectIndent(el) { const ps = el.querySelectorAll('p'); if (ps.length === 0) return false; let cnt = 0; const n = Math.min(ps.length, 10); for (let i = 0; i < n; i++) { const p = ps[i]; const st = window.getComputedStyle(p); const ti = parseFloat(st.textIndent) || 0; if (ti > 1) cnt++; } return cnt > n / 2; } function findLinkByText(selectors) { for (const s of selectors) { if (s.includes(':contains(')) { const m = s.match(/:contains\((.+?)\)/); if (m) { const text = m[1]; const base = s.replace(/:contains\(.+?\)/, ''); const els = base ? document.querySelectorAll(base) : document.querySelectorAll('a'); for (const el of els) { if (el.tagName === 'A' && el.href && el.textContent.includes(text) && isValidHref(el.href)) { return el; } } } } else { const el = document.querySelector(s); if (el && el.tagName === 'A' && el.href && isValidHref(el.href)) { return el; } } } return null; } function findNavLinksByContext() { const res = { prev: null, next: null, index: null }; const xpaths = { prev: '//*[contains(text(),"上一章") or contains(text(),"上一页") or contains(text(),"上一篇")]/following-sibling::a[1]', next: '//*[contains(text(),"下一章") or contains(text(),"下一页") or contains(text(),"下一篇")]/following-sibling::a[1]', index: '//*[contains(text(),"目录") or contains(text(),"章节目录") or contains(text(),"返回目录") or contains(text(),"书籍页")]/following-sibling::a[1]' }; for (let key in xpaths) { const node = document.evaluate(xpaths[key], document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (node && node.tagName === 'A' && node.href && isValidHref(node.href)) { res[key] = node; } } const containers = [ { key: 'prev', texts: ['上一章','上一页','上一篇'] }, { key: 'next', texts: ['下一章','下一页','下一篇'] }, { key: 'index', texts: ['目录','章节目录','返回目录','书籍页'] } ]; for (const item of containers) { if (!res[item.key]) { for (const txt of item.texts) { const container = document.evaluate(`//*[contains(text(),"${txt}")]`, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; if (container) { const link = container.querySelector('a[href]'); if (link && isValidHref(link.href)) { res[item.key] = link; break; } } } } } return res; } function findNavLinks() { let links = { prev: findLinkByText(NAV_SELECTORS.prev), next: findLinkByText(NAV_SELECTORS.next), index: findLinkByText(NAV_SELECTORS.index) }; const cl = findNavLinksByContext(); if (!links.prev && cl.prev) links.prev = cl.prev; if (!links.next && cl.next) links.next = cl.next; if (!links.index && cl.index) links.index = cl.index; return links; } function extractContent() { let contentElement = null; for (const s of CONTENT_SELECTORS) { const els = document.querySelectorAll(s); if (els.length) { let max = els[0]; let maxLen = els[0].innerHTML.length; for (let i = 1; i < els.length; i++) { const len = els[i].innerHTML.length; if (len > maxLen) { maxLen = len; max = els[i]; } } contentElement = max; // console.log(`阅读模式: 使用选择器 "${s}"`); break; } } if (!contentElement) { return null; } originalContentElement = contentElement; const hasIndent = detectIndent(contentElement); const clone = contentElement.cloneNode(true); cleanContent(clone); const html = clone.innerHTML.trim(); if (!html || !clone.textContent.trim()) { return null; } let title = ''; for (const s of TITLE_SELECTORS) { const el = document.querySelector(s); if (el && el.textContent.trim()) { title = el.textContent.trim(); // console.log(`阅读模式: 使用标题选择器 "${s}" 匹配到标题: "${title}"`); break; } } if (!title) { title = document.title || ''; // console.log(`阅读模式: 使用网页标题: "${title}"`); } const navLinks = findNavLinks(); return { title, content: html, nav: navLinks, hasIndent }; } function addReaderModeHash(url) { try { const u = new URL(url, window.location.origin); u.hash = 'readermode'; u.searchParams.set('reader_trigger', 'menu'); return u.href; } catch(e) { let result = url; if (!result.includes('#')) result += '#readermode'; if (result.includes('?')) { result += '&reader_trigger=menu'; } else { result += '?reader_trigger=menu'; } return result; } } function calculateColors(bg) { const dark = bg === '#1a1a1a'; return { textColor: dark ? '#e0e0e0' : '#000', borderColor: dark ? '#444' : '#eee', controlBgColor: dark ? '#333' : 'white', controlBorderColor: dark ? '#555' : '#ddd', subtleTextColor: dark ? '#aaa' : '#666', exitBtnColor: dark ? '#999' : '#666', navBtnColor: dark ? '#4a9eff' : '#007bff' }; } function createNavBar(nav, colors, addHash = false) { const hasAny = nav.prev || nav.next || nav.index; if (!hasAny) return null; const bar = document.createElement('div'); bar.className = 'reader-nav-bar'; bar.style.cssText = 'display:flex;justify-content:center;gap:20px;margin:20px 0;font-size:15px;'; const createBtn = (text, link) => { const a = document.createElement('a'); a.textContent = text; let finalHref = link ? link.href : '#'; if (link && addHash) { if (link.href.startsWith('javascript:')) { finalHref = link.href; } else { finalHref = addReaderModeHash(link.href); } } a.href = finalHref; a.style.cssText = `color:${link ? colors.navBtnColor : '#999'};text-decoration:none;cursor:${link ? 'pointer' : 'default'};opacity:${link ? 1 : 0.5};background:none;font-weight:normal;`; if (!link) a.addEventListener('click', e => e.preventDefault()); return a; }; bar.appendChild(createBtn('上一页', nav.prev)); bar.appendChild(createBtn('目录', nav.index)); bar.appendChild(createBtn('下一页', nav.next)); return bar; } function createControlBar(bg, fontSize, onBgChange, onFontSizeChange, onExit, origUrl) { const colors = calculateColors(bg); const bar = document.createElement('div'); bar.id = 'reader-control-bar'; bar.style.cssText = `margin-bottom:20px;padding-bottom:15px;border-bottom:1px solid ${colors.borderColor};`; const row = document.createElement('div'); row.style.cssText = `display:flex;justify-content:space-between;align-items:center;font-size:14px;color:${colors.subtleTextColor};`; const leftGroup = document.createElement('div'); const newTabBtn = document.createElement('a'); newTabBtn.textContent = '新标签页'; newTabBtn.href = addReaderModeHash(origUrl || window.location.href); newTabBtn.target = '_blank'; newTabBtn.style.cssText = `color:${colors.navBtnColor};text-decoration:none;padding:4px 12px;border:1px solid ${colors.borderColor};border-radius:16px;font-size:13px;transition:background 0.2s;`; newTabBtn.addEventListener('mouseenter', () => newTabBtn.style.background = colors.controlBgColor); newTabBtn.addEventListener('mouseleave', () => newTabBtn.style.background = 'transparent'); leftGroup.appendChild(newTabBtn); const settingsGroup = document.createElement('div'); settingsGroup.style.cssText = 'display:flex;gap:15px;'; const bgSelector = document.createElement('select'); bgSelector.style.cssText = `padding:4px 8px;border:1px solid ${colors.controlBorderColor};border-radius:3px;background:${colors.controlBgColor};color:${colors.textColor};cursor:pointer;font-size:13px;`; Object.keys(config.backgroundOptions).forEach(name => { const opt = document.createElement('option'); opt.value = config.backgroundOptions[name]; opt.textContent = name; if (config.backgroundOptions[name] === bg) opt.selected = true; bgSelector.appendChild(opt); }); const fontSizeSelector = document.createElement('select'); fontSizeSelector.style.cssText = bgSelector.style.cssText; Object.keys(config.fontSizeOptions).forEach(name => { const opt = document.createElement('option'); opt.value = config.fontSizeOptions[name]; opt.textContent = name; if (config.fontSizeOptions[name] === fontSize) opt.selected = true; fontSizeSelector.appendChild(opt); }); settingsGroup.appendChild(bgSelector); settingsGroup.appendChild(fontSizeSelector); const exitBtn = document.createElement('span'); exitBtn.textContent = '退出'; exitBtn.style.cssText = `color:${colors.exitBtnColor};cursor:pointer;text-decoration:underline;`; exitBtn.addEventListener('click', onExit); row.appendChild(leftGroup); row.appendChild(settingsGroup); row.appendChild(exitBtn); bar.appendChild(row); bgSelector.addEventListener('change', e => onBgChange(e.target.value)); fontSizeSelector.addEventListener('change', e => onFontSizeChange(e.target.value)); return bar; } function updateReaderContent(overlay, article) { if (!overlay || !article) return; const titleEl = overlay.querySelector('#reader-article-title'); const contentSec = overlay.querySelector('#reader-content-section'); const topNav = overlay.querySelector('#reader-top-nav'); const bottomNav = overlay.querySelector('#reader-bottom-nav'); if (titleEl) titleEl.textContent = article.title || ''; if (contentSec) { contentSec.innerHTML = article.content; const bg = overlay.style.backgroundColor || '#ffffff'; const colors = calculateColors(bg); contentSec.style.color = colors.textColor; if (article.hasIndent !== undefined) contentSec.dataset.hasIndent = article.hasIndent ? 'true' : 'false'; } const colors = calculateColors(overlay.style.backgroundColor || '#ffffff'); const newTop = createNavBar(article.nav, colors, true); const newBottom = createNavBar(article.nav, colors, true); if (topNav && newTop) topNav.replaceWith(newTop); else if (topNav && !newTop) topNav.remove(); if (bottomNav && newBottom) bottomNav.replaceWith(newBottom); else if (bottomNav && !newBottom) bottomNav.remove(); } // ==================== 构建阅读界面 ==================== function buildOverlayReaderUI(article, initialBgColor = '#ffffff', initialFontSize = '18px') { const overlay = document.createElement('div'); overlay.id = 'reader-overlay'; overlay.style.cssText = `position:fixed;top:0;left:0;width:100%;height:100%;background:${initialBgColor};z-index:2147483647;isolation:isolate;transform:translateZ(0);will-change:transform;overflow-y:auto;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;`; const mainContainer = document.createElement('div'); mainContainer.id = 'reader-container'; mainContainer.style.cssText = `width:92%;max-width:800px;margin:0 auto;padding:20px 10px;color:${calculateColors(initialBgColor).textColor};font-size:${initialFontSize};line-height:1.8;user-select:text;overflow-wrap:break-word;word-wrap:break-word;text-align:left;`; const contentArea = document.createElement('div'); contentArea.id = 'reader-content-area'; contentArea.style.cssText = 'text-align:left;'; const controlBar = createControlBar( initialBgColor, initialFontSize, (newColor) => { overlay.style.background = newColor; const colors = calculateColors(newColor); mainContainer.style.color = colors.textColor; if (contentSection) contentSection.style.color = colors.textColor; if (articleTitle) articleTitle.style.color = colors.textColor; }, (newSize) => { mainContainer.style.fontSize = newSize; if (contentSection) contentSection.style.fontSize = newSize; }, exitLegacyReaderMode, originalUrl ); let articleTitle = null; if (article.title && article.title.trim()) { articleTitle = document.createElement('h1'); articleTitle.id = 'reader-article-title'; articleTitle.textContent = article.title; articleTitle.style.cssText = `margin:0 0 30px 0;font-size:1.4em;font-weight:700;line-height:1.4;color:${calculateColors(initialBgColor).textColor};text-align:left;user-select:text;overflow-wrap:break-word;word-wrap:break-word;`; } const contentSection = document.createElement('div'); contentSection.id = 'reader-content-section'; contentSection.innerHTML = article.content; contentSection.style.cssText = `font-size:${initialFontSize};line-height:1.8;color:${calculateColors(initialBgColor).textColor};user-select:text;overflow-wrap:break-word;word-wrap:break-word;max-width:100%;text-align:left;`; if (article.hasIndent !== undefined) contentSection.dataset.hasIndent = article.hasIndent ? 'true' : 'false'; const style = document.createElement('style'); const indentRule = article.hasIndent ? '#reader-content-section p{text-indent:2em;}' : '#reader-content-section p{text-indent:0;}'; style.textContent = ` #reader-content-section p, #reader-content-section div, #reader-content-section h1, h2, h3, h4, h5, h6, #reader-content-section li, #reader-content-section blockquote, #reader-content-section pre, #reader-content-section table, #reader-content-section td, #reader-content-section th { max-width: 100%; overflow-wrap: break-word; word-wrap: break-word; box-sizing: border-box; } #reader-content-section p { text-align: justify; margin: 0 0 1.8em 0; } ${indentRule} #reader-content-section img { max-width: 100%; height: auto; } #reader-content-section blockquote { margin-left: 0; padding-left: 1.2em; border-left: 3px solid #ddd; } #reader-content-section a { color: ${calculateColors(initialBgColor).navBtnColor}; text-decoration: none; cursor: pointer; } #reader-content-section a:hover { text-decoration: none; } #reader-content-section strong, #reader-content-section b { font-weight: bold; } #reader-content-section em, #reader-content-section i { font-style: italic; } #reader-content-section h1, #reader-content-section h2, #reader-content-section h3, #reader-content-section h4, #reader-content-section h5, #reader-content-section h6 { font-weight: bold; margin-top: 1.2em; margin-bottom: 0.6em; } #reader-content-section h1 { font-size: 1.8em; } #reader-content-section h2 { font-size: 1.5em; } #reader-content-section h3 { font-size: 1.3em; } #reader-content-section ul, #reader-content-section ol { padding-left: 1.5em; margin: 0.5em 0; } #reader-content-section li { margin: 0.2em 0; } `; contentSection.appendChild(style); const topNavBar = createNavBar(article.nav, calculateColors(initialBgColor), false); const bottomNavBar = createNavBar(article.nav, calculateColors(initialBgColor), false); contentArea.appendChild(controlBar); if (articleTitle) contentArea.appendChild(articleTitle); if (topNavBar) contentArea.appendChild(topNavBar); contentArea.appendChild(contentSection); if (bottomNavBar) contentArea.appendChild(bottomNavBar); mainContainer.appendChild(contentArea); overlay.appendChild(mainContainer); document.documentElement.appendChild(overlay); overlay.focus(); setTimeout(() => { const ctrlBar = overlay.querySelector('#reader-control-bar'); if (ctrlBar) { const rect = ctrlBar.getBoundingClientRect(); overlay.scrollTo(0, rect.top + overlay.scrollTop + rect.height); } }, 0); return overlay; } function buildNewTabReaderUI(article, initialBgColor = '#ffffff', initialFontSize = '18px') { const hiddenContainer = document.createElement('div'); hiddenContainer.id = 'original-page-hidden'; hiddenContainer.style.cssText = 'display:none;'; while (document.body.firstChild) { hiddenContainer.appendChild(document.body.firstChild); } document.body.appendChild(hiddenContainer); const overlay = document.createElement('div'); overlay.id = 'reader-newtab-overlay'; overlay.style.cssText = `position:fixed;top:0;left:0;width:100%;height:100%;background:${initialBgColor};z-index:2147483647;isolation:isolate;transform:translateZ(0);will-change:transform;overflow-y:auto;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;`; const mainContainer = document.createElement('div'); mainContainer.id = 'reader-container'; mainContainer.style.cssText = `width:92%;max-width:800px;margin:0 auto;padding:20px 10px;color:${calculateColors(initialBgColor).textColor};font-size:${initialFontSize};line-height:1.8;user-select:text;overflow-wrap:break-word;word-wrap:break-word;text-align:left;`; const contentArea = document.createElement('div'); contentArea.id = 'reader-content-area'; contentArea.style.cssText = 'text-align:left;'; const controlBar = createControlBar( initialBgColor, initialFontSize, (newColor) => { overlay.style.background = newColor; const colors = calculateColors(newColor); mainContainer.style.color = colors.textColor; if (contentSection) contentSection.style.color = colors.textColor; if (articleTitle) articleTitle.style.color = colors.textColor; }, (newSize) => { mainContainer.style.fontSize = newSize; if (contentSection) contentSection.style.fontSize = newSize; }, () => window.close(), originalUrl ); let articleTitle = null; if (article.title && article.title.trim()) { articleTitle = document.createElement('h1'); articleTitle.id = 'reader-article-title'; articleTitle.textContent = article.title; articleTitle.style.cssText = `margin:0 0 30px 0;font-size:1.4em;font-weight:700;line-height:1.4;color:${calculateColors(initialBgColor).textColor};text-align:left;user-select:text;overflow-wrap:break-word;word-wrap:break-word;`; } const contentSection = document.createElement('div'); contentSection.id = 'reader-content-section'; contentSection.innerHTML = article.content; contentSection.style.cssText = `font-size:${initialFontSize};line-height:1.8;color:${calculateColors(initialBgColor).textColor};user-select:text;overflow-wrap:break-word;word-wrap:break-word;max-width:100%;text-align:left;`; if (article.hasIndent !== undefined) contentSection.dataset.hasIndent = article.hasIndent ? 'true' : 'false'; const style = document.createElement('style'); const indentRule = article.hasIndent ? '#reader-content-section p{text-indent:2em;}' : '#reader-content-section p{text-indent:0;}'; style.textContent = ` #reader-content-section p, #reader-content-section div, #reader-content-section h1, h2, h3, h4, h5, h6, #reader-content-section li, #reader-content-section blockquote, #reader-content-section pre, #reader-content-section table, #reader-content-section td, #reader-content-section th { max-width: 100%; overflow-wrap: break-word; word-wrap: break-word; box-sizing: border-box; } #reader-content-section p { text-align: justify; margin: 0 0 1.8em 0; } ${indentRule} #reader-content-section img { max-width: 100%; height: auto; } #reader-content-section blockquote { margin-left: 0; padding-left: 1.2em; border-left: 3px solid #ddd; } #reader-content-section a { color: ${calculateColors(initialBgColor).navBtnColor}; text-decoration: none; cursor: pointer; } #reader-content-section a:hover { text-decoration: none; } #reader-content-section strong, #reader-content-section b { font-weight: bold; } #reader-content-section em, #reader-content-section i { font-style: italic; } #reader-content-section h1, #reader-content-section h2, #reader-content-section h3, #reader-content-section h4, #reader-content-section h5, #reader-content-section h6 { font-weight: bold; margin-top: 1.2em; margin-bottom: 0.6em; } #reader-content-section h1 { font-size: 1.8em; } #reader-content-section h2 { font-size: 1.5em; } #reader-content-section h3 { font-size: 1.3em; } #reader-content-section ul, #reader-content-section ol { padding-left: 1.5em; margin: 0.5em 0; } #reader-content-section li { margin: 0.2em 0; } `; contentSection.appendChild(style); const topNavBar = createNavBar(article.nav, calculateColors(initialBgColor), true); const bottomNavBar = createNavBar(article.nav, calculateColors(initialBgColor), true); contentArea.appendChild(controlBar); if (articleTitle) contentArea.appendChild(articleTitle); if (topNavBar) contentArea.appendChild(topNavBar); contentArea.appendChild(contentSection); if (bottomNavBar) contentArea.appendChild(bottomNavBar); mainContainer.appendChild(contentArea); overlay.appendChild(mainContainer); document.documentElement.appendChild(overlay); overlay.focus(); if (originalContentElement) { contentObserver = new MutationObserver(() => { const newArticle = extractContent(); if (newArticle) updateReaderContent(overlay, newArticle); }); contentObserver.observe(originalContentElement, { childList: true, subtree: true, characterData: true }); } setTimeout(() => { const ctrlBar = overlay.querySelector('#reader-control-bar'); if (ctrlBar) { const rect = ctrlBar.getBoundingClientRect(); overlay.scrollTo(0, rect.top + overlay.scrollTop + rect.height); } }, 0); newTabOverlay = overlay; return overlay; } // ==================== 浮动按钮 ==================== function clearButtonTimer() { if (buttonHideTimeout) { clearTimeout(buttonHideTimeout); buttonHideTimeout = null; } } function createMainButtonOnce() { if (mainButton) return; const btn = document.createElement('button'); btn.id = 'reader-main-btn'; btn.textContent = '阅读模式'; btn.style.cssText = 'position:fixed;bottom:60px;right:20px;z-index:10000;padding:8px 16px;background:#007bff;color:white;border:none;border-radius:4px;cursor:pointer;font-size:14px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;opacity:0.9;box-shadow:0 2px 8px rgba(0,0,0,0.2);transition:opacity 0.3s;user-select:none;'; btn.addEventListener('mouseenter', () => { if (isReaderMode) return; btn.style.opacity = '1'; clearButtonTimer(); }); btn.addEventListener('mouseleave', () => { if (isReaderMode) return; btn.style.opacity = '0.9'; clearButtonTimer(); buttonHideTimeout = setTimeout(() => hideButton(btn), 3000); }); btn.addEventListener('click', enterLegacyReaderMode); document.body.appendChild(btn); mainButton = btn; buttonHideTimeout = setTimeout(() => hideButton(btn), 3000); } function hideButton(btn) { if (!isReaderMode && btn && btn.parentNode) { btn.style.opacity = '0'; setTimeout(() => { if (btn.parentNode && !isReaderMode) btn.style.display = 'none'; }, 300); } } function hideMainButtonPermanently() { if (mainButton) { clearButtonTimer(); mainButton.style.display = 'none'; } } // ==================== 模式切换核心逻辑 ==================== function enterLegacyReaderMode() { if (isReaderMode) return; hideMainButtonPermanently(); setTimeout(() => { const article = extractContent(); if (!article) { alert('未找到可阅读的正文内容'); return; } originalUrl = window.location.href.replace(/#.*$/, ''); scrollY = window.scrollY; const body = document.body; originalBodyOverflow = body.style.overflow; originalBodyPosition = body.style.position; originalBodyTop = body.style.top; originalBodyWidth = body.style.width; body.style.overflow = 'hidden'; body.style.position = 'fixed'; body.style.top = `-${scrollY}px`; body.style.width = '100%'; readerOverlay = buildOverlayReaderUI(article); isReaderMode = true; updateMenuCommands(); }, 50); } function exitLegacyReaderMode() { if (!isReaderMode || !readerOverlay) return; readerOverlay.remove(); readerOverlay = null; const body = document.body; body.style.overflow = originalBodyOverflow; body.style.position = originalBodyPosition; body.style.top = originalBodyTop; body.style.width = originalBodyWidth; window.scrollTo(0, scrollY); isReaderMode = false; updateMenuCommands(); } function activateNewTabReaderMode() { // 内部判断是否由菜单触发(存在 reader_trigger=menu 参数) const isMenuTriggered = new URLSearchParams(window.location.search).get('reader_trigger') === 'menu'; let attempts = 0; const maxAttempts = 5; const tryExtract = () => { const article = extractContent(); if (article) { originalUrl = window.location.href.replace(/#.*$/, ''); buildNewTabReaderUI(article); updateMenuCommands(); } else { attempts++; if (attempts < maxAttempts) { setTimeout(tryExtract, 300); } else { // 超时未找到 if (isMenuTriggered) { alert('未找到可阅读的正文内容,可能加载超时'); window.close(); } // 否则静默失败(域名有 #readermode 但不带参数) } } }; tryExtract(); } function openReaderInNewTab() { const url = addReaderModeHash(window.location.href); window.open(url, '_blank'); } // ==================== 油猴菜单管理 ==================== function updateMenuCommands() { if (menuCommandId !== null) { GM_unregisterMenuCommand(menuCommandId); menuCommandId = null; } if (SHOULD_ACTIVATE_NEW_TAB) { menuCommandId = GM_registerMenuCommand('❌ 退出阅读模式', () => window.close()); } else if (isReaderMode) { menuCommandId = GM_registerMenuCommand('❌ 退出阅读模式', exitLegacyReaderMode); } else { menuCommandId = GM_registerMenuCommand('打开阅读模式', enterLegacyReaderMode); GM_registerMenuCommand('新标签页打开阅读模式', openReaderInNewTab); } } // ==================== 初始化 ==================== function init() { if (SHOULD_ACTIVATE_NEW_TAB) { activateNewTabReaderMode(); } else { let retryCount = 0; const maxRetries = 5; const retryInterval = 300; const intervalId = setInterval(() => { const testArticle = extractContent(); retryCount++; if (testArticle || retryCount >= maxRetries) { clearInterval(intervalId); if (testArticle) createMainButtonOnce(); updateMenuCommands(); } }, retryInterval); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();