Greasy Fork

Greasy Fork is available in English.

Large Type Display

全屏以大号字体显示选中的文本,类似Mac的PopClip Large Type插件功能。按Alt+D (可自定义) 触发,按Esc或点击蒙版退出。

当前为 2025-06-05 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Large Type Display
// @namespace    http://googleyixia.com/
// @version      1.0
// @description  全屏以大号字体显示选中的文本,类似Mac的PopClip Large Type插件功能。按Alt+D (可自定义) 触发,按Esc或点击蒙版退出。
// @author       Byron (and AI assistant)
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @run-at       document-idle
// @license      CC BY-NC-SA 4.0
// ==/UserScript==

(function() {
    'use_strict';

    let overlayElement = null;
    let currentShortcutKey = 'D'; // 默认快捷键的字母部分
    const MODIFIER_KEY = 'altKey'; // 'altKey', 'ctrlKey', 'shiftKey', 'metaKey'

    // 样式
    GM_addStyle(`
        .large-type-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            background-color: rgba(0, 0, 0, 0.75); /* 灰色透明蒙版 */
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 99999999; /* 确保在最顶层 */
            opacity: 0;
            visibility: hidden;
            transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out;
            padding: 20px; /* 重要的内边距,计算可用空间时需要考虑 */
            box-sizing: border-box;
        }
        .large-type-overlay.visible {
            opacity: 1;
            visibility: visible;
        }
        .large-type-text {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
            /* font-size is now set by JavaScript */
            color: white;
            text-align: center;
            line-height: 1.2; /* 重要,用于高度计算 */
            text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
            max-width: 90%; /* 约束文本容器的宽度 */
            overflow-wrap: break-word;
            word-wrap: break-word; /* 旧版浏览器兼容 */
        }
        /* 针对特别长的单词,强制换行 */
        .large-type-text span {
             word-break: break-all; /* 对于无空格长单词仍然有用 */
        }

        /* 媒体查询中关于 font-size 的部分已被移除,因为JS会处理动态字体大小
        如果需要其他针对小屏幕的非字体大小的样式调整,可以保留或添加在这里
        */
    `);

    function getSelectedText() {
        let text = "";
        if (window.getSelection) {
            text = window.getSelection().toString();
        } else if (document.selection && document.selection.type != "Control") { // IE < 9
            text = document.selection.createRange().text;
        }
        return text.trim();
    }

    function createOverlay() {
        if (document.getElementById('large-type-overlay-container')) {
            overlayElement = document.getElementById('large-type-overlay-container');
            return;
        }

        overlayElement = document.createElement('div');
        overlayElement.id = 'large-type-overlay-container';
        overlayElement.className = 'large-type-overlay';

        const textElement = document.createElement('div');
        textElement.className = 'large-type-text';
        textElement.id = 'large-type-content';

        overlayElement.appendChild(textElement);
        document.body.appendChild(overlayElement);

        overlayElement.addEventListener('click', hideLargeType);
    }

    function showLargeType() {
        const selectedText = getSelectedText();
        if (!selectedText) {
            console.log("Large Type: No text selected.");
            return;
        }

        if (!overlayElement) {
            createOverlay();
        }

        const textContentElement = overlayElement.querySelector('#large-type-content');
        // 清空容器, 然后创建新的span来包裹文本, 以确保scrollWidth/scrollHeight的测量准确性
        textContentElement.innerHTML = '';
        const span = document.createElement('span');
        span.textContent = selectedText;
        textContentElement.appendChild(span);

        const viewportWidth = window.innerWidth;
        const viewportHeight = window.innerHeight;
        const textLength = selectedText.length;

        // 计算文本可以占据的目标渲染宽度和高度
        const overlayPaddingTotal = 40; // overlay CSS中定义的 padding: 20px (左右各20)

        // .large-type-text 容器的最大宽度是其父元素(overlay内容区)的90%
        // overlay内容区的宽度是 viewportWidth - overlayPaddingTotal
        const textContainerMaxWidth = (viewportWidth - overlayPaddingTotal) * 0.9;
        // overlay内容区的高度是 viewportHeight - overlayPaddingTotal
        const textContainerMaxHeight = viewportHeight - overlayPaddingTotal;

        // 我们希望实际渲染的文本在上述容器内再小一点,留出视觉上的空白
        const targetRenderWidth = textContainerMaxWidth * 0.98; // 给左右留一点点空隙
        const targetRenderHeight = textContainerMaxHeight * 0.95; // 给上下多留一点空隙

        let fontSizePx; // 将以像素为单位计算和设置字体大小

        // 根据文本长度决定一个初始的、可能比较大的字体大小(像素)
        if (textLength <= 15) { // 短文本 (例如 <= 15 字符),尝试让它非常大
            let initialSizeByHeight = targetRenderHeight * 0.7; // 初步估算基于高度
            if (textLength === 1) initialSizeByHeight = targetRenderHeight * 0.8; // 单字符可以更高
            else if (textLength <= 3) initialSizeByHeight = targetRenderHeight * 0.75;

            // 初步估算基于宽度 (假设平均字符宽度是字体大小的0.55倍 - 这只是一个粗略值)
            let initialSizeByWidth = targetRenderWidth / (Math.max(1, textLength) * 0.55);

            fontSizePx = Math.min(initialSizeByHeight, initialSizeByWidth);
        } else { // 较长文本
            let initialVwEquivalent = 8; // 默认类似8vw
            if (textLength < 30) initialVwEquivalent = 10;
            else if (textLength < 60) initialVwEquivalent = 8;
            else if (textLength < 100) initialVwEquivalent = 6;
            else initialVwEquivalent = 5; // 非常长的文本使用更小的基础字号
            fontSizePx = (initialVwEquivalent / 100) * viewportWidth;
        }

        // 确保初始字体大小在合理范围内
        fontSizePx = Math.max(10, Math.min(fontSizePx, viewportHeight * 0.85)); // 最小10px, 最大不超过视口高度的85%
        textContentElement.style.fontSize = fontSizePx + 'px';

        // 迭代缩小字体直到适应目标区域
        let loops = 0;
        const maxLoops = 100; // 防止死循环

        while (loops < maxLoops && (textContentElement.scrollWidth > targetRenderWidth || textContentElement.scrollHeight > targetRenderHeight)) {
            fontSizePx -= Math.max(1, fontSizePx * 0.05); // 每次减小5%或至少1px
            if (fontSizePx < 10) { // 设置一个最小字体大小 (例如10px)
                fontSizePx = 10;
                textContentElement.style.fontSize = fontSizePx + 'px';
                break;
            }
            textContentElement.style.fontSize = fontSizePx + 'px';
            loops++;
        }
        // console.log(`Final font size: ${fontSizePx.toFixed(1)}px for text: "${selectedText.substring(0,30)}"`);

        overlayElement.classList.add('visible');
        document.addEventListener('keydown', handleEscapeKey);
    }

    function hideLargeType() {
        if (overlayElement && overlayElement.classList.contains('visible')) {
            overlayElement.classList.remove('visible');
            document.removeEventListener('keydown', handleEscapeKey);
        }
    }

    function handleEscapeKey(event) {
        if (event.key === "Escape") {
            hideLargeType();
        }
    }

    function handleShortcut(event) {
        // 检查是否按下了正确的修饰键 (Alt) 和我们设定的字母键
        // 使用 event.code 来匹配物理按键,'Key' + 字母 (大写)
        if (event[MODIFIER_KEY] && event.code === 'Key' + currentShortcutKey.toUpperCase() && !event.ctrlKey && !event.shiftKey && !event.metaKey) {
            // 排除输入框和可编辑元素内部的触发,除非有文本选中
            const activeElement = document.activeElement;
            const isEditable = activeElement && (activeElement.isContentEditable || activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.tagName === 'SELECT');
            const selectedText = getSelectedText();

            if (!isEditable || selectedText) { // 如果不是在可编辑区域,或者在可编辑区域但有选中文字
                event.preventDefault();
                event.stopPropagation();
                showLargeType();
            }
        }
    }

    // --- 配置快捷键 ---
    async function loadShortcut() {
        const savedKey = await GM_getValue('largeTypeShortcutKey', 'D');
        currentShortcutKey = savedKey;
        // console.log(`Large Type: Shortcut loaded - ${MODIFIER_KEY} + ${currentShortcutKey}`);
    }

    async function saveShortcut(key) {
        await GM_setValue('largeTypeShortcutKey', key);
        currentShortcutKey = key;
        // console.log(`Large Type: Shortcut saved - ${MODIFIER_KEY} + ${currentShortcutKey}`);
        alert(`快捷键已设置为: Alt + ${currentShortcutKey.toUpperCase()}`);
    }

    function changeShortcut() {
        const newKey = prompt(`请输入新的快捷键字母 (例如 D, L, M),当前为 Alt + ${currentShortcutKey.toUpperCase()}:`, currentShortcutKey);
        if (newKey && newKey.trim().length === 1 && /^[a-zA-Z]$/.test(newKey.trim())) {
            saveShortcut(newKey.trim().toUpperCase());
        } else if (newKey !== null) { // 用户输入了但格式不对
            alert("无效的按键。请输入单个字母。");
        }
    }

    // --- 初始化 ---
    async function init() {
        await loadShortcut();
        document.addEventListener('keydown', handleShortcut, true); // 使用捕获阶段以优先处理
        GM_registerMenuCommand("设置 大号文本显示 快捷键", changeShortcut, "L");
        createOverlay(); // 预先创建DOM元素,但不显示
        // console.log("Large Type Display script initialized.");
    }

    init();

})();