您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为微信读书打造的全能美化工具:多主题切换、自动滚屏、字体调节、页面优化。提升阅读体验,让每次阅读都是视觉享受。
// ==UserScript== // @name WeReadBetter-享阅(微信阅读美化) // @icon https://weread.qq.com/favicon.ico // @version 20251002 // @description 为微信读书打造的全能美化工具:多主题切换、自动滚屏、字体调节、页面优化。提升阅读体验,让每次阅读都是视觉享受。 // @author StitchHu // @match https://weread.qq.com/* // @license MIT // @grant GM_addStyle // @grant GM_getResourceURL // @grant GM_setValue // @grant GM_getValue // @resource BG01 https://gitee.com/StitchHu/images/raw/master/%E7%BA%B8%E7%BA%B93.jpg // @resource BG02 https://gitee.com/StitchHu/images/raw/master/%E8%83%8C%E6%99%AF-%E7%BA%A2%E8%8A%B1.jpg // @resource BG03 https://gitee.com/StitchHu/images/raw/master/%E8%83%8C%E6%99%AF-%E8%BF%9C%E5%B1%B1.jpg // @namespace https://gf.qytechs.cn/users/1510853 // ==/UserScript== (function () { 'use strict'; // ============================== // 📝 基础功能配置 - 可根据个人喜好调整 // ============================== const CONFIG = { // 🚀 自动滚动配置 SCROLL_SPEED: 1, // 自动滚动速度:数值越大滚动越快,建议范围 1-5 INTERVAL_MS: 35, // 滚动间隔毫秒数:数值越小滚动越流畅,建议范围 20-50 // 📏 阅读栏宽度配置 WIDTH_STEP: 100, // 每次点击加宽/减宽的像素数:建议范围 50-200 MIN_WIDTH: 400, // 阅读栏最小宽度:防止过窄影响阅读体验 MAX_WIDTH: 1300, // 阅读栏最大宽度:防止过宽导致视线跨度过大 // 🎯 界面隐藏/显示配置(单栏模式专用) HIDE_THRESHOLD: 30, // 向下滚动多少像素开始隐藏顶部栏:数值越小越敏感 HIDE_DISTANCE: 50, // 完全隐藏需要的额外滚动距离:数值越大越不容易完全隐藏 SHOW_THRESHOLD: 30, // 向上滚动多少像素开始显示顶部栏:数值越小越敏感 SHOW_DISTANCE: 50, // 完全显示需要的额外滚动距离:数值越大越不容易完全显示 // ✍️ 字体粗细配置 // 100: '极细', // 200: '特细', // 300: '细体', // 400: '正常', // 500: '中等', // 600: '半粗', // 700: '粗体', // 800: '特粗', // 900: '极粗' // FONT_WEIGHTS: [100, 200, 300, 400, 500, 600, 700, 800, 900], // 可选择的字体粗细等级(共9个) FONT_WEIGHTS: [300, 400, 500, 600, 700], // 这里选择了其中5个等级 DEFAULT_FONT_WEIGHT: 400 // 默认字体粗细:400为标准粗细 }; // ============================== // 🎨 主题配色方案 - 自定义你的阅读体验 // ============================== // ============================== // 🔧 颜色搭配建议 - 帮助你选择合适的配色 // ============================== /* * 📋 主题设计原则: * 1. textColor(正文色)与 readerBgColor(背景色)要有足够对比度 * 2. backgroundColor 建议比 readerBgColor 稍深或稍浅,营造层次感 * 3. readerButtonColor 建议选择中性色,不要过于鲜艳 * 4. underlineColor 可选,用于书友想法划线,建议与主色调协调 * * 🎨 经典配色组合推荐: * - 护眼绿色系:背景 #E8F5E8,文字 #2F4F2F,按钮 #5F7F5F * - 温暖米色系:背景 #F5F5DC,文字 #8B4513,按钮 #CD853F * - 冷色蓝灰系:背景 #F0F8FF,文字 #2F4F4F,按钮 #708090 * - 深色护眼系:背景 #2F2F2F,文字 #E0E0E0,按钮 #8AA9FF * * 💡 配色工具推荐: * - Adobe Color:https://color.adobe.com/zh/ * - Coolors:https://coolors.co/ * - 中国色:http://zhongguose.com/ */ const THEMES = [ { name: '牛皮纸纹理', url: GM_getResourceURL("BG01"), textColor: '#2D1B15', backgroundColor: '#2D2419', readerButtonColor: '#4F4F4F', underlineColor: '#2D2419', darkEnable: false, }, { name: '花笺诗韵', url: GM_getResourceURL("BG02"), textColor: '#2F3D2A', backgroundColor: '#CDD3C0', readerButtonColor: '#6B7A5F', underlineColor: '#8A9B7A', darkEnable: false, }, { name: '水墨清韵', url: GM_getResourceURL("BG03"), textColor: '#2C3E50', backgroundColor: '#D5D8DC', readerButtonColor: '#5D6D7E', underlineColor: '#85929E', darkEnable: false, }, { name: '古典羊皮纸', readerBgColor: '#F5ECD9', textColor: '#3A2F24', backgroundColor: '#E6D9BC', readerButtonColor: '#B48A5A', darkEnable: false, }, { name: '暮色森林', readerBgColor: '#404F37', textColor: '#D0D0D0', backgroundColor: '#38442F', readerButtonColor: '#7B8C6F', underlineColor: '#5A6B4E', darkEnable: true, }, { name: '暖阳书香', readerBgColor: '#FDF6E3', textColor: '#8B4513', backgroundColor: '#F4E4BC', readerButtonColor: '#CD853F', underlineColor: '#DEB887', darkEnable: false }, { name: '春山茶纸', readerBgColor: '#D6DBBC', textColor: '#474E31', backgroundColor: '#C8D6B8', readerButtonColor: '#4E7B50', darkEnable: false, }, { name: '雾霾灰调', readerBgColor: '#DEDEE3', textColor: '#2A2A2E', backgroundColor: '#EAEAEF', readerButtonColor: '#DEDEE', darkEnable: false, }, { name: '静夜寂黑', readerBgColor: '#2E2E2E', textColor: '#EAEAEA', backgroundColor: '#242424', readerButtonColor: '#8AA9FF', darkEnable: false, }, // 💡 如需添加自定义主题,请按以下格式添加: // { // name: '你的主题名称', // readerBgColor: '#颜色代码', // 阅读区背景 // textColor: '#颜色代码', // 正文字体颜色 // backgroundColor: '#颜色代码', // 页面周围背景色 // readerButtonColor: '#颜色代码', // 按钮颜色 // underlineColor: '#颜色代码', // 划线颜色 // darkEnable: true/false, // 是否支持深色模式 // }, ]; // SVG 图标配置 const ICONS = { theme: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="m20 13.7-2.1-2.1a2 2 0 0 0-2.8 0L9.7 17"/><path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20"/><circle cx="10" cy="8" r="2"/></svg>`, scroll: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v14"/><path d="m19 9-7 7-7-7"/><circle cx="12" cy="21" r="1"/></svg>`, fullscreen: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><rect width="10" height="8" x="7" y="8" rx="1"/></svg>`, decrease: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M8 12h8"/></svg>`, increase: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M8 12h8"/><path d="M12 8v8"/></svg>`, fontWeight: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4h8a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/><path d="M6 12h9a4 4 0 0 1 4 4 4 4 0 0 1-4 4H6z"/></svg>` }; // ============================== // 工具类 // ============================== class Utils { static addStyle(css) { GM_addStyle(css); } static createElement(tag, className, innerHTML) { const element = document.createElement(tag); if (className) element.className = className; if (innerHTML) element.innerHTML = innerHTML; return element; } static debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } static getCurrentValue(element, property) { const value = window.getComputedStyle(element)[property]; return parseInt(value.replace('px', '')) || 0; } // 判断当前阅读模式 static getReaderMode() { const horizontalReader = document.querySelector( ".readerControls_item.isHorizontalReader" ); const normalReader = document.querySelector( ".readerControls_item.isNormalReader" ); //normal为上下滚动阅读模式,会展示“isNormalReader” //horizontal表示水平双栏阅读模式 return normalReader ? "normal" : "horizontal"; } // 判断是否为深色模式 static isDarkMode() { // 方法1: 检查深色模式按钮的类名状态 const darkButton = document.querySelector('.readerControls_item.white'); if (darkButton) { console.log("当前为深色模式!") return true; } console.log("当前为浅色模式!") return false; } } // ============================== // 按钮管理类 // ============================== class ButtonManager { constructor() { this.buttons = new Map(); } create(config) { const controls = document.querySelector('.readerControls'); if (!controls || controls.querySelector('.' + config.className)) return null; const container = Utils.createElement('div', 'wr_tooltip_container'); container.setAttribute('style', '--offset: 6px;'); const btn = Utils.createElement('button', `${config.className} readerControls_item`, config.icon); const tooltip = Utils.createElement('div', 'wr_tooltip_item wr_tooltip_item--right', config.tooltip); tooltip.style.display = 'none'; container.appendChild(btn); container.appendChild(tooltip); // 添加悬停效果 container.addEventListener('mouseenter', () => tooltip.style.display = 'block'); container.addEventListener('mouseleave', () => tooltip.style.display = 'none'); if (config.onClick) { btn.addEventListener('click', config.onClick); } controls.appendChild(container); this.buttons.set(config.className, btn); return btn; } get(className) { return this.buttons.get(className); } } // ============================== // 自动滚动管理类 // ============================== class ScrollManager { constructor() { this.scrolling = false; this.scrollTimer = null; } start() { if (this.scrolling) return; this.scrolling = true; this.scrollTimer = setInterval(() => { window.scrollBy(0, CONFIG.SCROLL_SPEED); }, CONFIG.INTERVAL_MS); } stop() { this.scrolling = false; if (this.scrollTimer) { clearInterval(this.scrollTimer); this.scrollTimer = null; } } toggle(button) { if (this.scrolling) { this.stop(); button.classList.remove('active'); button.title = '开始自动滚动'; } else { this.start(); button.classList.add('active'); button.title = '暂停自动滚动'; } } } // ============================== // 主题管理类 // ============================== class ThemeManager { constructor() { this.currentTheme = GM_getValue('currentTheme', null); this.modal = null; } // 获取应用背景色的目标元素 getTargetElement() { const mode = Utils.getReaderMode(); if (mode === "normal") { //上下 return document.querySelector(".app_content"); } else { //水平 return document.querySelector(".readerChapterContent"); } } applyTheme(theme) { // 如果是默认主题,清除所有自定义样式 // if (theme.isDefault) { // this.clearCustomStyles(); // return; // } const content = this.getTargetElement(); if (content) { if (theme.url) { content.style.cssText += ` background-image: url(${theme.url}); background-size: auto; background-position: center top; background-attachment: fixed; background-repeat: repeat; image-rendering: crisp-edges; `; } else if (theme.readerBgColor) { content.style.cssText += ` background-image: none; background-color: ${theme.readerBgColor}; `; } } if (Utils.getReaderMode() === "normal"){ //针对上下模式的容器 Utils.addStyle(` .readerChapterContent { color: ${theme.textColor} !important; } .readerContent { background-color: ${theme.backgroundColor}; } .readerFooter_button { color: ${theme.readerButtonColor} !important; } .readerHeaderButton { color: ${theme.readerButtonColor} !important; } `); }else{ //针对水平模式的容器 let styles = ` .readerChapterContent { color: ${theme.textColor} !important; } .readerContent { background-color: ${theme.backgroundColor}; } .readerFooter_button { color: ${theme.readerButtonColor} !important; } .readerHeaderButton { color: ${theme.readerButtonColor} !important; } .readerChapterContent_container { color: ${theme.textColor} !important; background-color: ${theme.backgroundColor} !important; } .readerTopBar { background-color: ${theme.backgroundColor} !important; } .renderTargetPageInfo_header_chapterTitle { color: ${theme.textColor} !important; } .wr_flyleaf_module_rating_graph_badge_wrapper{ color: ${theme.textColor} !important; } .wr_flyleaf_module_rating_stats_item_content{ color: ${theme.textColor} !important; } .wr_flyleaf_module_rating_stats_wrapper { color: ${theme.textColor} !important; } .wr_flyleaf_module_rating_stats_item_content { color: ${theme.textColor} !important; } .wr_flyleaf_module_rating_action_button { color: ${theme.textColor} !important; } .wr_whiteTheme .wr_flyleaf_module_rating_stats_wrapper .wr_flyleaf_module_rating_stats_item_label { color: ${theme.textColor} !important; } .wr_flyleaf_module_rating_graph_bar_wrapper { color: ${theme.textColor} !important; } .wr_flyleaf_page wr_flyleaf_page_bookInfo { color: ${theme.textColor} !important; } .wr_whiteTheme .wr_flyleaf_page_bookInfo_author.wr_flyleaf_page_bookInfo_author_clickable { color: ${theme.textColor} !important; } .wr_whiteTheme .wr_flyleaf_module_rating_top_section .wr_flyleaf_module_rating_title { color: ${theme.textColor} !important; } `; // 划线颜色适配:当主题有配置underlineColor时才添加划线颜色样式 if (theme.underlineColor) { styles += ` .wr_underline_thought { border-bottom-color: ${theme.underlineColor} !important; } `; } Utils.addStyle(styles); } // 添加深色模式按钮监听,当非深色模式可用主题时,点击按钮清空样式 document.addEventListener('click', (event) => { // 检查点击的是否是深色模式按钮 if (event.target.closest('button.readerControls_item.white') && !this.currentTheme.darkEnable) { console.log('检测到深色模式按钮点击,且当前主题不支持深色模式!'); // 延迟执行,确保微信阅读的模式切换完成 setTimeout(() => { this.clearCustomStyles(); }, 150); } }); } // 默认主题:清除自定义样式的方法 clearCustomStyles() { // 移除所有自定义添加的样式标签 const customStyles = document.querySelectorAll('style[id*="GM_"], style[id="font-weight-style"]'); customStyles.forEach(style => style.remove()); // 清除内联样式 const content = this.getTargetElement(); if (content) { content.style.cssText = ''; } // 重置body背景 const body = document.querySelector('body'); if (body) { body.style.backgroundColor = ''; } // 清除存储的主题和字体粗细 GM_setValue('currentTheme', null); GM_setValue('currentFontWeight', 400); console.log('已恢复默认主题'); location.reload(); } openModal() { if (this.modal) return; const isDarkMode = Utils.isDarkMode(); const overlay = Utils.createElement('div', 'bg-overlay'); overlay.addEventListener('click', () => this.closeModal()); const modal = Utils.createElement('div', 'bg-modal'); const closeBtn = Utils.createElement('button', 'bg-close', '×'); closeBtn.addEventListener('click', () => this.closeModal()); const title = Utils.createElement('h3', '', '选择阅读主题'); // 添加当前模式提示 // const modeIndicator = Utils.createElement('div', 'theme-mode-indicator', // `当前模式: ${isDarkMode ? '深色模式' : '浅色模式'}`); const grid = Utils.createElement('div', 'bg-grid'); THEMES.forEach((theme) => { const item = Utils.createElement('div', 'bg-item'); item.setAttribute('data-name', theme.name); // 检查主题在当前模式下是否可用 const isEnabled = isDarkMode ? theme.darkEnable : true; if (!isEnabled) { item.classList.add('disabled'); } if (theme.url) { item.style.backgroundImage = `url(${theme.url})`; } else if (theme.readerBgColor) { item.style.backgroundColor = theme.readerBgColor; } // 添加禁用状态的视觉反馈 if (!isEnabled) { const disabledOverlay = Utils.createElement('div', 'disabled-overlay'); const disabledText = Utils.createElement('div', 'disabled-text', `${isDarkMode ? '深色模式' : '浅色模式'}不支持`); disabledOverlay.appendChild(disabledText); item.appendChild(disabledOverlay); } // 添加当前选中的主题标记 if (this.currentTheme && this.currentTheme.name === theme.name) { item.classList.add('selected'); const selectedMark = Utils.createElement('div', 'selected-mark', '✓'); item.appendChild(selectedMark); } item.addEventListener('click', () => { if (!isEnabled) { // 显示提示信息 this.showDisabledTooltip(item, `此主题在${isDarkMode ? '深色' : '浅色'}模式下不可用`); return; } GM_setValue('currentTheme', theme); location.reload(); }); grid.appendChild(item); }); // 添加重置按钮 const resetBtn = Utils.createElement('button', 'theme-reset-btn', '恢复默认主题'); resetBtn.addEventListener('click', () => { GM_setValue('currentTheme', null); location.reload(); }); modal.appendChild(closeBtn); modal.appendChild(title); // modal.appendChild(modeIndicator); modal.appendChild(grid); modal.appendChild(resetBtn); document.body.appendChild(overlay); document.body.appendChild(modal); document.body.style.overflow = 'hidden'; this.modal = { modal, overlay }; } // 显示禁用主题的提示 showDisabledTooltip(element, message) { const tooltip = Utils.createElement('div', 'disabled-tooltip', message); element.appendChild(tooltip); setTimeout(() => { tooltip.classList.add('show'); }, 10); setTimeout(() => { tooltip.classList.remove('show'); setTimeout(() => { if (tooltip.parentNode) { tooltip.parentNode.removeChild(tooltip); } }, 300); }, 2000); } closeModal() { if (!this.modal) return; const { modal, overlay } = this.modal; modal.style.animation = 'slideOut 0.4s cubic-bezier(0.4, 0.0, 0.2, 1) forwards'; overlay.style.animation = 'fadeOut 0.4s cubic-bezier(0.4, 0.0, 0.2, 1) forwards'; setTimeout(() => { modal.remove(); overlay.remove(); document.body.style.overflow = ''; this.modal = null; }, 400); } init() { if (this.currentTheme) { this.applyTheme(this.currentTheme); } } } // ============================== // 宽度管理类 // ============================== class WidthManager { constructor() { this.elements = { content: () => document.querySelector(".readerContent .app_content"), topBar: () => document.querySelector('.readerTopBar'), controls: () => document.querySelector('.readerControls') }; } changeWidth(increase) { const content = this.elements.content(); const topBar = this.elements.topBar(); const controls = this.elements.controls(); if (!content || !topBar) return; const currentValue = Utils.getCurrentValue(content, 'maxWidth'); const currentMargin = Utils.getCurrentValue(controls, 'marginLeft'); let newValue = increase ? Math.min(currentValue + CONFIG.WIDTH_STEP, CONFIG.MAX_WIDTH) : Math.max(currentValue - CONFIG.WIDTH_STEP, CONFIG.MIN_WIDTH); let newMargin = currentMargin + (newValue - currentValue) / 2; content.style.maxWidth = newValue + 'px'; topBar.style.maxWidth = newValue + 'px'; if (controls) { controls.style.marginLeft = newMargin + 'px'; controls.style.transition = 'margin-left 0.3s cubic-bezier(0.4, 0.0, 0.2, 1)'; } window.dispatchEvent(new Event('resize')); this.updateButtonStates(newValue); } updateButtonStates(currentWidth) { const decreaseBtn = document.querySelector('.width-decrease-btn'); const increaseBtn = document.querySelector('.width-increase-btn'); [decreaseBtn, increaseBtn].forEach(btn => { if (!btn) return; const isDisabled = (btn === decreaseBtn && currentWidth <= CONFIG.MIN_WIDTH) || (btn === increaseBtn && currentWidth >= CONFIG.MAX_WIDTH); btn.style.opacity = isDisabled ? '0.5' : '1'; btn.style.cursor = isDisabled ? 'not-allowed' : 'pointer'; }); } } // ============================== // 顶部栏及控制栏自动隐藏类 // ============================== class TopBarManager { constructor() { this.lastScrollY = window.scrollY; this.baseScrollY = window.scrollY; this.currentState = 'visible'; this.ticking = false; // 双栏模式下控制 this.hideTimer = null; this.mouseInside = false; } setup() { const topBar = document.querySelector('.readerTopBar'); const controls = document.querySelector('.readerControls'); if (!topBar || !controls) return; topBar.style.transition = 'transform 0.3s ease'; controls.style.transition = 'opacity 0.3s ease, transform 0.3s ease'; // controls.style.willChange = 'opacity, transform'; if(Utils.getReaderMode() !== "horizontal"){ //单栏 const onScroll = () => { const currentY = window.scrollY; const scrollDelta = currentY - this.lastScrollY; if (scrollDelta > 0) { this.handleDownScroll(currentY, topBar, controls); } else if (scrollDelta < 0) { this.handleUpScroll(currentY, topBar, controls); } this.lastScrollY = currentY; this.ticking = false; }; window.addEventListener('scroll', () => { if (!this.ticking) { window.requestAnimationFrame(onScroll); this.ticking = true; } }); // 鼠标进入控制栏:立刻显示并清除定时器 controls.addEventListener('mouseenter', () => { controls.style.opacity = '1'; }); }else{ // ==== 双栏模式:3s 自动隐藏 + 悬停渐显 ==== this.setupHorizontalMode(controls); } } handleDownScroll(currentY, topBar, controls) { if (this.currentState === 'visible' || this.currentState === 'showing') { this.baseScrollY = currentY; this.currentState = 'hiding'; } if (this.currentState === 'hiding' || this.currentState === 'hidden') { const hideScroll = currentY - this.baseScrollY; if (hideScroll > CONFIG.HIDE_THRESHOLD) { const hideProgress = Math.min((hideScroll - CONFIG.HIDE_THRESHOLD) / CONFIG.HIDE_DISTANCE, 1); topBar.style.transform = `translateY(${-100 * hideProgress}%)`; controls.style.opacity = '0'; // controls.style.opacity = 1 - hideProgress; // controls.style.transform = 'none'; if (hideProgress >= 1) { this.currentState = 'hidden'; } } } } handleUpScroll(currentY, topBar, controls) { if (this.currentState === 'hidden' || this.currentState === 'hiding') { this.baseScrollY = currentY; this.currentState = 'showing'; } if (this.currentState === 'showing' || this.currentState === 'visible') { const showScroll = this.baseScrollY - currentY; if (showScroll > CONFIG.SHOW_THRESHOLD) { const showProgress = Math.min((showScroll - CONFIG.SHOW_THRESHOLD) / CONFIG.SHOW_DISTANCE, 1); const hideProgress = 1 - showProgress; topBar.style.transform = `translateY(${-100 * hideProgress}%)`; controls.style.opacity = '1'; // controls.style.opacity = 1 - hideProgress; // controls.style.transform = 'none'; if (showProgress >= 1) { this.currentState = 'visible'; } } } } /* ---------- 双栏模式专用 ---------- */ setupHorizontalMode(controls) { // 初次进入 3 秒后隐藏 this.startHideTimer(controls); // 鼠标进入控制栏:立刻显示并清除定时器 controls.addEventListener('mouseenter', () => { this.mouseInside = true; this.showControls(controls); this.clearHideTimer(); }); // 鼠标离开控制栏:3 秒后隐藏 controls.addEventListener('mouseleave', () => { this.mouseInside = false; this.startHideTimer(controls); }); } startHideTimer(controls) { this.clearHideTimer(); this.hideTimer = setTimeout(() => { if (!this.mouseInside) { this.hideControls(controls); } }, 3000); // 3 秒 } clearHideTimer() { if (this.hideTimer) { clearTimeout(this.hideTimer); this.hideTimer = null; } } hideControls(controls) { controls.style.opacity = '0'; // controls.style.transform = 'translateX(40px)'; // controls.style.pointerEvents = 'none'; } showControls(controls) { controls.style.opacity = '1'; // controls.style.transform = 'translateX(0)'; // controls.style.pointerEvents = 'auto'; } } // ============================== // 字体粗细滑块管理类 - 支持自定义粗细等级 // ============================== class FontWeightSliderManager { constructor() { // 从存储中获取当前字体粗细,默认使用配置的默认值 this.currentWeight = GM_getValue('currentFontWeight', CONFIG.DEFAULT_FONT_WEIGHT); this.popup = null; this.isPopupVisible = false; // 使用配置的字体粗细等级 this.fontWeights = CONFIG.FONT_WEIGHTS; } /** * 创建字体粗细调节悬浮框 */ createPopup(button) { const popup = Utils.createElement('div', 'font-weight-popup'); const title = Utils.createElement('div', 'font-weight-title', '字体粗细'); const sliderContainer = Utils.createElement('div', 'font-weight-slider-container'); // 创建左侧标签(细) const leftLabel = Utils.createElement('span', 'font-weight-label left', 'A'); leftLabel.style.fontWeight = this.fontWeights[0]; // 使用配置的最小值 // 创建滑块 - 动态设置范围 const slider = Utils.createElement('input', 'font-weight-slider'); slider.type = 'range'; slider.min = '0'; // 使用索引而不是实际值 slider.max = String(this.fontWeights.length - 1); slider.step = '1'; // 找到当前值在数组中的索引 const currentIndex = this.fontWeights.indexOf(this.currentWeight); slider.value = currentIndex >= 0 ? currentIndex : Math.floor(this.fontWeights.length / 2); // 创建右侧标签(粗) const rightLabel = Utils.createElement('span', 'font-weight-label right', 'A'); rightLabel.style.fontWeight = this.fontWeights[this.fontWeights.length - 1]; // 使用配置的最大值 // 创建当前值显示 const valueDisplay = Utils.createElement('div', 'font-weight-value'); this.updateValueDisplay(valueDisplay, this.currentWeight); // 组装滑块容器 sliderContainer.appendChild(leftLabel); sliderContainer.appendChild(slider); sliderContainer.appendChild(rightLabel); // 组装悬浮框 popup.appendChild(title); popup.appendChild(sliderContainer); popup.appendChild(valueDisplay); // 绑定滑块事件 - 根据索引获取实际值 slider.addEventListener('input', (e) => { const index = parseInt(e.target.value); const weight = this.fontWeights[index]; this.currentWeight = weight; this.updateValueDisplay(valueDisplay, weight); this.applyFontWeight(weight); GM_setValue('currentFontWeight', weight); }); // 添加滑块变化完成事件(松开鼠标时) slider.addEventListener('change', () => { location.reload(); }); // 定位悬浮框 this.positionPopup(popup, button); return popup; } /** * 更新值显示 - 支持自定义字体粗细值 */ updateValueDisplay(valueDisplay, weight) { // 预定义常见的字体粗细名称 const weightNames = { 100: '极细', 200: '特细', 300: '细体', 400: '正常', 500: '中等', 600: '半粗', 700: '粗体', 800: '特粗', 900: '极粗' }; // 如果有预定义名称就使用,否则显示数值 const displayText = weightNames[weight] || `${weight}`; valueDisplay.textContent = displayText; // 添加当前数值显示 if (!weightNames[weight]) { valueDisplay.textContent = `粗细 ${weight}`; } } /** * 定位悬浮框 */ positionPopup(popup, button) { const buttonRect = button.getBoundingClientRect(); popup.style.position = 'fixed'; popup.style.right = (window.innerWidth - buttonRect.left + 10) + 'px'; popup.style.top = (buttonRect.top - 10) + 'px'; popup.style.zIndex = '10000'; } /** * 应用字体粗细样式 */ applyFontWeight(weight) { const mode = Utils.getReaderMode(); // 移除之前的样式 const existingStyle = document.querySelector('#font-weight-style'); if (existingStyle) { existingStyle.remove(); } // 创建新的样式 const style = document.createElement('style'); style.id = 'font-weight-style'; if (mode === "normal") { style.textContent = ` .readerChapterContent { font-weight: ${weight} !important; } `; } else { style.textContent = ` .readerChapterContent, .readerChapterContent_container { font-weight: ${weight} !important; } `; } document.head.appendChild(style); } /** * 显示悬浮框 */ showPopup(button) { if (this.isPopupVisible) { this.hidePopup(); return; } this.popup = this.createPopup(button); document.body.appendChild(this.popup); this.isPopupVisible = true; setTimeout(() => { document.addEventListener('click', this.handleDocumentClick.bind(this)); }, 100); } /** * 隐藏悬浮框 */ hidePopup() { if (this.popup) { this.popup.remove(); this.popup = null; this.isPopupVisible = false; document.removeEventListener('click', this.handleDocumentClick.bind(this)); } } /** * 处理文档点击事件 */ handleDocumentClick(e) { if (this.popup && !this.popup.contains(e.target) && !e.target.closest('.font-weight-btn')) { this.hidePopup(); } } /** * 初始化字体粗细 */ init() { // 确保当前值在配置的范围内 if (!this.fontWeights.includes(this.currentWeight)) { console.warn(`字体粗细 ${this.currentWeight} 不在配置范围内,使用默认值 ${CONFIG.DEFAULT_FONT_WEIGHT}`); this.currentWeight = CONFIG.DEFAULT_FONT_WEIGHT; GM_setValue('currentFontWeight', this.currentWeight); } this.applyFontWeight(this.currentWeight); } } // 样式定义 const FONT_WEIGHT_POPUP_STYLES = ` /* 字体粗细悬浮框样式 */ .font-weight-popup { background: #ffffff; border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); padding: 20px; width: 280px; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); animation: popupFadeIn 0.2s ease-out; } .font-weight-title { font-size: 16px; font-weight: 600; color: #333; margin-bottom: 16px; text-align: center; } .font-weight-slider-container { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; } .font-weight-label { font-size: 14px; color: #666; min-width: 20px; text-align: center; } .font-weight-slider { flex: 1; -webkit-appearance: none; height: 6px; border-radius: 3px; background: #e0e0e0; outline: none; cursor: pointer; } .font-weight-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 20px; height: 20px; border-radius: 50%; background: #1D88EE; cursor: pointer; box-shadow: 0 2px 6px rgba(29, 136, 238, 0.3); transition: all 0.2s ease; } .font-weight-slider::-webkit-slider-thumb:hover { transform: scale(1.1); box-shadow: 0 4px 12px rgba(29, 136, 238, 0.4); } .font-weight-slider::-moz-range-thumb { width: 20px; height: 20px; border-radius: 50%; background: #1D88EE; cursor: pointer; border: none; box-shadow: 0 2px 6px rgba(29, 136, 238, 0.3); transition: all 0.2s ease; } .font-weight-value { text-align: center; font-size: 14px; color: #1D88EE; font-weight: 600; padding: 8px 16px; background: rgba(29, 136, 238, 0.1); border-radius: 8px; } @keyframes popupFadeIn { from { opacity: 0; transform: translateY(-10px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } } `; // ============================== // 主应用类 // ============================== class WeReadEnhancer { constructor() { this.buttonManager = new ButtonManager(); this.scrollManager = new ScrollManager(); this.themeManager = new ThemeManager(); this.widthManager = new WidthManager(); this.topBarManager = new TopBarManager(); this.fontWeightSliderManager = new FontWeightSliderManager(); this.observer = null; } init() { this.addStyles(); this.setupObserver(); // this.themeManager.init(); // this.fontWeightSliderManager.init(); // 延迟初始化顶部栏管理器,确保DOM已完全加载 setTimeout(() => { // this.topBarManager.setup(); this.createButtons(); this.fontWeightSliderManager.init(); }, 500); Utils.isDarkMode(); } addStyles() { Utils.addStyle(` /* 通用按钮样式 */ .readerControls_item { display: flex; align-items: center; justify-content: center; width: 48px; height: 48px; border: none; background: transparent; cursor: pointer; font-size: 18px; border-radius: 50%; transition: all 0.3s ease; color: #868C96; } .readerControls_item:hover { color: #212832; transform: scale(1.05); } .auto-scroll-btn.active { color: #1D88EE; } .width-control-btn:disabled { opacity: 0.5; cursor: not-allowed; } /* 主题选择器增强样式 */ .bg-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.6); z-index: 9998; backdrop-filter: blur(4px); animation: fadeIn 0.3s ease; } .bg-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #ffffff; border-radius: 20px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); padding: 32px; z-index: 9999; width: 90%; max-width: 600px; max-height: 80vh; overflow-y: auto; animation: slideIn 0.3s ease; } .bg-modal h3 { margin: 0 0 16px 0; font-size: 22px; font-weight: 600; text-align: center; color: #333; } // .theme-mode-indicator { // text-align: center; // margin-bottom: 24px; // padding: 8px 16px; // background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); // color: white; // border-radius: 20px; // font-size: 14px; // font-weight: 500; // } .bg-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 20px; margin-bottom: 24px; } .bg-item { width: 100%; height: 120px; background-size: cover; background-position: center; border-radius: 12px; cursor: pointer; border: 3px solid transparent; transition: all 0.3s ease; position: relative; overflow: hidden; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .bg-item:not(.disabled):hover { border-color: #4caf50; transform: translateY(-4px); box-shadow: 0 8px 25px rgba(76, 175, 80, 0.3); } .bg-item.selected { border-color: #2196F3; box-shadow: 0 4px 20px rgba(33, 150, 243, 0.4); } .bg-item.disabled { cursor: not-allowed; // opacity: 0.8; filter: grayscale(0.8); } .bg-item.disabled:hover { transform: none; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); } .disabled-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.7); display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.3s ease; } .bg-item.disabled:hover .disabled-overlay { opacity: 1; } .disabled-text { color: white; font-size: 12px; text-align: center; padding: 4px 8px; background: rgba(255, 255, 255, 0.2); border-radius: 4px; backdrop-filter: blur(4px); } .selected-mark { position: absolute; top: 8px; right: 8px; width: 24px; height: 24px; background: #2196F3; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 14px; font-weight: bold; box-shadow: 0 2px 8px rgba(33, 150, 243, 0.4); } .disabled-tooltip { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(244, 67, 54, 0.95); color: white; padding: 8px 12px; border-radius: 6px; font-size: 12px; white-space: nowrap; opacity: 0; transition: all 0.3s ease; z-index: 10; pointer-events: none; } .disabled-tooltip.show { opacity: 1; transform: translate(-50%, -50%) scale(1); } .bg-item::after { content: attr(data-name); position: absolute; bottom: 0; left: 0; right: 0; background: linear-gradient(transparent, rgba(0, 0, 0, 0.8)); color: white; padding: 16px 8px 8px; font-size: 12px; text-align: center; opacity: 0; transform: translateY(10px); transition: all 0.3s ease; } .bg-item:not(.disabled):hover::after { opacity: 1; transform: translateY(0); background: #ABABAB } .theme-reset-btn { width: 100%; padding: 12px; background: #F4F5F7; color: white; border: none; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.3s ease; color: #202832 } .theme-reset-btn:hover { background: #E2E3E5; transform: translateY(-2px); color: #202832 } .bg-close { position: absolute; top: 16px; right: 16px; width: 32px; height: 32px; border: none; background: #F4F5F7; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 18px; color: #666; transition: all 0.3s ease; } .bg-close:hover { background: #E2E3E5; transform: translateY(-2px); color: #202832; } /* 动画 */ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes slideIn { from { opacity: 0; transform: translate(-50%, -50%) scale(0.9); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes slideOut { from { opacity: 1; transform: translate(-50%, -50%) scale(1); } to { opacity: 0; transform: translate(-50%, -50%) scale(0.95); } } `); Utils.addStyle(FONT_WEIGHT_POPUP_STYLES); } createButtons() { const basicButtonConfigs = [ { className: 'font-weight-btn', icon: ICONS.fontWeight, tooltip: '字体粗细', onClick: () => { const btn = this.buttonManager.get('font-weight-btn'); this.fontWeightSliderManager.showPopup(btn); } }, { className: 'bg-select-btn', icon: ICONS.theme, tooltip: '切换主题', onClick: () => this.themeManager.openModal() }, { className: 'full-screen-btn', icon: ICONS.fullscreen, tooltip: '沉浸式', onClick: () => { if (!document.fullscreenElement) { document.documentElement.requestFullscreen?.(); } else { document.exitFullscreen?.(); } } }, ]; //滚动模式独有的按钮 const normalButtonConfigs = [ { className: 'auto-scroll-btn', icon: ICONS.scroll, tooltip: '自动滚动', onClick: () => { const btn = this.buttonManager.get('auto-scroll-btn'); this.scrollManager.toggle(btn); } }, { className: 'width-increase-btn', icon: ICONS.increase, tooltip: '加宽', onClick: () => this.widthManager.changeWidth(true) }, { className: 'width-decrease-btn', icon: ICONS.decrease, tooltip: '减宽', onClick: () => this.widthManager.changeWidth(false) } ] let buttonConfigs; if (Utils.getReaderMode() === "normal") { buttonConfigs = basicButtonConfigs.concat(normalButtonConfigs); //滚动模式按钮多,防止溢出 //TODO Utils.addStyle(` .readerControls { top: 5% !important; } .readerControls>* { margin-bottom: 15px; } `); } else { buttonConfigs = basicButtonConfigs; } buttonConfigs.forEach(config => this.buttonManager.create(config)); } setupObserver() { this.observer = new MutationObserver(Utils.debounce(() => { // this.createButtons(); // 确保顶部栏管理器重新初始化relo setTimeout(() => { this.topBarManager.setup(); }, 100); if (this.themeManager.currentTheme) { this.themeManager.applyTheme(this.themeManager.currentTheme); } }, 100)); this.observer.observe(document.body, { childList: true, subtree: true }); } } // ============================== // 启动应用 // ============================== const app = new WeReadEnhancer(); app.init(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址