拟物钟脚本

// ==UserScript==
// @name         拟物时钟
// @namespace    http://tampermonkey.net/
// @version      4.0
// @description  带毛玻璃底座的拟物时钟。支持位置/大小/显隐状态记忆。滚轮缩放,边缘拖拽,右键半透明。
// @author       舒凌
// @match        *://*/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // --- 1. 配置与默认值 ---
    const STORAGE_KEY = 'glass_clock_settings_v4';
    const DEFAULT_SETTINGS = {
        top: 50,
        right: 50,
        size: 160, // 基础直径
        isHidden: false // 是否处于“半透明”状态
    };

    // 读取本地存储
    function loadSettings() {
        try {
            const saved = localStorage.getItem(STORAGE_KEY);
            return saved ? JSON.parse(saved) : { ...DEFAULT_SETTINGS };
        } catch (e) {
            return { ...DEFAULT_SETTINGS };
        }
    }

    // 保存本地存储
    function saveSettings(settings) {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
    }

    let settings = loadSettings();

    // --- 2. 注入 CSS 样式 ---
    const style = document.createElement('style');
    style.innerHTML = `
        #glass-clock-wrapper {
            position: fixed;
            /* 初始位置由 JS 控制 */
            z-index: 2147483647;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            padding: 20px;
            border-radius: 24px;

            /* 核心:毛玻璃底座效果 */
            background: rgba(255, 255, 255, 0.15);
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
            border: 1px solid rgba(255, 255, 255, 0.3);
            box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15);

            transition: opacity 0.3s ease, transform 0.1s;
            user-select: none;
            cursor: default;
        }

        /* 半透明模式(右键触发) */
        #glass-clock-wrapper.dimmed {
            opacity: 0.1 !important;
        }

        /* 鼠标悬停时恢复清晰 */
        #glass-clock-wrapper.dimmed:hover {
            opacity: 1 !important;
        }

        /* 拖拽时的样式 */
        #glass-clock-wrapper.dragging {
            cursor: grabbing;
            transition: none; /* 拖拽时移除过渡动画以防延迟 */
        }

        /* 调整大小时的鼠标样式 */
        .resize-handle {
            position: absolute;
            bottom: 0;
            right: 0;
            width: 20px;
            height: 20px;
            cursor: nwse-resize;
            z-index: 10;
        }

        /* 表盘容器 */
        #clock-face {
            width: var(--clock-size);
            height: var(--clock-size);
            border-radius: 50%;
            background: radial-gradient(circle at 30% 30%, rgba(255,255,255,0.9), rgba(240,240,240,0.6));
            box-shadow:
                inset 0 0 10px rgba(0,0,0,0.05),
                0 5px 15px rgba(0,0,0,0.1);
            position: relative;
            margin-bottom: 10px;
        }

        /* 刻度 */
        .tick {
            position: absolute;
            left: 50%;
            top: 0;
            width: 2px;
            height: 100%;
            pointer-events: none;
        }
        .tick::before {
            content: '';
            position: absolute;
            top: 5px;
            left: 0;
            width: 100%;
            height: 8px;
            background: #555;
            border-radius: 2px;
        }
        .tick.major::before {
            height: 12px;
            width: 3px;
            left: -0.5px;
            background: #222;
        }

        /* 指针 */
        .hand {
            position: absolute;
            bottom: 50%;
            left: 50%;
            transform-origin: bottom center;
            border-radius: 4px;
            z-index: 2;
        }
        .hour-hand {
            width: 4px;
            height: 25%;
            background: #333;
            margin-left: -2px;
        }
        .minute-hand {
            width: 3px;
            height: 35%;
            background: #666;
            margin-left: -1.5px;
        }
        .second-hand {
            width: 1px;
            height: 40%;
            background: #d32f2f;
            margin-left: -0.5px;
            z-index: 3;
        }
        .center-dot {
            position: absolute;
            top: 50%;
            left: 50%;
            width: 8px;
            height: 8px;
            background: #d32f2f;
            border-radius: 50%;
            transform: translate(-50%, -50%);
            z-index: 4;
            box-shadow: 0 1px 3px rgba(0,0,0,0.3);
        }

        /* 数字时间显示 */
        #digital-time {
            font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
            font-size: calc(var(--clock-size) * 0.15);
            color: #333;
            font-weight: 600;
            letter-spacing: 1px;
            text-shadow: 0 1px 2px rgba(255,255,255,0.8);
            margin-top: 5px;
        }
    `;
    document.head.appendChild(style);

    // --- 3. 构建 DOM 结构 ---
    const wrapper = document.createElement('div');
    wrapper.id = 'glass-clock-wrapper';

    // 应用初始设置
    wrapper.style.top = settings.top + 'px';
    wrapper.style.right = settings.right + 'px';
    wrapper.style.setProperty('--clock-size', settings.size + 'px');
    if (settings.isHidden) wrapper.classList.add('dimmed');

    // 内部 HTML
    wrapper.innerHTML = `
        <div id="clock-face">
            <div class="hand hour-hand"></div>
            <div class="hand minute-hand"></div>
            <div class="hand second-hand"></div>
            <div class="center-dot"></div>
        </div>
        <div id="digital-time">00:00:00</div>
        <div class="resize-handle" title="拖动调整大小"></div>
    `;

    document.body.appendChild(wrapper);

    // 生成刻度
    const face = wrapper.querySelector('#clock-face');
    for (let i = 0; i < 60; i++) {
        const tick = document.createElement('div');
        tick.className = i % 5 === 0 ? 'tick major' : 'tick';
        tick.style.transform = `rotate(${i * 6}deg)`;
        face.appendChild(tick);
    }

    // --- 4. 逻辑功能 ---
    const hourHand = wrapper.querySelector('.hour-hand');
    const minuteHand = wrapper.querySelector('.minute-hand');
    const secondHand = wrapper.querySelector('.second-hand');
    const digitalTime = wrapper.querySelector('#digital-time');

    // 更新时间
    function updateClock() {
        const now = new Date();
        const h = now.getHours();
        const m = now.getMinutes();
        const s = now.getSeconds();
        const ms = now.getMilliseconds();

        // 平滑秒针角度
        const sDeg = (s + ms / 1000) * 6;
        const mDeg = (m + s / 60) * 6;
        const hDeg = ((h % 12) + m / 60) * 30;

        secondHand.style.transform = `rotate(${sDeg}deg)`;
        minuteHand.style.transform = `rotate(${mDeg}deg)`;
        hourHand.style.transform = `rotate(${hDeg}deg)`;

        // 数字时间
        const pad = (n) => n.toString().padStart(2, '0');
        digitalTime.textContent = `${pad(h)}:${pad(m)}:${pad(s)}`;

        requestAnimationFrame(updateClock);
    }
    updateClock();

    // --- 5. 交互事件 ---

    // A. 拖拽移动 (Drag to Move)
    let isDragging = false;
    let startX, startY, initialRight, initialTop;

    wrapper.addEventListener('mousedown', (e) => {
        // 如果点击的是调整大小的手柄,不触发移动
        if (e.target.classList.contains('resize-handle')) return;

        isDragging = true;
        wrapper.classList.add('dragging');
        startX = e.clientX;
        startY = e.clientY;
        initialRight = parseInt(wrapper.style.right || 0);
        initialTop = parseInt(wrapper.style.top || 0);
    });

    window.addEventListener('mousemove', (e) => {
        if (!isDragging) return;
        const dx = e.clientX - startX;
        const dy = e.clientY - startY;

        // 计算新位置
        const newRight = initialRight - dx;
        const newTop = initialTop + dy;

        wrapper.style.right = newRight + 'px';
        wrapper.style.top = newTop + 'px';
    });

    window.addEventListener('mouseup', () => {
        if (isDragging) {
            isDragging = false;
            wrapper.classList.remove('dragging');
            // 保存位置
            settings.right = parseInt(wrapper.style.right);
            settings.top = parseInt(wrapper.style.top);
            saveSettings(settings);
        }
    });

    // B. 滚轮缩放 (Scroll to Scale)
    wrapper.addEventListener('wheel', (e) => {
        e.preventDefault();
        let newSize = settings.size;
        // 向上滚动放大,向下缩小
        newSize += e.deltaY > 0 ? -5 : 5;

        // 限制最小和最大尺寸
        newSize = Math.max(80, Math.min(newSize, 400));

        settings.size = newSize;
        wrapper.style.setProperty('--clock-size', newSize + 'px');
        saveSettings(settings);
    }, { passive: false });

    // C. 边缘拖拽调整大小 (Resize Handle)
    const resizeHandle = wrapper.querySelector('.resize-handle');
    let isResizing = false;
    let startSize, startMouseX, startMouseY;

    resizeHandle.addEventListener('mousedown', (e) => {
        isResizing = true;
        startSize = settings.size;
        startMouseX = e.clientX;
        startMouseY = e.clientY;
        e.stopPropagation(); // 防止触发父级拖拽
    });

    window.addEventListener('mousemove', (e) => {
        if (!isResizing) return;
        // 简单的对角线距离计算来调整大小
        const deltaX = startMouseX - e.clientX; // 向左拖变大
        const deltaY = startMouseY - e.clientY; // 向上拖变大

        // 取变化量的平均值作为缩放依据
        const delta = (deltaX + deltaY) / 2;
        let newSize = startSize + delta;

        newSize = Math.max(80, Math.min(newSize, 400));
        settings.size = newSize;
        wrapper.style.setProperty('--clock-size', newSize + 'px');
    });

    window.addEventListener('mouseup', () => {
        if (isResizing) {
            isResizing = false;
            saveSettings(settings);
        }
    });

    // D. 右键切换显隐 (Right Click Toggle)
    wrapper.addEventListener('contextmenu', (e) => {
        e.preventDefault(); // 阻止默认菜单
        settings.isHidden = !settings.isHidden;
        if (settings.isHidden) {
            wrapper.classList.add('dimmed');
        } else {
            wrapper.classList.remove('dimmed');
        }
        saveSettings(settings);
    });

})();
posted @ 2026-07-16 18:37  舒凌  阅读(5)  评论(0)    收藏  举报