Large Type Display

全屏以大号字体显示选中的文本。快捷键(默认Alt+D,可自定义)触发。按Esc或点击蒙版退出。

  1. // ==UserScript==
  2. // @name Large Type Display
  3. // @namespace http://googleyixia.com/
  4. // @version 1.1.3
  5. // @description 全屏以大号字体显示选中的文本。快捷键(默认Alt+D,可自定义)触发。按Esc或点击蒙版退出。
  6. // @author Byron (and AI assistant)
  7. // @match *://*/*
  8. // @icon data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">Aa</text></svg>
  9. // @grant GM_addStyle
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @grant GM_registerMenuCommand
  13. // @run-at document-idle
  14. // @license CC BY-NC-SA 4.0
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. 'use_strict';
  19.  
  20. let overlayElement = null;
  21. let currentShortcutKey = 'D';
  22. const MODIFIER_KEY = 'altKey';
  23.  
  24. const MIN_FONT_SIZE_PX = 42; // <-- 最小字体大小更新为 42px
  25.  
  26. // 样式
  27. GM_addStyle(`
  28. .large-type-overlay {
  29. position: fixed;
  30. top: 0;
  31. left: 0;
  32. width: 100vw;
  33. height: 100vh;
  34. background-color: rgba(0, 0, 0, 0.75);
  35. display: flex;
  36. justify-content: center;
  37. align-items: center;
  38. z-index: 2147483647;
  39. opacity: 0;
  40. visibility: hidden;
  41. transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out;
  42. padding: 20px;
  43. box-sizing: border-box;
  44. }
  45. .large-type-overlay.visible {
  46. opacity: 1;
  47. visibility: visible;
  48. }
  49. .large-type-text {
  50. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  51. color: white;
  52. text-align: center;
  53. line-height: 1.5; /* <-- 行间距从 1.2 增加到 1.5 */
  54. text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
  55. max-width: 90%;
  56. overflow-wrap: break-word;
  57. word-wrap: break-word;
  58. overflow-y: auto;
  59. max-height: calc(100vh - 80px);
  60. }
  61. .large-type-text span {
  62. word-break: break-all;
  63. }
  64. `);
  65.  
  66. function getSelectedText() {
  67. let text = "";
  68. if (window.getSelection) {
  69. text = window.getSelection().toString();
  70. } else if (document.selection && document.selection.type != "Control") {
  71. text = document.selection.createRange().text;
  72. }
  73. return text.trim();
  74. }
  75.  
  76. function createOverlay() {
  77. overlayElement = document.createElement('div');
  78. overlayElement.id = 'large-type-overlay-container';
  79. overlayElement.className = 'large-type-overlay';
  80.  
  81. const textElement = document.createElement('div');
  82. textElement.className = 'large-type-text';
  83. textElement.id = 'large-type-content';
  84.  
  85. overlayElement.appendChild(textElement);
  86. document.body.appendChild(overlayElement);
  87.  
  88. overlayElement.addEventListener('click', function(event) {
  89. if (event.target === overlayElement) {
  90. hideLargeType();
  91. }
  92. });
  93. }
  94.  
  95. function showLargeType(textToShow) {
  96. const selectedText = textToShow;
  97. if (!selectedText) {
  98. return;
  99. }
  100.  
  101. if (!overlayElement) {
  102. createOverlay();
  103. }
  104.  
  105. const textContentElement = overlayElement.querySelector('#large-type-content');
  106. textContentElement.innerHTML = '';
  107. const span = document.createElement('span');
  108. span.textContent = selectedText;
  109. textContentElement.appendChild(span);
  110.  
  111. const viewportWidth = window.innerWidth;
  112. const viewportHeight = window.innerHeight;
  113. const textLength = selectedText.length;
  114.  
  115. const overlayPaddingTotal = 40;
  116. const textContainerMaxWidth = (viewportWidth - overlayPaddingTotal) * 0.9;
  117. const textContainerMaxHeight = viewportHeight - overlayPaddingTotal;
  118. const targetRenderWidth = textContainerMaxWidth * 0.98;
  119. const targetRenderHeight = textContainerMaxHeight * 0.95;
  120.  
  121. let fontSizePx;
  122.  
  123. if (textLength <= 15) {
  124. let initialSizeByHeight = targetRenderHeight * 0.7;
  125. if (textLength === 1) initialSizeByHeight = targetRenderHeight * 0.8;
  126. else if (textLength <= 3) initialSizeByHeight = targetRenderHeight * 0.75;
  127. let initialSizeByWidth = targetRenderWidth / (Math.max(1, textLength) * 0.55);
  128. fontSizePx = Math.min(initialSizeByHeight, initialSizeByWidth);
  129. } else {
  130. let initialVwEquivalent = 8; // 对于较长文本,此基准可能需要配合新的最小字号进行观察调整
  131. if (textLength < 30) initialVwEquivalent = 10;
  132. else if (textLength < 60) initialVwEquivalent = 8;
  133. else if (textLength < 100) initialVwEquivalent = 7;
  134. else initialVwEquivalent = 6;
  135. fontSizePx = (initialVwEquivalent / 100) * viewportWidth;
  136. }
  137.  
  138. fontSizePx = Math.max(MIN_FONT_SIZE_PX, Math.min(fontSizePx, viewportHeight * 0.85));
  139. textContentElement.style.fontSize = fontSizePx + 'px';
  140.  
  141. let loops = 0;
  142. const maxLoops = 100;
  143. while (loops < maxLoops && (textContentElement.scrollWidth > targetRenderWidth || textContentElement.scrollHeight > targetRenderHeight)) {
  144. fontSizePx -= Math.max(1, fontSizePx * 0.05);
  145. if (fontSizePx < MIN_FONT_SIZE_PX) {
  146. fontSizePx = MIN_FONT_SIZE_PX;
  147. textContentElement.style.fontSize = fontSizePx + 'px';
  148. break;
  149. }
  150. textContentElement.style.fontSize = fontSizePx + 'px';
  151. loops++;
  152. }
  153.  
  154. overlayElement.classList.add('visible');
  155. document.addEventListener('keydown', handleEscapeKey);
  156. }
  157.  
  158. function hideLargeType() {
  159. if (overlayElement) {
  160. if (overlayElement.classList.contains('visible')) {
  161. overlayElement.classList.remove('visible');
  162. setTimeout(() => {
  163. if (overlayElement) {
  164. overlayElement.remove();
  165. overlayElement = null;
  166. }
  167. }, 200);
  168. } else {
  169. overlayElement.remove();
  170. overlayElement = null;
  171. }
  172. document.removeEventListener('keydown', handleEscapeKey);
  173. }
  174. }
  175.  
  176. function handleEscapeKey(event) {
  177. if (event.key === "Escape") {
  178. hideLargeType();
  179. }
  180. }
  181.  
  182. function handleShortcut(event) {
  183. if (event[MODIFIER_KEY] && event.code === 'Key' + currentShortcutKey.toUpperCase() && !event.ctrlKey && !event.shiftKey && !event.metaKey) {
  184. event.preventDefault();
  185. event.stopImmediatePropagation();
  186.  
  187. const selectedText = getSelectedText();
  188. if (selectedText) {
  189. showLargeType(selectedText);
  190. }
  191. }
  192. }
  193.  
  194. async function loadShortcut() {
  195. currentShortcutKey = await GM_getValue('largeTypeShortcutKey', 'D');
  196. }
  197.  
  198. async function saveShortcut(key) {
  199. await GM_setValue('largeTypeShortcutKey', key);
  200. currentShortcutKey = key;
  201. alert(`快捷键已设置为: Alt + ${currentShortcutKey.toUpperCase()}`);
  202. }
  203.  
  204. function changeShortcut() {
  205. const newKey = prompt(`请输入新的快捷键字母 (例如 D, L, M),当前为 Alt + ${currentShortcutKey.toUpperCase()}:`, currentShortcutKey);
  206. if (newKey && newKey.trim().length === 1 && /^[a-zA-Z]$/.test(newKey.trim())) {
  207. saveShortcut(newKey.trim().toUpperCase());
  208. } else if (newKey !== null) {
  209. alert("无效的按键。请输入单个字母。");
  210. }
  211. }
  212.  
  213. async function init() {
  214. await loadShortcut();
  215. document.addEventListener('keydown', handleShortcut, true);
  216. GM_registerMenuCommand("设置 大号文本显示 快捷键", changeShortcut, "L");
  217. }
  218.  
  219. init();
  220.  
  221. })();