🏠 Home 

最美字迹放大镜

在鼠标划过文本时显示悬浮放大的效果,带有亚克力模糊背景。提供更多个性化设置选项。

  1. // ==UserScript==
  2. // @name 最美字迹放大镜
  3. // @version 1.0
  4. // @description 在鼠标划过文本时显示悬浮放大的效果,带有亚克力模糊背景。提供更多个性化设置选项。
  5. // @author hiisme
  6. // @match *://*/*
  7. // @grant GM_registerMenuCommand
  8. // @grant GM_setValue
  9. // @grant GM_getValue
  10. // @namespace https://greasyfork.org/users/217852
  11. // ==/UserScript==
  12. (async () => {
  13. 'use strict';
  14. // 默认设置
  15. const defaultSettings = {
  16. fontSize: GM_getValue('fontSize', '24px'),
  17. textColor: GM_getValue('textColor', '#000000'),
  18. borderColor: GM_getValue('borderColor', '#ccc'),
  19. transitionDuration: GM_getValue('transitionDuration', '0.3s'),
  20. acrylicBlur: GM_getValue('acrylicBlur', true),
  21. noBorder: GM_getValue('noBorder', false),
  22. textOpacity: GM_getValue('textOpacity', 1),
  23. backgroundOpacity: GM_getValue('backgroundOpacity', 0.2),
  24. acrylicStrength: GM_getValue('acrylicStrength', 10),
  25. fontWeight: GM_getValue('fontWeight', 'normal')
  26. };
  27. let settings = { ...defaultSettings };
  28. // 创建悬浮放大的文本容器
  29. const zoomBox = document.createElement('div');
  30. Object.assign(zoomBox.style, {
  31. position: 'absolute',
  32. padding: '5px',
  33. color: settings.textColor,
  34. border: settings.noBorder ? 'none' : `1px solid ${settings.borderColor}`,
  35. borderRadius: '5px',
  36. boxShadow: '0 0 10px rgba(0, 0, 0, 0.2)',
  37. pointerEvents: 'none',
  38. zIndex: '10000',
  39. display: 'none',
  40. fontSize: settings.fontSize,
  41. fontWeight: settings.fontWeight,
  42. transition: `transform ${settings.transitionDuration} ease-out, opacity ${settings.transitionDuration} ease-out`,
  43. transform: 'scale(0.9)',
  44. opacity: settings.textOpacity,
  45. backgroundColor: `rgba(255, 255, 255, ${settings.backgroundOpacity})`,
  46. backdropFilter: settings.acrylicBlur ? `blur(${settings.acrylicStrength}px)` : 'none',
  47. webkitBackdropFilter: settings.acrylicBlur ? `blur(${settings.acrylicStrength}px)` : 'none'
  48. });
  49. document.body.appendChild(zoomBox);
  50. let currentElement = null; // 当前的元素引用
  51. let currentLineIndex = -1; // 当前显示的行索引
  52. // 获取元素的每一行文本内容
  53. const getTextLinesFromElement = (element) => {
  54. return Array.from(element.childNodes)
  55. .filter(node => node.nodeType === Node.TEXT_NODE || (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'br'))
  56. .reduce((acc, node) => {
  57. if (node.nodeType === Node.TEXT_NODE) {
  58. acc.push(node.textContent.trim());
  59. } else if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'br') {
  60. acc.push('\n');
  61. }
  62. return acc;
  63. }, [])
  64. .filter(line => line.trim().length > 0);
  65. };
  66. // 显示放大的文本
  67. const showZoomBox = (lines, index, x, y) => {
  68. if (index < 0 || index >= lines.length) return;
  69. zoomBox.textContent = lines[index];
  70. zoomBox.style.left = `${x + 10}px`;
  71. zoomBox.style.top = `${y + 10}px`;
  72. zoomBox.style.display = 'block';
  73. requestAnimationFrame(() => {
  74. zoomBox.style.transform = 'scale(1)';
  75. zoomBox.style.opacity = settings.textOpacity;
  76. });
  77. };
  78. // 隐藏放大的文本
  79. const hideZoomBox = () => {
  80. zoomBox.style.transform = 'scale(0.9)';
  81. zoomBox.style.opacity = '0';
  82. zoomBox.addEventListener('transitionend', () => {
  83. if (currentElement === null) {
  84. zoomBox.style.display = 'none';
  85. }
  86. }, { once: true });
  87. };
  88. // 事件处理:被动事件监听器
  89. document.body.addEventListener('mouseover', (e) => {
  90. const lines = getTextLinesFromElement(e.target);
  91. if (lines.length > 0 && e.target !== currentElement) {
  92. currentElement = e.target;
  93. currentLineIndex = 0;
  94. showZoomBox(lines, currentLineIndex, e.pageX, e.pageY);
  95. }
  96. }, { passive: true });
  97. document.body.addEventListener('mousemove', (e) => {
  98. if (currentElement) {
  99. zoomBox.style.left = `${e.pageX + 10}px`;
  100. zoomBox.style.top = `${e.pageY + 10}px`;
  101. const lines = getTextLinesFromElement(currentElement);
  102. const index = Math.floor((e.clientY - currentElement.getBoundingClientRect().top) / 20); // 根据行高计算行索引
  103. if (index >= 0 && index < lines.length && index !== currentLineIndex) {
  104. currentLineIndex = index;
  105. showZoomBox(lines, currentLineIndex, e.pageX, e.pageY);
  106. }
  107. }
  108. }, { passive: true });
  109. document.body.addEventListener('mouseout', (e) => {
  110. if (e.target === currentElement) {
  111. currentElement = null;
  112. currentLineIndex = -1;
  113. hideZoomBox();
  114. }
  115. }, { passive: true });
  116. // 注册菜单命令,用于调整设置
  117. const registerMenuCommands = () => {
  118. GM_registerMenuCommand('设置字体大小', async () => {
  119. const fontSize = prompt('请输入字体大小 (例如: 24px):', settings.fontSize);
  120. if (fontSize) {
  121. settings.fontSize = fontSize;
  122. GM_setValue('fontSize', fontSize);
  123. zoomBox.style.fontSize = fontSize;
  124. }
  125. });
  126. GM_registerMenuCommand('设置文字颜色', async () => {
  127. const textColor = prompt('请输入文字颜色 (例如: #000000):', settings.textColor);
  128. if (textColor) {
  129. settings.textColor = textColor;
  130. GM_setValue('textColor', textColor);
  131. zoomBox.style.color = textColor;
  132. }
  133. });
  134. GM_registerMenuCommand('设置边框颜色', async () => {
  135. const borderColor = prompt('请输入边框颜色 (例如: #ccc):', settings.borderColor);
  136. if (borderColor) {
  137. settings.borderColor = borderColor;
  138. GM_setValue('borderColor', borderColor);
  139. zoomBox.style.border = settings.noBorder ? 'none' : `1px solid ${borderColor}`;
  140. }
  141. });
  142. GM_registerMenuCommand('切换边框', async () => {
  143. settings.noBorder = !settings.noBorder;
  144. GM_setValue('noBorder', settings.noBorder);
  145. zoomBox.style.border = settings.noBorder ? 'none' : `1px solid ${settings.borderColor}`;
  146. alert(`边框已${settings.noBorder ? '隐藏' : '显示'}`);
  147. });
  148. GM_registerMenuCommand('设置文本不透明度', async () => {
  149. const textOpacity = parseFloat(prompt('请输入文本不透明度 (0 到 1):', settings.textOpacity));
  150. if (textOpacity >= 0 && textOpacity <= 1) {
  151. settings.textOpacity = textOpacity;
  152. GM_setValue('textOpacity', textOpacity);
  153. zoomBox.style.opacity = textOpacity;
  154. }
  155. });
  156. GM_registerMenuCommand('设置背景不透明度', async () => {
  157. const backgroundOpacity = parseFloat(prompt('请输入背景不透明度 (0 到 1):', settings.backgroundOpacity));
  158. if (backgroundOpacity >= 0 && backgroundOpacity <= 1) {
  159. settings.backgroundOpacity = backgroundOpacity;
  160. GM_setValue('backgroundOpacity', backgroundOpacity);
  161. zoomBox.style.backgroundColor = `rgba(255, 255, 255, ${backgroundOpacity})`;
  162. }
  163. });
  164. GM_registerMenuCommand('设置亚克力模糊强度', async () => {
  165. const acrylicStrength = parseInt(prompt('请输入亚克力模糊强度 (像素):', settings.acrylicStrength), 10);
  166. if (acrylicStrength >= 0) {
  167. settings.acrylicStrength = acrylicStrength;
  168. GM_setValue('acrylicStrength', acrylicStrength);
  169. zoomBox.style.backdropFilter = settings.acrylicBlur ? `blur(${acrylicStrength}px)` : 'none';
  170. zoomBox.style.webkitBackdropFilter = settings.acrylicBlur ? `blur(${acrylicStrength}px)` : 'none';
  171. }
  172. });
  173. GM_registerMenuCommand('切换亚克力模糊', async () => {
  174. settings.acrylicBlur = !settings.acrylicBlur;
  175. GM_setValue('acrylicBlur', settings.acrylicBlur);
  176. zoomBox.style.backdropFilter = settings.acrylicBlur ? `blur(${settings.acrylicStrength}px)` : 'none';
  177. zoomBox.style.webkitBackdropFilter = settings.acrylicBlur ? `blur(${settings.acrylicStrength}px)` : 'none';
  178. alert(`亚克力模糊已${settings.acrylicBlur ? '启用' : '禁用'}`);
  179. });
  180. GM_registerMenuCommand('设置过渡时间', async () => {
  181. const transitionDuration = prompt('请输入过渡时间 (例如: 0.3s):', settings.transitionDuration);
  182. if (transitionDuration) {
  183. settings.transitionDuration = transitionDuration;
  184. GM_setValue('transitionDuration', transitionDuration);
  185. zoomBox.style.transition = `transform ${transitionDuration} ease-out, opacity ${transitionDuration} ease-out`;
  186. }
  187. });
  188. GM_registerMenuCommand('设置字体粗细', async () => {
  189. const fontWeight = prompt('请输入字体粗细 (例如: normal, bold):', settings.fontWeight);
  190. if (fontWeight) {
  191. settings.fontWeight = fontWeight;
  192. GM_setValue('fontWeight', fontWeight);
  193. zoomBox.style.fontWeight = fontWeight;
  194. }
  195. });
  196. };
  197. registerMenuCommands();
  198. })();