微信读书

修改背景颜色、字体颜色,阅读时宽屏,隐藏按钮和菜单

  1. // ==UserScript==
  2. // @name 微信读书
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.50
  5. // @description 修改背景颜色、字体颜色,阅读时宽屏,隐藏按钮和菜单
  6. // @author Oscar
  7. // @match https://weread.qq.com/web/reader/*
  8. // @license AGPL License
  9. // @grant GM_addStyle
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // ==/UserScript==
  13.  
  14. (() => {
  15. /* 1. 预设与持久化 */
  16. const BG = ['#FFFFFF', '#F9F3E3', '#E8F5E9', '#000000'];
  17. const FG = ['#FFFFFF', '#0A1F44', '#39FF14'];
  18. let bg = GM_getValue('wr_bgColor') || BG[0];
  19. let fg = GM_getValue('wr_fontColor') || FG[1];
  20. let useDefaultFg = GM_getValue('wr_useDefaultFg') || false;
  21.  
  22. /* 2. 颜色变量样式 */
  23. let styleRules = `
  24. :root{--wr-bg:${bg};${!useDefaultFg ? `--wr-fg:${fg}` : ''}}
  25. .wr_whiteTheme .readerContent .app_content,
  26. .wr_whiteTheme .wr_horizontalReader .readerChapterContent,
  27. .wr_whiteTheme .wr_horizontalReader .readerControls_fontSize {
  28. background: var(--wr-bg) !important;
  29. ${!useDefaultFg ? 'color: var(--wr-fg) !important;' : ''}
  30. }
  31.  
  32. .wr_whiteTheme .readerContent .app_content * ,
  33. .wr_whiteTheme .wr_horizontalReader .readerChapterContent * ,
  34. .wr_whiteTheme .wr_horizontalReader .readerControls_fontSize * {
  35. ${!useDefaultFg ? 'color: var(--wr-fg) !important;' : ''}
  36. }
  37. `;
  38.  
  39. GM_addStyle(styleRules);
  40.  
  41. /* 3. 固定样式 */
  42. GM_addStyle(`
  43. #tm{font:14px sans-serif;color:#333;line-height:1.4}
  44. #tm .ttl{margin:6px 0 4px;font-weight:600}
  45. #tm .row{display:flex;align-items:center;gap:8px;margin-bottom:6px}
  46. #tm .sw{width:24px;height:24px;border:1px solid #aaa;cursor:pointer}
  47. #tm .sw:hover{outline:2px solid #888}
  48. #tm input[type=color]{width:24px;height:24px;border:1px solid #aaa;padding:0;cursor:pointer}
  49. #tm .ok{margin-left:auto;padding:2px 12px;font-size:13px;cursor:pointer;border-radius:4px;border:1px solid #3e8ef7;background:#3e8ef7;color:#fff;transition:.15s}
  50. #tm .ok:hover{background:#66a9ff}
  51. #tm .ok:active{background:#2e7be6}
  52. .cp .icon{font-size:28px;display:flex;justify-content:center;align-items:center;width:100%;height:100%}
  53. img.wr_readerImage_opacity{opacity:.8!important}
  54. .readerControls{margin-left:calc(50% - 60px)!important}
  55. .app_content,.readerTopBar{max-width:100%!important}
  56. .readerControls,.readerTopBar{opacity:0;transition:opacity .2s}
  57. .readerControls:hover,.readerTopBar:hover,.readerControls.tm-show{opacity:1!important}
  58. .readerCatalog,.readerAIChatPanel,.readerNotePanel{left:unset!important;right:0!important}
  59. /* 双栏阅读模式 */
  60. .wr_horizontalReader .wr_horizontalReader_app_content,
  61. .wr_horizontalReader .readerChapterContent_container,
  62. .wr_horizontalReader .readerChapterContent,
  63. .wr_horizontalReader .renderTargetContainer,
  64. .wr_horizontalReader .wr_canvasContainer {
  65. width: 100vw !important;
  66. max-width: 100vw !important;
  67. margin: 0 auto !important;
  68. padding: 0 !important;
  69. position: static !important;
  70. }
  71.  
  72. .wr_horizontalReader .readerChapterContent,
  73. .wr_horizontalReader .readerChapterContent_container {min-height: 100vh !important;}
  74. .wr_horizontalReader .readerControls {position: fixed !important; right: 16px !important;}
  75. `);
  76.  
  77. /* 4. 轮询等待底栏 */
  78. let attempts = 0;
  79. const iv = setInterval(() => {
  80. const ctl = document.querySelector('.readerControls');
  81. if (ctl) {
  82. clearInterval(iv);
  83. try { init(ctl); } catch (err) { console.error('[取色面板] 初始化失败:', err); }
  84. } else if (++attempts > 15) {
  85. clearInterval(iv);
  86. }
  87. }, 200);
  88.  
  89. /* 5. 初始化面板 */
  90. function init(controls) {
  91. const btn = document.createElement('button');
  92. btn.className = 'readerControls_item cp';
  93. btn.title = '取色';
  94. btn.innerHTML = '<span class="icon">🎨</span>';
  95. controls.prepend(btn);
  96.  
  97. const panel = document.createElement('div');
  98. panel.id = 'tm';
  99. panel.style.cssText = 'display:none;position:fixed;width:220px;z-index:9999;background:#fff;border:1px solid #ccc;border-radius:6px;padding:6px';
  100. panel.innerHTML = `
  101. <div class="ttl">背景色</div>
  102. <div class="row">
  103. ${BG.map(c => `<div class="sw b" data-c="${c}" style="background:${c}"></div>`).join('')}
  104. <label style="display:flex;align-items:center;gap:4px">自定义:<input type="color" class="cb" value="${bg}"></label>
  105. </div>
  106. <div class="ttl">字体色</div>
  107. <div class="row">
  108. <div class="sw f default" data-default="true" title="默认字体色" style="background:repeating-conic-gradient(#ccc 0% 25%, transparent 0% 50%) 50% / 10px 10px"></div>
  109. ${FG.map(c => `<div class="sw f" data-c="${c}" style="background:${c}"></div>`).join('')}
  110. <label style="display:flex;align-items:center;gap:4px">自定义:<input type="color" class="cf" value="${fg}"></label>
  111. </div>
  112. <div class="row"><button class="ok">应用</button></div>`;
  113. document.body.append(panel);
  114.  
  115. const cBg = panel.querySelector('.cb');
  116. const cFg = panel.querySelector('.cf');
  117.  
  118. const show = () => { locate(); panel.style.display = 'block'; controls.classList.add('tm-show'); };
  119. const hide = () => { panel.style.display = 'none'; controls.classList.remove('tm-show'); };
  120. btn.onclick = e => { e.stopPropagation(); panel.style.display === 'block' ? hide() : show(); };
  121. document.addEventListener('click', e => {
  122. if (panel.style.display === 'block' && !panel.contains(e.target) && !btn.contains(e.target)) hide();
  123. }, true);
  124.  
  125. panel.addEventListener('click', e => {
  126. const sw = e.target.closest('.sw');
  127. if (sw) {
  128. if (sw.classList.contains('b')) {
  129. bg = sw.dataset.c;
  130. cBg.value = bg;
  131. } else {
  132. if (sw.dataset.default) {
  133. useDefaultFg = true;
  134. fg = '';
  135. } else {
  136. useDefaultFg = false;
  137. fg = sw.dataset.c;
  138. cFg.value = fg;
  139. }
  140. }
  141. return;
  142. }
  143. if (e.target.classList.contains('ok')) {
  144. GM_setValue('wr_bgColor', bg);
  145. GM_setValue('wr_fontColor', fg);
  146. GM_setValue('wr_useDefaultFg', useDefaultFg);
  147.  
  148. document.documentElement.style.setProperty('--wr-bg', bg);
  149. if (!useDefaultFg) {
  150. document.documentElement.style.setProperty('--wr-fg', fg);
  151. } else {
  152. document.documentElement.style.removeProperty('--wr-fg');
  153. }
  154. hide();
  155. }
  156. });
  157.  
  158. cBg.oninput = e => { bg = e.target.value; };
  159. cFg.oninput = e => { fg = e.target.value; useDefaultFg = false; };
  160.  
  161. function locate() {
  162. const { left, top } = btn.getBoundingClientRect();
  163. panel.style.left = (left - 250) + 'px';
  164. panel.style.top = top + 'px';
  165. }
  166. }
  167. })();