您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
微信读书的阅读字体修改为苍耳今楷,加减宽度,鼠标离开显示隐藏导航栏、功能栏、滚动条,多档滚动速度,自动翻页,自定义宽度和主题(与原生深/浅主题互斥),仅适配weread.qq.com站点
// ==UserScript== // @name 沉浸阅读(for微信读书) // @version 0.5.0 // @author GinWU // @contributor !Sylas;SimonDW;Li_MIxdown;hubzy;xvusrmqj;LossJ;JackieZheng;das2m;harmonyLife;yehuda // @namespace https://screw-hand.com/ // @description 微信读书的阅读字体修改为苍耳今楷,加减宽度,鼠标离开显示隐藏导航栏、功能栏、滚动条,多档滚动速度,自动翻页,自定义宽度和主题(与原生深/浅主题互斥),仅适配weread.qq.com站点 // @match https://weread.qq.com/web/reader/* // @icon https://weread.qq.com/favicon.ico // @grant GM_addStyle // @grant unsafeWindow // @license MIT // ==/UserScript== /** * fork from https://greasyfork.org/zh-CN/scripts/490065-%E5%BE%AE%E4%BF%A1%E8%AF%BB%E4%B9%A6weread%E9%98%85%E8%AF%BB%E7%BB%BC%E5%90%88%E5%8A%9F%E8%83%BD%E7%89%88 * and so as https://greasyfork.org/zh-CN/scripts/458095-%E5%BE%AE%E4%BF%A1%E8%AF%BB%E4%B9%A6%E5%8A%A0%E5%AE%BD%E5%8F%AF%E8%A7%86%E8%8C%83%E5%9B%B4%E5%92%8C%E5%87%A0%E4%B8%AA%E7%99%BD%E8%89%B2%E4%B8%BB%E9%A2%98%E7%9A%84%E6%8A%A4%E7%9C%BC%E6%A8%A1%E5%BC%8F */ GM_addStyle(` * { font-family: SourceHanSerifCN-Bold !important; } .readerControls { height: 433px; margin-left: 0; left: initial; right: 20px; display: flex; flex-wrap: wrap; flex-direction: column; width: initial; right: 10px; bottom: 20px; padding-left: 200px; } .readerTopBar, .readerControls { opacity: 0; transition: opacity 1s; } .readerControls_item, .readerControls_fontSize { margin-top: 25px; margin-right: 10px; color:#6a6c6c; cursor:pointer; } .readerChapterContent { margin-left: 30px !important; margin-right: 30px !important; } .readerControls:hover, .readerTopBar:hover { opacity: 1; } .readerCatalog { right: 125px; left: initial; } .readerAIChatPanel { right: 125px; left: initial; } .readerAIChatPanel_header { display: grid; grid-template-columns: 1fr auto; grid-template-rows: auto auto; grid-template-areas: "title button_full" "subtitle button_close"; align-items: center; column-gap: 15px; padding: 8px 15px; } .readerAIChatPanel_header_title { grid-area: title; justify-self: start; } .readerAIChatPanel_header_subtitle { grid-area: subtitle; justify-self: start; } .readerAIChatPanel_header_btn_full, .readerAIChatPanel_header_btn_close { margin: 4px 0; padding: 0 20px; background: #3a3a3a; color: #fff; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 8px rgba(0,0,0,0.08); } .readerAIChatPanel_header_btn_full { grid-area: button_full; } .readerAIChatPanel_header_btn_close { grid-area: button_close; } .readerAIChatPanel_fullscreen { z-index: 9999 !important; left: 0 !important; top: 0 !important; right: 0 !important; bottom: 0 !important; width: 100vw !important; height: 100vh !important; margin: 0 !important; border-radius: 0 !important; max-width: 100vw !important; max-height: 100vh !important; } `); const colors = [ { bg: 'rgba(236, 217, 172, 0.4)', rbg: 'rgba(243, 227, 188, 1)', bgb: 'rgba(251, 239, 209, 0.5)', bgbBar: '#dbdbdb', white: true }, { bg: 'rgba(102, 128, 102, 0.1)', rbg: 'rgba(65, 94, 62, 1)', bgb: 'rgba(102, 128, 102, 0.25)', bgbBar: '#dbdbdb', white: true }, { bg: '#b5b5b5', rbg: '#c5c5c5', bgb: '#dbdbdb', bgbBar: '#dbdbdb', white: true } ]; const ElementUtils = { cssGet: (tar_elm, property) => { /** * 获取 css 属性 * * tar_elm: elementNode * property: string */ return window.getComputedStyle(tar_elm).getPropertyValue(property); }, cssSet: (tar_elm, property, value, priority) => { /** * 设置 css 属性 * * tar_elm: node * property: string * value: string | number | null * priority: string | null */ return tar_elm.style.setProperty(property, value, priority); }, htmlGet: (tar_elm, is_out) => { /** * 获取 HTML 代码 * * tar_elm: node * is_in: bool */ if (is_out) { return tar_elm.outerHTML; } return tar_elm.innerHTML; }, insertHtml: (tar_elm, ins_html, position) => { /** * 在目标元素指定位置插入 HTML 代码 * * tar_elm: node * ins_html: string * position: string -> 可选项:beforeBegin、afterBegin、beforeEnd、afterEnd */ position = typeof position === "undefined" ? "beforeEnd" : position; tar_elm.insertAdjacentHTML(position, ins_html); }, insertElement: (tar_elm, ins_elm, position) => { /** * 在目标元素指定位置插入元素 * * tar_elm: node * ins_elm: node * position: string -> 可选项:beforeBegin、afterBegin、beforeEnd、afterEnd */ position = typeof position === "undefined" ? "beforeEnd" : position; tar_elm.insertAdjacentElement(position, ins_elm); }, }; let div_body = $css("body"); let div_content = $css(".app_content"); let div_controls = $css(".readerControls"); let div_top_bar = $css(".readerTopBar"); let btn_width_add = $css("#width-add"); let btn_width_dev = $css("#width-dev"); let btn_scroll_on = $css("#scroll-on"); let btn_scroll_off = $css("#scroll-off"); let btn_turn_tips = $css("#turn-page-tips"); let scroll_speed = 0; let styleElementForCustomTheme = null; function getStyleStr_customTheme() { let style = ` /* 通用样式覆盖,确保在任何情况下均优先显示自定义主题 */ html body.wr-mode-0, html body.wr-mode-1, html body.wr-mode-2 { transition: background-color 0.3s ease-in-out !important; } `; for (let i = 0; i < colors.length; i++) { const color = colors[i]; style += ` html body.wr-mode-${i} { background-color:${color.bg} !important; } html body.wr-mode-${i} .readerTopBar { background-color:${color.rbg} !important; } html body.wr-mode-${i} .readerControls_item, html body.wr-mode-${i} .readerControls_fontSize, html body.wr-mode-${i} .app_content, html body.wr-mode-${i} #custom-theme-toggle-btn /* 确保自定义按钮也应用背景 */ { background-color:${color.bgb} !important; } html body.wr-mode-${i} .readerCatalog { background-color:${color.bgbBar} !important; } html body.wr-mode-${i} .chapterItem_link { border: dashed #2a2a2a !important; border-width: 0 0 2px !important; } html body.wr-mode-${i} .chapterItem_text { font-size: 18px !important; } html body.wr-mode-${i} .readerNotePanel { background-color:${color.bgbBar} !important; } html body.wr-mode-${i} .sectionListItem_divider { border: dashed #2a2a2a !important; border-width: 0 0 2px !important; } html body.wr-mode-${i} .readerNotePanelBottomBar { border: solid ${color.bgbBar} !important; border-width: 0 0 2px !important; background-color:${color.rbg} !important; } /* 阅读区域内容样式 */ html body.wr-mode-${i} .readerChapterContent { color: ${color.white ? '#333333' : '#e8e8e8'} !important; } /* 章节标题样式 */ html body.wr-mode-${i} .readerChapterContent .chapterTitle { color: ${color.white ? '#000000' : '#ffffff'} !important; } `; if (color.white) { style += ` html body.wr-mode-${i} .readerFooter_button, html body.wr-mode-${i} .readerFooter_button:hover, html body.wr-mode-${i} #custom-theme-toggle-btn /* 自定义按钮文字颜色 */ { color:#2a2a2a !important; } html body.wr-mode-${i} .readerFooter_button { background-color:${color.bg} !important; } `; } else { style += ` html body.wr-mode-${i} #custom-theme-toggle-btn /* 自定义按钮文字颜色 */ { color:#b9b9ba !important; } `; } } return style; } function applyCustomThemeStyles() { const styleContent = getStyleStr_customTheme(); if (!styleElementForCustomTheme) { styleElementForCustomTheme = document.createElement('style'); styleElementForCustomTheme.id = 'custom-weread-styles-by-yehuda'; styleElementForCustomTheme.setAttribute('data-priority', 'highest'); document.head.appendChild(styleElementForCustomTheme); } styleElementForCustomTheme.textContent = styleContent; document.head.appendChild(styleElementForCustomTheme); console.log("自定义主题样式已应用,共计样式规则数量:" + (styleElementForCustomTheme.sheet ? styleElementForCustomTheme.sheet.cssRules.length : "未知")); } function removeCustomStyles() { const body = $css("body"); if (!body) return; colors.forEach((color, index) => { body.classList.remove('wr-mode-' + index); }); if (localStorage.getItem('wr-custom-mode')) { localStorage.removeItem('wr-custom-mode'); } body.removeAttribute('data-custom-color-mode'); // 可选:如果想在移除时也移除style标签内容,但通常保留规则让切换回来更快 if (styleElementForCustomTheme) { styleElementForCustomTheme.textContent = ''; } } function changeCustomThemeMode(modeIndexStr, currentModeIndexStr = null) { const body = $css("body"); if(!body) return; const modeIndex = parseInt(modeIndexStr); colors.forEach((_, index) => body.classList.remove('wr-mode-' + index)); body.classList.remove('wr_darkTheme'); if (!isNaN(modeIndex) && colors[modeIndex]) { body.classList.add('wr-mode-' + modeIndex); body.setAttribute('data-custom-color-mode', modeIndex.toString()); localStorage.setItem('wr-custom-mode', modeIndex.toString()); console.log(`已应用自定义主题 ${modeIndex},当前body类: ${body.classList.toString()}`); document.documentElement.style.display = 'none'; void document.documentElement.offsetHeight; document.documentElement.style.display = ''; } else { console.warn(`微信读书脚本:无效的自定义模式索引: ${modeIndexStr}`); removeCustomStyles(); } } function loadCustomThemeFeature() { if (!div_controls) { console.warn("微信读书脚本:.readerControls 未找到,无法添加自定义主题按钮。"); return; } applyCustomThemeStyles(); const customThemeButton = createElement('button', { title: '切换自定义主题', id: 'custom-theme-toggle-btn', class: 'readerControls_item' }); customThemeButton.textContent = '主题'; const NATIVE_WHITE_THEME_CLASS = 'wr_whiteTheme'; customThemeButton.onclick = async () => { const body = $css("body"); if (!body) { console.warn("微信读书脚本:未找到 body 元素。"); return; } customThemeButton.style.transform = "scale(1.1)"; customThemeButton.style.transition = "transform 0.2s"; setTimeout(() => { customThemeButton.style.transform = ""; }, 200); if (!body.classList.contains(NATIVE_WHITE_THEME_CLASS)) { console.log(`深色主题不支持自定义主题切换。当前类: ${body.classList.toString()}`); alert("请先手动切换为原生浅色主题,再使用自定义主题功能。"); return; } let nextCustomModeIndex = 0; const currentAppliedCustomModeStr = body.getAttribute('data-custom-color-mode'); if (currentAppliedCustomModeStr !== null) { const currentAppliedIndex = parseInt(currentAppliedCustomModeStr); if (!isNaN(currentAppliedIndex) && currentAppliedIndex >= 0 && currentAppliedIndex < colors.length) { nextCustomModeIndex = (currentAppliedIndex + 1) % colors.length; } else { console.warn(`Invalid 'data-custom-color-mode' value: ${currentAppliedCustomModeStr}. Resetting to first custom theme.`); nextCustomModeIndex = 0; } } else { console.log("No prior custom theme applied (data-custom-color-mode is null). Applying first custom theme."); nextCustomModeIndex = 0; } applyCustomThemeStyles(); changeCustomThemeMode(nextCustomModeIndex.toString(), currentAppliedCustomModeStr); }; div_controls.appendChild(customThemeButton); // 定义统一的原生主题按钮点击处理函数,以便复用和移除/添加监听器 const nativeThemeButtonClickHandler = () => { console.log("原生主题按钮点击,移除自定义主题样式。"); removeCustomStyles(); }; setTimeout(() => { const initialNativeDarkButton = document.querySelector('[title="深色"]'); const initialNativeLightButton = document.querySelector('[title="浅色"]'); // 当原生"切换到深色"按钮被点击(即从浅色变为深色)时,移除自定义主题 if (initialNativeDarkButton) { initialNativeDarkButton.addEventListener('click', nativeThemeButtonClickHandler); console.log("Added click listener to initialNativeDarkButton"); } // 当原生"切换到浅色"按钮被点击(即从深色变为浅色)时,移除自定义主题 if (initialNativeLightButton) { initialNativeLightButton.addEventListener('click', nativeThemeButtonClickHandler); console.log("Added click listener to initialNativeLightButton"); } }, 500); // 从 localStorage 初始化主题 const localMode = localStorage.getItem('wr-custom-mode'); if (localMode !== null && colors[parseInt(localMode)]) { changeCustomThemeMode(localMode); } } async function init() { "use strict"; // 最多等待 30 秒页面加载完成 let book = await waitElement(".wr_canvasContainer canvas", 30000); if (!book) { alert("书本内容加载失败,请手动刷新页面!"); return; } div_body = $css("body"); div_controls = $css(".readerControls"); div_top_bar = $css(".readerTopBar"); // 添加功能按键; div_controls.insertHtml(` <button title="等待翻页" id='turn-page-tips' class='readerControls_item'>等待翻页</button> <button title="加宽" id='width-add' class='readerControls_item'>加宽</button> <button title="减宽" id='width-dev' class='readerControls_item'>减宽</button> <button title="播放X0" id='scroll-on' class='readerControls_item'>播放X0</button> <button title="停止播放" id='scroll-off' class='readerControls_item'>停止播放</button> `); btn_width_add = $css("#width-add"); btn_width_dev = $css("#width-dev"); btn_scroll_on = $css("#scroll-on"); btn_scroll_off = $css("#scroll-off"); btn_turn_tips = $css("#turn-page-tips"); // 额外功能按钮功能实现 btn_width_add.onclick = () => changeWidth(true); btn_width_dev.onclick = () => changeWidth(false); btn_scroll_on.onclick = () => { scroll_speed++; if (scroll_speed == 1) { autoScroll(); } btn_scroll_on.innerHTML = "播放X" + scroll_speed; }; btn_scroll_off.onclick = () => { scroll_speed = 0; btn_scroll_on.innerHTML = "播放X0"; }; // 应用localStorage中的宽度设置 setWidth(window.localStorage.getItem('setWidth')); // 加载自定义主题相关功能 loadCustomThemeFeature(); let header = await waitElement(".readerAIChatPanel_header", 30000); if (!header) { console.warn('.readerAIChatPanel_header" not found, cannot add buttons.'); } // 全屏按钮 let btnFull = createElement('button'); btnFull.innerText = '全屏'; btnFull.className = 'readerAIChatPanel_header_btn_full'; // 类名用于grid-area btnFull.onclick = function() { let panel = $css('.readerAIChatPanel .readerAIChatPanel'); if(panel) { panel.classList.toggle('readerAIChatPanel_fullscreen'); if(panel.classList.contains('readerAIChatPanel_fullscreen')) { btnFull.innerText = '还原'; } else { btnFull.innerText = '全屏'; } } }; // 关闭按钮 let btnClose = createElement('button'); btnClose.innerText = '关闭'; btnClose.className = 'readerAIChatPanel_header_btn_close'; // 类名用于grid-area btnClose.onclick = function() { let mask = $css('.wr_mask.wr_mask_Show'); if(mask) mask.click(); }; // 只有当 header 存在时才添加按钮 if (header) { header.appendChild(btnFull); header.appendChild(btnClose); } }; // init 函数结束括号 init(); function changeWidth(isAdd, step) { step = typeof step === "undefined" ? 60 : step let currentMaxWidth = $css(".app_content").cssGet("max-width"); let width = 0; if (currentMaxWidth && currentMaxWidth !== "none" && currentMaxWidth.endsWith("px")) { width = Number(currentMaxWidth.replace("px", "")); } else { const appContentDiv = $css(".app_content"); if (appContentDiv) { width = appContentDiv.clientWidth; } if (!width || width <=0) width = 1000; } if (isAdd) { width += step; } else { width -= step; } setWidth(width); } function setWidth(width) { if (!width) { return; } const numericWidth = parseFloat(width); if (isNaN(numericWidth) || numericWidth <= 0) { console.warn("setWidth: 无效的宽度值", width); return; } let div_content_el = $css(".app_content"); let div_top_bar_el = $css(".readerTopBar"); if (div_content_el) { div_content_el.cssSet("max-width", numericWidth + "px"); } if (div_top_bar_el) { div_top_bar_el.cssSet("max-width", numericWidth + "px"); } window.localStorage.setItem('setWidth', numericWidth.toString()); let resize_event = new Event("resize"); window.dispatchEvent(resize_event); } // 滑动屏幕,滚至页面底部 async function autoScroll(step, delay) { step = typeof step === "undefined" ? 1 : step; delay = typeof delay === "undefined" ? 1000 : delay; while (scroll_speed > 0) { window.scrollBy(0, step); if (isPageBottom()) { await nextPage(); } await sleep(delay / scroll_speed / scroll_speed); if (isShowInView($css(".readerFooter_ending"))) { scroll_speed = 0; btn_scroll_on.innerHTML = "播放X0"; } } } async function nextPage(sleep_time) { sleep_time = typeof sleep_time === "undefined" ? 6000 : sleep_time; let btn_turn = document.querySelector(".readerFooter_button"); while (true) { if (isShowInView(btn_turn) && scroll_speed > 0) { console.log(`wait ${sleep_time / 1000} seconds. turn page.`); let last_time = sleep_time / 1000; while (last_time > 0) { btn_turn_tips.innerHTML = `${last_time}s翻页`; last_time--; await sleep(1000); } pressKey("right"); await sleep(3000); btn_turn_tips.innerHTML = `等待翻页`; break; } } } /** ------ 常用函数 ------ **/ function initElement(elements) { /** * 初始化元素属性和函数 * * element: elementNode */ if (!Array.isArray(elements)) { elements = [elements]; } for (let element of elements) { for (let fn_name in ElementUtils) { element[fn_name] = (...args) => { return ElementUtils[fn_name](element, ...args); }; } } } function $css(query) { /** * css 选择器,单元素 * * query: string */ let elm = document.querySelector(query); if (!elm) { return; } initElement(elm); return elm; } function $cssAll(query) { /** * css 选择器,单元素 * * query: string */ let elms = Array.apply(null, document.querySelectorAll(query)); if (elms.length === 0) { return; } initElement(elms); return elms; } function $xpa(query, node) { /** * xpath 选择器,单元素 * * query: string * node: elementNode */ node = typeof node === "undefined" ? document : node; let elm = document.evaluate(query, node).iterateNext(); if (!elm) { return; } initElement(elm); return elm; } function $xpaAll(query, node) { /** * xpath 选择器,多元素 * * query: string * node: elementNode */ node = typeof node === "undefined" ? document : node; let elm_list = []; let elm_box = document.evaluate(query, document); let elm = elm_box.iterateNext(); if (!elm) { return; } while (elm) { elm_list.push(elm); elm = elm_box.iterateNext(); } initElement(elm_list); return elm_list; } function sleep(ms) { ms = typeof ms === "undefined" ? 1000 : ms; /** * 休眠函数,单位:ms * 使用方法: * sleep(500).then(() => { Do something after the sleep! }); * 或者 * await sleep(500); Do something after the sleep! */ return new Promise((resolve) => setTimeout(resolve, ms)); } function isShowInView(element) { /** * 判断元素是否出现在视窗中 */ if (element === null || element === undefined) { return false; } let documentHeight = Math.max( document.documentElement.scrollHeight, document.documentElement.offsetHeight, document.documentElement.clientHeight ); let screenHeight = window.innerHeight || documentHeight; let screenWidth = window.innerWidth || document.documentElement.clientWidth; let { top, right, bottom, left } = element.getBoundingClientRect(); return ( top >= 0 && left >= 0 && right <= screenWidth && bottom <= screenHeight ); } function isExistElement(css_selector) { /** * 判断指定元素是否存在页面中 */ if ($css(css_selector)) { return true; } return false; } function createElement(tagName, attributes) { const elm = document.createElement(tagName); for (const attr in attributes) { elm.setAttribute(attr, attributes[attr]); } return elm; } async function waitElement(css_selector, max_wait_ms) { /** * 等待指定元素出现 */ max_wait_ms = typeof max_wait_ms === "undefined" ? 3000 : max_wait_ms; let start_time = new Date().getTime(); let elm = $css(css_selector); while (!elm && start_time + max_wait_ms >= new Date().getTime()) { await sleep(300); elm = $css(css_selector); if (elm) { break; } } return elm; } function getScrollTop() { /** * 滚动条在Y轴上已经滚动的距离 */ return document.documentElement.scrollTop || document.body.scrollTop; } function getScrollHeight() { /** * 整个页面的高度 */ return document.documentElement.scrollHeight || document.body.scrollHeight; } function getWindowHeight() { /** * 浏览器可视窗口高度 */ return document.documentElement.clientHeight || document.body.clientHeight; } function isPageBottom(deviation_value) { /** * 判断页面是否滚动到底,默认允许3个像素的误差,大部分情况下总是会差0.3-0.8像素 */ deviation_value = typeof deviation_value === "undefined" ? 3 : deviation_value; if ( getScrollHeight() - getScrollTop() - getWindowHeight() < deviation_value ) { return true; } return false; } function pressKeyByCode(code) { /** * 按键触发,根据传入的按键编号 * * code: Number */ return document.dispatchEvent( new KeyboardEvent("keydown", { bubbles: true, cancelable: true, keyCode: code, }) ); } function pressKey(name) { /** * 按键触发,根据传入的按键名 * * name: string */ const KeyNameToCode = { back: 8, tab: 9, clear: 12, enter: 13, shift: 16, ctrl: 17, alt: 18, capelock: 20, esc: 27, space: 32, pageup: 33, pagedown: 34, end: 35, home: 36, left: 37, up: 38, right: 39, down: 40, insert: 45, delete: 46, 0: 48, 1: 49, 2: 50, 3: 51, 4: 52, 5: 53, 6: 54, 7: 55, 8: 56, 9: 57, a: 65, b: 66, c: 67, d: 68, e: 69, f: 70, g: 71, h: 72, i: 73, j: 74, k: 75, l: 76, m: 77, n: 78, o: 79, p: 80, q: 81, r: 82, s: 83, t: 84, u: 85, v: 86, w: 87, x: 88, y: 89, z: 90, f1: 112, f2: 113, f3: 114, f4: 115, f5: 116, f6: 117, f7: 118, f8: 119, f9: 120, f10: 121, f11: 122, f12: 123, // 数字小键盘部分 n0: 96, n1: 97, n2: 98, n3: 99, n4: 100, n5: 101, n6: 102, n7: 103, n8: 104, n9: 105, "n*": 106, "n+": 107, nenter: 108, "n-": 109, "n.": 110, "n/": 111, numlock: 144, ";": 186, ":": 186, "=": 187, "+": 187, ",": 188, "<": 188, "-": 189, _: 189, ".": 190, ">": 190, "/": 191, "?": 191, "`": 192, "~": 192, "[": 219, "{": 219, "\\": 220, "|": 220, "]": 221, "}": 221, "'": 222, '"': 222, }; return pressKeyByCode(KeyNameToCode[name.toLowerCase()]); }