// ==UserScript== // @name 选中文本|辅助阅读 // @namespace http://tampermonkey.net/ // @version 1.0.1 // @description 辅助阅读:鼠标或键盘选中文案后,在页面底部显示放大的高对比度字幕,方便快速阅读与复习。支持滚动查看超出内容、右上角按钮与 Esc 关闭、不重复创建容器,整体风格参考酷狗歌词霓虹样式。 // @author zhyp // @match *://*/* // @run-at document-idle // @grant none // @license MIT // ==/UserScript== (function () { 'use strict'; // 配置:可按需调整 const CONFIG = { heightVH: 33, // 底部区域高度占视窗的百分比(vh) widthVW: 92, // 宽度占视窗百分比(vw) maxWidthPx: 1200, // 最大宽度(像素) fontSizeMin: 20, // 最小字体大小(像素) fontSizeMax: 36, // 最大字体大小(像素) lineHeight: 1.6, backdropBlur: 6, // 毛玻璃强度(px) bgOpacity: 0.45, // 背景透明度 zIndex: 2147483646 // 尽量置顶但保留给系统/扩展一位 }; let overlay, content, closeBtn; let isOpen = false; let lastText = ''; function ensureOverlay() { if (overlay && document.body.contains(overlay)) return overlay; overlay = document.createElement('div'); overlay.id = 'tm-selection-subtitle-overlay'; overlay.setAttribute('role', 'dialog'); overlay.setAttribute('aria-label', '选中文本字幕'); const widthStyle = `width: min(${CONFIG.widthVW}vw, ${CONFIG.maxWidthPx}px);`; const heightStyle = `height: ${CONFIG.heightVH}vh;`; overlay.style.cssText = ` position: fixed; left: 50%; bottom: 10px; transform: translateX(-50%); ${widthStyle} ${heightStyle} padding: 14px 16px 16px 16px; box-sizing: border-box; background: rgba(0,0,0,${CONFIG.bgOpacity}); backdrop-filter: blur(${CONFIG.backdropBlur}px); -webkit-backdrop-filter: blur(${CONFIG.backdropBlur}px); border-radius: 14px; box-shadow: 0 12px 40px rgba(0,0,0,0.35); z-index: ${CONFIG.zIndex}; display: none; opacity: 0; transition: opacity 160ms ease-in-out; `; // 关闭按钮 closeBtn = document.createElement('button'); closeBtn.type = 'button'; closeBtn.textContent = '×'; closeBtn.title = '关闭字幕'; closeBtn.style.cssText = ` position: absolute; top: 8px; right: 10px; height: 28px; width: 28px; display: inline-flex; align-items: center; justify-content: center; border-radius: 8px; border: 1px solid rgba(255,255,255,0.25); background: rgba(255,255,255,0.12); color: #fff; font-size: 18px; line-height: 1; cursor: pointer; text-shadow: 0 0 6px rgba(255,255,255,0.35); box-shadow: 0 2px 8px rgba(0,0,0,0.2); `; closeBtn.addEventListener('click', hideOverlay); closeBtn.addEventListener('mouseenter', () => { closeBtn.style.background = 'rgba(255,255,255,0.18)'; }); closeBtn.addEventListener('mouseleave', () => { closeBtn.style.background = 'rgba(255,255,255,0.12)'; }); // 内容容器(滚动区域) content = document.createElement('div'); content.id = 'tm-selection-subtitle-content'; content.style.cssText = ` position: relative; height: 100%; overflow-y: auto; overflow-x: hidden; padding-right: 10px; `; // 字幕样式(酷狗歌词风格:青蓝霓虹) const lyric = document.createElement('div'); lyric.id = 'tm-selection-subtitle-text'; lyric.style.cssText = ` white-space: pre-wrap; word-break: break-word; color: #a7fffb; font-weight: 600; letter-spacing: 0.02em; font-size: clamp(${CONFIG.fontSizeMin}px, 2.6vw, ${CONFIG.fontSizeMax}px); line-height: ${CONFIG.lineHeight}; text-align: center; text-shadow: 0 0 6px rgba(0, 255, 255, 0.80), 0 0 12px rgba(0, 150, 255, 0.70), 0 0 24px rgba(0, 120, 255, 0.50); -webkit-text-stroke: 0.6px rgba(255,255,255,0.25); padding: 8px 6px; `; content.appendChild(lyric); overlay.appendChild(content); overlay.appendChild(closeBtn); document.documentElement.appendChild(overlay); // 防止页面滚动事件影响,鼠标滚动只控制字幕 overlay.addEventListener('wheel', (e) => { // 在字幕区域内滚动,不让事件冒泡到页面 e.stopPropagation(); }, { passive: true }); // 点击字幕区域不会丢失页面选择状态(但我们主动展示,不依赖持续选择) overlay.addEventListener('mousedown', (e) => { e.stopPropagation(); }); return overlay; } function showOverlay(text) { const lyric = document.getElementById('tm-selection-subtitle-text'); if (!lyric) return; // 已打开时,仅更新内容,避免重复“打开/动画” if (isOpen) { const newText = (text || '').trim(); if (newText.length > 0 && newText !== lastText) { lyric.textContent = newText; content.scrollTop = 0; // 更新时回到顶部,可按需移除 lastText = newText; } return; } const newText = (text || '').trim(); if (!newText) return; lyric.textContent = newText; overlay.style.display = 'block'; content.scrollTop = 0; requestAnimationFrame(() => { overlay.style.opacity = '1'; }); isOpen = true; lastText = newText; } function hideOverlay() { if (!overlay) return; overlay.style.opacity = '0'; setTimeout(() => { if (overlay) overlay.style.display = 'none'; isOpen = false; lastText = ''; }, 160); } function getSelectedText() { const sel = window.getSelection ? window.getSelection() : null; let text = ''; // 优先获取页面中选区文本(含 contentEditable) if (sel && !sel.isCollapsed) { text = sel.toString(); } // 若为空,再尝试获取输入框/文本域中的选区(不处理密码框) if (!text || text.trim().length === 0) { const el = document.activeElement; const isTextarea = el && el.tagName === 'TEXTAREA'; const isTextInput = el && el.tagName === 'INPUT' && (el.type || 'text').toLowerCase() !== 'password'; if ((isTextarea || isTextInput) && typeof el.selectionStart === 'number' && typeof el.selectionEnd === 'number') { const start = el.selectionStart; const end = el.selectionEnd; if (end > start) { text = el.value.substring(start, end); } } } text = (text || '').trim(); // 过滤过短/纯空白选择 if (!text || text.replace(/\s+/g, '').length === 0) return ''; return text; } function onMouseUp () { const text = getSelectedText(); if (text) { showOverlay(text); } } function onKeyUp(e) { // Esc 关闭 if (e.key === 'Escape') { hideOverlay(); return; } // 键盘选择完成(如 Shift+方向键)后展示 const text = getSelectedText(); if (text) { showOverlay(text); } } // 初始化监听 function init() { ensureOverlay(); document.addEventListener('mouseup', onMouseUp, true); document.addEventListener('keyup', onKeyUp, true); document.addEventListener('selectionchange', () => { const sel = window.getSelection(); if (sel && sel.isCollapsed) hideOverlay(); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init, { once: true }); } else { init(); } })();