Greasy Fork

Reading Ruler 阅读标尺

A reading ruler tool to help focus while reading

目前为 2024-12-20 提交的版本。查看 最新版本

// ==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();
    }
})();