// ==UserScript== // @name 专注阅读 - 移动端沉浸式阅读 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 消除页面噪音,提供沉浸式阅读体验 // @author FocusReader // @match http://*/* // @match https://*/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @downloadURL https://update.greasyfork.icu/scripts/538369/%E4%B8%93%E6%B3%A8%E9%98%85%E8%AF%BB%20-%20%E7%A7%BB%E5%8A%A8%E7%AB%AF%E6%B2%89%E6%B5%B8%E5%BC%8F%E9%98%85%E8%AF%BB.user.js // @updateURL https://update.greasyfork.icu/scripts/538369/%E4%B8%93%E6%B3%A8%E9%98%85%E8%AF%BB%20-%20%E7%A7%BB%E5%8A%A8%E7%AB%AF%E6%B2%89%E6%B5%B8%E5%BC%8F%E9%98%85%E8%AF%BB.meta.js // ==/UserScript== (function() { 'use strict'; let isReaderMode = false; let originalContent = null; let readerContainer = null; // 护眼配色方案 const colorSchemes = { default: { name: '默认白', bg: '#ffffff', text: '#333333', border: '#e0e0e0' }, warm: { name: '暖白护眼', bg: '#faf8f3', text: '#4a4a4a', border: '#e8e6e1' }, sepia: { name: '复古褐', bg: '#f4f0e6', text: '#5c4b37', border: '#d4c8b8' }, green: { name: '护眼绿', bg: '#f0f8f0', text: '#2d4a2d', border: '#c8e6c8' }, dark: { name: '暗夜模式', bg: '#1a1a1a', text: '#e0e0e0', border: '#404040' }, blue: { name: '海洋蓝', bg: '#f0f8ff', text: '#1e3a5f', border: '#c8d8e8' } }; // 字体选择 const fontFamilies = { system: '系统默认', songti: '"Songti SC", "SimSun", serif', heiti: '"Heiti SC", "SimHei", sans-serif', kaiti: '"Kaiti SC", "KaiTi", cursive', fangsong: '"FangSong SC", "FangSong", serif', pingfang: '"PingFang SC", "Hiragino Sans GB", sans-serif' }; // 获取当前设置 function getCurrentSettings() { return { colorScheme: GM_getValue('colorScheme', 'warm'), fontSize: GM_getValue('fontSize', '18'), fontFamily: GM_getValue('fontFamily', 'system'), lineHeight: GM_getValue('lineHeight', '1.8'), maxWidth: GM_getValue('maxWidth', '90%') }; } // 保存设置 function saveSettings(settings) { GM_setValue('colorScheme', settings.colorScheme); GM_setValue('fontSize', settings.fontSize); GM_setValue('fontFamily', settings.fontFamily); GM_setValue('lineHeight', settings.lineHeight); GM_setValue('maxWidth', settings.maxWidth); } // 智能提取文章内容 function extractContent() { // 常见的文章容器选择器 const contentSelectors = [ 'article', '[role="main"]', '.content', '.article-content', '.post-content', '.entry-content', '.main-content', '#content', '.article-body', '.post-body' ]; let content = null; // 首先尝试通过选择器查找 for (let selector of contentSelectors) { content = document.querySelector(selector); if (content && content.innerText.length > 200) { break; } } // 如果没找到合适的内容,使用启发式方法 if (!content || content.innerText.length < 200) { const allElements = document.querySelectorAll('div, section, article, main'); let maxScore = 0; for (let element of allElements) { let score = 0; const text = element.innerText || ''; const textLength = text.length; if (textLength < 100) continue; // 文本长度得分 score += Math.min(textLength / 100, 50); // 段落数量得分 const paragraphs = element.querySelectorAll('p').length; score += paragraphs * 3; // 链接密度惩罚 const links = element.querySelectorAll('a').length; const linkDensity = links / Math.max(textLength / 100, 1); score -= linkDensity * 10; // 类名和ID得分 const className = element.className.toLowerCase(); const id = element.id.toLowerCase(); if (className.includes('content') || className.includes('article') || className.includes('post') || id.includes('content') || id.includes('article') || id.includes('post')) { score += 15; } if (score > maxScore) { maxScore = score; content = element; } } } return content; } // 清理内容 function cleanContent(element) { if (!element) return null; const clonedElement = element.cloneNode(true); // 移除不需要的元素 - 加强版噪音过滤 const removeSelectors = [ // 脚本和样式 'script', 'style', 'noscript', 'link[rel="stylesheet"]', // 媒体元素(移除所有图片和视频) 'img', 'picture', 'figure', 'video', 'audio', 'canvas', 'svg', 'iframe', 'embed', 'object', // 广告和营销 '.advertisement', '.ads', '.ad', '[class*="ad-"]', '[id*="ad-"]', '[class*="ads-"]', '[id*="ads-"]', '.promo', '.banner', '.sponsor', '.promotion', '[class*="affiliate"]', // 社交和分享 '.social', '.share', '.social-share', '.social-links', '.share-buttons', '.social-media', '[class*="social"]', '[class*="share"]', '.follow', '.subscribe', // 导航和菜单 'nav', '.navigation', '.nav', '.menu', '.navbar', '.breadcrumb', '.breadcrumbs', '.pagination', '.next-prev', '.prev-next', // 页眉页脚 'header', '.header', 'footer', '.footer', '.site-header', '.site-footer', '.page-header', '.page-footer', // 侧边栏和小工具 'aside', '.sidebar', '.side-bar', '.widget', '.widgets', '.secondary', '.complementary', // 评论系统 '.comments', '.comment', '.comment-section', '.comment-form', '.comment-list', '.discussion', '.disqus', '.facebook-comments', // 相关内容和推荐 '.related', '.related-posts', '.related-articles', '.recommended', '.suggestions', '.more-posts', '.similar', '.also-read', '.you-may-like', // 表单和输入 'form', 'input', 'textarea', 'select', 'button[type="submit"]', '.form', '.search-form', '.contact-form', '.newsletter', '.signup', '.login', // 弹窗和模态框 '.popup', '.modal', '.overlay', '.lightbox', '.dialog', '.tooltip', '.dropdown', // 标签和分类 '.tags', '.tag', '.categories', '.category', '.meta', '.post-meta', '.entry-meta', '.author', '.author-bio', '.author-info', '.date', '.timestamp', '.published', // 通用垃圾内容 '.noise', '.clutter', '.extra', '.misc', '[role="complementary"]', '[role="banner"]', '[role="navigation"]', '[role="contentinfo"]', '.skip-link', '.screen-reader-text', // 特定网站常见噪音 '.wp-caption', '.wp-caption-text', '.caption', '.gallery', '.slideshow', '.slider', '.code-toolbar', '.toolbar', '.copy-code', '.highlight-toolbar' ]; // 先移除明确的噪音元素 removeSelectors.forEach(selector => { const elements = clonedElement.querySelectorAll(selector); elements.forEach(el => el.remove()); }); // 移除所有包含特定关键词的元素 const noiseKeywords = [ 'advertisement', 'ads', 'promo', 'banner', 'sponsor', 'social', 'share', 'follow', 'subscribe', 'newsletter', 'comment', 'related', 'recommended', 'similar', 'tags', 'category', 'author', 'meta', 'breadcrumb', 'navigation' ]; const allElements = Array.from(clonedElement.querySelectorAll('*')); allElements.forEach(el => { const className = (el.className || '').toLowerCase(); const id = (el.id || '').toLowerCase(); const tagName = el.tagName.toLowerCase(); // 检查类名和ID是否包含噪音关键词 const hasNoiseKeyword = noiseKeywords.some(keyword => className.includes(keyword) || id.includes(keyword) ); if (hasNoiseKeyword) { el.remove(); return; } // 移除空元素或只包含空白的元素 const text = el.textContent?.trim() || ''; if (text.length === 0 && !['p', 'div', 'section', 'article', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) { el.remove(); return; } // 移除链接密度过高的元素(可能是导航或广告) if (tagName === 'div' || tagName === 'section') { const links = el.querySelectorAll('a').length; const textLength = text.length; const linkDensity = textLength > 0 ? links / (textLength / 100) : 0; if (linkDensity > 0.5 && links > 3) { el.remove(); return; } } // 清理所有属性,只保留基本结构 Array.from(el.attributes).forEach(attr => { el.removeAttribute(attr.name); }); }); // 最后清理:只保留有意义的文本内容 const finalElements = Array.from(clonedElement.querySelectorAll('*')); finalElements.forEach(el => { const text = el.textContent?.trim() || ''; const tagName = el.tagName.toLowerCase(); // 保留标题和段落,但要求有足够的文本内容 if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName)) { if (text.length === 0 || text.length > 200) { el.remove(); } } else if (tagName === 'p') { if (text.length < 10) { el.remove(); } } else if (tagName === 'div' || tagName === 'section') { // 如果div或section没有有效的子元素,移除它 const hasValidChildren = el.querySelector('p, h1, h2, h3, h4, h5, h6'); if (!hasValidChildren && text.length < 50) { el.remove(); } } }); return clonedElement; } // 创建设置面板 function createSettingsPanel() { const settings = getCurrentSettings(); const currentScheme = colorSchemes[settings.colorScheme]; const panel = document.createElement('div'); panel.innerHTML = `
阅读设置
${Object.entries(colorSchemes).map(([key, scheme]) => `
${scheme.name}
`).join('')}
${settings.fontSize}px
${settings.lineHeight}
`; document.body.appendChild(panel); // 事件监听 panel.querySelector('#closeSettings').onclick = () => panel.remove(); // 配色方案选择 panel.querySelectorAll('[data-scheme]').forEach(item => { item.onclick = () => { panel.querySelectorAll('[data-scheme]').forEach(el => el.style.border = '2px solid #ddd' ); item.style.border = '2px solid #007AFF'; }; }); // 字体大小实时预览 const fontSizeSlider = panel.querySelector('#fontSize'); const fontSizeDisplay = fontSizeSlider.nextElementSibling; fontSizeSlider.oninput = () => { fontSizeDisplay.textContent = fontSizeSlider.value + 'px'; }; // 行间距实时预览 const lineHeightSlider = panel.querySelector('#lineHeight'); const lineHeightDisplay = lineHeightSlider.nextElementSibling; lineHeightSlider.oninput = () => { lineHeightDisplay.textContent = lineHeightSlider.value; }; // 应用设置 panel.querySelector('#applySettings').onclick = () => { const newSettings = { colorScheme: panel.querySelector('[data-scheme][style*="rgb(0, 122, 255)"]')?.dataset.scheme || settings.colorScheme, fontSize: fontSizeSlider.value, fontFamily: panel.querySelector('#fontFamily').value, lineHeight: lineHeightSlider.value, maxWidth: settings.maxWidth }; saveSettings(newSettings); applyReaderStyles(); panel.remove(); }; // 重置设置 panel.querySelector('#resetSettings').onclick = () => { const defaultSettings = { colorScheme: 'warm', fontSize: '18', fontFamily: 'system', lineHeight: '1.8', maxWidth: '90%' }; saveSettings(defaultSettings); applyReaderStyles(); panel.remove(); }; } // 应用阅读器样式 function applyReaderStyles() { if (!readerContainer) return; const settings = getCurrentSettings(); const scheme = colorSchemes[settings.colorScheme]; const fontFamily = settings.fontFamily === 'system' ? '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif' : fontFamilies[settings.fontFamily]; readerContainer.style.cssText = ` position: fixed !important; top: 0 !important; left: 0 !important; width: 100vw !important; height: 100vh !important; background: ${scheme.bg} !important; color: ${scheme.text} !important; z-index: 10000 !important; overflow-x: hidden !important; overflow-y: auto !important; padding: 0 !important; margin: 0 !important; font-family: ${fontFamily} !important; font-size: ${settings.fontSize}px !important; line-height: ${settings.lineHeight} !important; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; `; const contentArea = readerContainer.querySelector('.reader-content'); if (contentArea) { contentArea.style.cssText = ` max-width: ${settings.maxWidth} !important; margin: 0 auto !important; padding: 40px 20px 60px 20px !important; word-wrap: break-word !important; hyphens: auto !important; `; } // 应用内容样式 const allElements = readerContainer.querySelectorAll('*'); allElements.forEach(el => { if (el.tagName === 'H1' || el.tagName === 'H2' || el.tagName === 'H3' || el.tagName === 'H4' || el.tagName === 'H5' || el.tagName === 'H6') { el.style.cssText = ` color: ${scheme.text} !important; margin: 1.5em 0 1em 0 !important; font-weight: bold !important; line-height: 1.4 !important; `; } else if (el.tagName === 'P') { el.style.cssText = ` color: ${scheme.text} !important; margin: 1em 0 !important; text-align: justify !important; text-indent: 2em !important; `; } else if (el.tagName === 'BLOCKQUOTE') { el.style.cssText = ` border-left: 4px solid ${scheme.border} !important; padding-left: 1em !important; margin: 1em 0 !important; font-style: italic !important; color: ${scheme.text} !important; background: transparent !important; `; } // 移除了图片相关的样式设置,因为图片已经被过滤掉了 }); } // 创建工具栏 function createToolbar() { const toolbar = document.createElement('div'); toolbar.className = 'reader-toolbar'; toolbar.innerHTML = `
`; readerContainer.appendChild(toolbar); // 获取工具栏内容区域 const toolbarContent = toolbar.querySelector('.toolbar-content'); // 工具栏事件 toolbar.querySelector('#readerSettings').onclick = createSettingsPanel; toolbar.querySelector('#readerScrollTop').onclick = () => { readerContainer.scrollTo({ top: 0, behavior: 'smooth' }); }; toolbar.querySelector('#exitReader').onclick = exitReaderMode; // --- Corrected Toolbar Scroll Logic --- let scrollTimeout; let lastScrollTop = readerContainer.scrollTop; function showToolbar() { toolbarContent.style.opacity = '1'; toolbarContent.style.transform = 'translateX(-50%) translateY(0)'; } function hideToolbar() { toolbarContent.style.opacity = '0'; toolbarContent.style.transform = 'translateX(-50%) translateY(20px)'; } readerContainer.addEventListener('scroll', () => { const currentScrollTop = readerContainer.scrollTop; // Clear any existing timeout clearTimeout(scrollTimeout); if (currentScrollTop > lastScrollTop) { // Scrolling down // Temporarily hide immediately when scrolling down hideToolbar(); // If scrolling stops while going down, keep it hidden scrollTimeout = setTimeout(() => { // Do nothing, keep hidden if still scrolling down or stopped after scrolling down }, 300); // Small delay to check if still scrolling } else if (currentScrollTop < lastScrollTop) { // Scrolling up // Show immediately when scrolling up showToolbar(); // If scrolling stops after scrolling up, keep it visible scrollTimeout = setTimeout(() => { showToolbar(); }, 300); } else { // Scroll stopped (no change in scrollTop) if (currentScrollTop === 0) { // If at the very top, always show showToolbar(); } else if (toolbarContent.style.opacity === '0') { // If previously hidden (scrolled down), keep hidden when stopped // This is the core fix for "down-scroll-then-stop-and-hide" } else { // If previously visible (scrolled up or at top), keep visible when stopped showToolbar(); } } lastScrollTop = currentScrollTop; }); // Ensure toolbar is visible when entering reader mode initially showToolbar(); } // 进入阅读模式 function enterReaderMode() { if (isReaderMode) return; // 保存原始内容 originalContent = { html: document.documentElement.innerHTML, title: document.title }; // 提取文章内容 const content = extractContent(); if (!content) { alert('未能识别到文章内容,请手动选择文本后重试'); return; } const cleanedContent = cleanContent(content); if (!cleanedContent) { alert('内容处理失败,请重试'); return; } // 创建阅读器容器 readerContainer = document.createElement('div'); readerContainer.className = 'focus-reader-container'; // 获取标题 let title = document.title; const h1 = document.querySelector('h1'); if (h1 && h1.innerText.length < 100) { title = h1.innerText; } // 检查cleanedContent中是否已经有标题 const existingH1 = cleanedContent.querySelector('h1'); let contentHTML = cleanedContent.innerHTML; // 如果内容中已有h1标题,就不额外添加标题 if (existingH1) { readerContainer.innerHTML = `
${contentHTML}
`; } else { // 如果内容中没有h1标题,才添加页面标题 readerContainer.innerHTML = `

${title}

${contentHTML}
`; } document.body.appendChild(readerContainer); // 隐藏原始内容 document.body.style.overflow = 'hidden'; Array.from(document.body.children).forEach(child => { if (child !== readerContainer) { child.style.display = 'none'; } }); // 应用样式和创建工具栏 applyReaderStyles(); createToolbar(); isReaderMode = true; // 阻止页面滚动 document.addEventListener('touchmove', preventDefaultTouch, { passive: false }); } // 阻止默认触摸事件(除了阅读器内部) function preventDefaultTouch(e) { if (!readerContainer.contains(e.target)) { e.preventDefault(); } } // 退出阅读模式 function exitReaderMode() { if (!isReaderMode) return; // 移除阅读器容器 if (readerContainer) { readerContainer.remove(); readerContainer = null; } // 恢复原始内容显示 document.body.style.overflow = ''; Array.from(document.body.children).forEach(child => { child.style.display = ''; }); // 移除事件监听 document.removeEventListener('touchmove', preventDefaultTouch); isReaderMode = false; } // 注册油猴菜单 GM_registerMenuCommand('🔍 进入专注阅读', enterReaderMode); GM_registerMenuCommand('⚙️ 阅读设置', createSettingsPanel); // 快捷键支持(可选) document.addEventListener('keydown', function(e) { // Ctrl/Cmd + Shift + R 进入/退出阅读模式 if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'R') { e.preventDefault(); if (isReaderMode) { exitReaderMode(); } else { enterReaderMode(); } } }); // 移动端优化:添加触摸手势支持 let touchStartY = 0; let touchStartTime = 0; document.addEventListener('touchstart', function(e) { if (e.touches.length === 2) { touchStartY = e.touches[0].clientY + e.touches[1].clientY; touchStartTime = Date.now(); } }); document.addEventListener('touchend', function(e) { if (e.changedTouches.length === 2 && touchStartTime > 0) { const touchEndTime = Date.now(); if (touchEndTime - touchStartTime < 500) { // 500ms内的双指触摸 if (isReaderMode) { exitReaderMode(); } else { enterReaderMode(); } } } touchStartTime = 0; }); console.log('专注阅读脚本已加载 - 使用油猴菜单或双指轻触进入阅读模式'); })();