Greasy Fork

Greasy Fork is available in English.

Reading Ruler 阅读标尺

A reading ruler tool to help focus while reading

当前为 2024-12-20 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Reading Ruler 阅读标尺
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  A reading ruler tool to help focus while reading
// @author       lumos momo
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @license GPL-3.0-or-later
// ==/UserScript==

(function() {
    'use strict';

    // 默认设置
    const defaultSettings = {
        height: 30,
        color: '#ffeb3b',
        opacity: 0.3,
        isEnabled: false,
        isInverted: false,
        position: { x: 20, y: '50%' }
    };

    // 获取保存的设置
    let settings = {
        ...defaultSettings,
        ...GM_getValue('rulerSettings', {})
    };

    // 创建样式
    const style = document.createElement('style');
    style.textContent = `
        .reading-ruler {
            position: fixed;
            left: 0;
            width: 100%;
            height: ${settings.height}px;
            pointer-events: none;
            z-index: 9999;
            transition: top 0.1s ease;
            display: none;
        }

        .reading-ruler.normal {
            background-color: ${settings.color};
            opacity: ${settings.opacity};
        }

        .reading-ruler.inverted {
            background-color: transparent;
            box-shadow: 0 0 0 100vh ${settings.color};
            position: fixed;
            left: 0;
            right: 0;
            width: 100%;
        }

        .ruler-control {
            position: fixed;
            left: ${settings.position.x}px;
            top: ${settings.position.y};
            transform: translateY(-50%);
            z-index: 10000;
            cursor: move;
            user-select: none;
        }

        .ruler-toggle {
            width: 48px;
            height: 48px;
            border-radius: 50%;
            background: white;
            border: none;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            transition: background-color 0.3s;
            font-size: 20px;
            font-weight: bold;
            color: #666;
        }

        .ruler-toggle:hover {
            background-color: #f5f5f5;
        }

        .ruler-toggle.active {
            background-color: #e3f2fd;
            color: #2196f3;
        }

        .ruler-settings {
            position: absolute;
            top: 0;
            left: 100%;
            margin-left: 10px;
            background: white;
            border-radius: 4px;
            padding: 15px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
            display: none;
            width: 200px;
        }

        .ruler-settings.visible {
            display: block;
        }

        .ruler-settings label {
            display: block;
            margin: 10px 0;
            font-size: 14px;
        }

        .ruler-settings input {
            width: 100%;
            margin-top: 5px;
        }

        .ruler-settings .mode-switch {
            display: flex;
            align-items: center;
            margin: 10px 0;
            padding: 8px 0;
            border-top: 1px solid #eee;
        }

        .ruler-settings .mode-switch span {
            flex-grow: 1;
            font-size: 14px;
        }

        .mode-switch-toggle {
            position: relative;
            display: inline-block;
            width: 40px;
            height: 20px;
        }

        .mode-switch-toggle input {
            opacity: 0;
            width: 0;
            height: 0;
        }

        .mode-switch-slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: #ccc;
            transition: .4s;
            border-radius: 20px;
        }

        .mode-switch-slider:before {
            position: absolute;
            content: "";
            height: 16px;
            width: 16px;
            left: 2px;
            bottom: 2px;
            background-color: white;
            transition: .4s;
            border-radius: 50%;
        }

        .mode-switch-toggle input:checked + .mode-switch-slider {
            background-color: #2196F3;
        }

        .mode-switch-toggle input:checked + .mode-switch-slider:before {
            transform: translateX(20px);
        }
    `;
    document.head.appendChild(style);

    // 创建标尺元素
    const ruler = document.createElement('div');
    ruler.className = 'reading-ruler';
    document.body.appendChild(ruler);

    // 创建控制面板
    const control = document.createElement('div');
    control.className = 'ruler-control';
    control.innerHTML = `
        <button class="ruler-toggle" id="toggleRuler">📏</button>
        <div class="ruler-settings">
            <label>
                高度 (px):
                <input type="range" id="rulerHeight" min="10" max="100" value="${settings.height}">
                <span id="heightValue">${settings.height}</span>px
            </label>
            <label>
                颜色:
                <input type="color" id="rulerColor" value="${settings.color}">
            </label>
            <label>
                透明度:
                <input type="range" id="rulerOpacity" min="0" max="100" value="${settings.opacity * 100}">
                <span id="opacityValue">${Math.round(settings.opacity * 100)}</span>%
            </label>
            <div class="mode-switch">
                <span>反色模式</span>
                <label class="mode-switch-toggle">
                    <input type="checkbox" id="toggleMode" ${settings.isInverted ? 'checked' : ''}>
                    <span class="mode-switch-slider"></span>
                </label>
            </div>
        </div>
    `;
    document.body.appendChild(control);

    // 获取所有需要的元素
    const toggleButton = document.getElementById('toggleRuler');
    const modeSwitch = document.getElementById('toggleMode');
    const settingsPanel = control.querySelector('.ruler-settings');

    // 拖动功能实现
    let isDragging = false;
    let currentX;
    let currentY;
    let initialX;
    let initialY;
    let xOffset = settings.position.x;
    let yOffset = parseInt(settings.position.y) || window.innerHeight / 2;

    function dragStart(e) {
        if (e.type === "mousedown") {
            initialX = e.clientX - xOffset;
            initialY = e.clientY - yOffset;
        }

        if (e.target.closest('.ruler-toggle')) {
            isDragging = true;
        }
    }

    function dragEnd(e) {
        initialX = currentX;
        initialY = currentY;
        isDragging = false;

        settings.position = {
            x: xOffset,
            y: yOffset + 'px'
        };
        saveSettings();
    }

    function drag(e) {
        if (isDragging) {
            e.preventDefault();
            currentX = e.clientX - initialX;
            currentY = e.clientY - initialY;
            xOffset = currentX;
            yOffset = currentY;

            control.style.left = currentX + 'px';
            control.style.top = currentY + 'px';
            control.style.transform = 'none';
        }
    }

    // 更新设置显示
    function updateSettingsDisplay() {
        document.getElementById('heightValue').textContent = settings.height;
        document.getElementById('opacityValue').textContent = Math.round(settings.opacity * 100);
        ruler.style.height = `${settings.height}px`;
        updateRulerMode();
    }

    // 更新标尺模式
    function updateRulerMode() {
        ruler.className = 'reading-ruler ' + (settings.isInverted ? 'inverted' : 'normal');
        if (!settings.isInverted) {
            ruler.style.backgroundColor = settings.color;
            ruler.style.opacity = settings.opacity;
            ruler.style.boxShadow = '';
        } else {
            ruler.style.backgroundColor = 'transparent';
            ruler.style.boxShadow = `0 0 0 100vh ${settings.color}`;
            ruler.style.opacity = settings.opacity;
        }
    }

    // 保存设置
    function saveSettings() {
        GM_setValue('rulerSettings', settings);
    }

    // 更新显示模式
    function updateDisplayMode() {
        ruler.style.display = settings.isEnabled ? 'block' : 'none';
        updateRulerMode();
    }

    // 事件监听器
    toggleButton.addEventListener('click', () => {
        settings.isEnabled = !settings.isEnabled;
        toggleButton.classList.toggle('active', settings.isEnabled);
        updateDisplayMode();
        saveSettings();
    });

    // 模式切换
    modeSwitch.addEventListener('change', (e) => {
        settings.isInverted = e.target.checked;
        updateDisplayMode();
        saveSettings();
    });

    // 右键菜单
    toggleButton.addEventListener('contextmenu', (e) => {
        e.preventDefault();
        settingsPanel.classList.toggle('visible');
    });

    // 点击其他地方关闭设置面板
    document.addEventListener('click', (e) => {
        if (!e.target.closest('.ruler-settings') && !e.target.closest('.ruler-toggle')) {
            settingsPanel.classList.remove('visible');
        }
    });

    // 拖动事件监听
    control.addEventListener("mousedown", dragStart);
    document.addEventListener("mousemove", drag);
    document.addEventListener("mouseup", dragEnd);

    // 设置面板事件监听
    document.getElementById('rulerHeight').addEventListener('input', (e) => {
        settings.height = parseInt(e.target.value);
        updateSettingsDisplay();
        saveSettings();
    });

    document.getElementById('rulerColor').addEventListener('input', (e) => {
        settings.color = e.target.value;
        updateSettingsDisplay();
        saveSettings();
    });

    document.getElementById('rulerOpacity').addEventListener('input', (e) => {
        settings.opacity = parseInt(e.target.value) / 100;
        updateSettingsDisplay();
        saveSettings();
    });

    // 鼠标移动时更新位置
    document.addEventListener('mousemove', (e) => {
        if (settings.isEnabled) {
            const y = e.clientY - (settings.height / 2);
            ruler.style.top = `${y}px`;
        }
    });

    // 初始化显示状态
    if (settings.isEnabled) {
        toggleButton.classList.add('active');
        updateDisplayMode();
    }
})();