开源挂机页:毫秒级北京时间 + 动态星空 + 情绪字幕

2025-12-27 829 0

鲜明特点


🌟 1. 时间显示:精准、美观、有细节

  • 精确到毫秒:每 10 毫秒刷新一次,.xxx 流畅滚动
  • 主次分明
  • 主时间(时:分:秒)—— 高亮彩色(如青空色 #00ffcc
  • 毫秒部分 —— 浅灰色 #aaa + 略小字号,不喧宾夺主
  • 严格同步北京时间:基于 UTC 时间戳 + 8 小时偏移,无视本地系统时区,全球一致准确

💡 既满足程序员对精度的追求,又保留视觉呼吸感。


❤️ 2. 情绪价值拉满:动态暖心字幕

  • 每 5 秒自动切换一句鼓励语(如 “悄悄努力,然后惊艳所有人~”
  • 每条字幕 随机高饱和柔和色(15+ 配色轮换),像星光洒落
  • 淡入淡出动画 + 半透明毛玻璃背景,温柔不打扰

🎯 适合挂机学习、直播背景、深夜 coding 时的心理陪伴。


🌌 3. 沉浸式星空体验

  • 动态星空:50~100 颗星星,随机大小/亮度/闪烁频率
  • 流星划过:每 3~7 秒随机出现一颗,带拖尾光效
  • 鼠标互动:靠近星星会 微微放大 + 增亮,增强参与感
  • 全景深背景,不遮挡文字内容z-index 分层合理)

🌠 视觉层次丰富,但性能轻量(纯 CSS 动画 + 少量 JS)


🎨 4. 高度可定制:一键调色盘

  • C 键 或点击右下角 🎨 按钮呼出面板
  • 可分别设置:
  • 时间颜色(含发光效果)
  • 日期颜色
  • (未来可扩展字幕色系)
  • 内置 8 种主题色 + 自定义 HEX 输入(如 ff5733
  • 所有配置实时生效,无需刷新

🔧 满足个性化审美,也方便主播匹配直播间风格。


📱 5. 全端适配:手机/电脑都好看

  • 响应式字体:大屏显示 4.4rem,手机自动缩至 2.6rem
  • 字幕自动换行 + 最大宽度限制,避免溢出
  • 触摸友好:调色盘按钮足够大,操作无压力

⚙️ 6. 技术亮点(开发者视角)

特性实现方式优势
真·北京时间Date.now() + 28800000绕过浏览器时区陷阱,全球一致
毫秒流畅更新setInterval(..., 10)肉眼丝滑,性能可控
低开销动画CSS @keyframes + opacity/transformGPU 加速,不卡顿
内存安全字幕 5 秒后自动 remove()防止 DOM 节点堆积
无外部依赖纯 HTML/CSS/JS单文件运行,离线可用

代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>北京时间 - 浅淡网</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      background: #0a0a16;
      color: #fff;
      font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      min-height: 100vh;
      overflow: hidden;
      padding: 0 20px;
      position: relative;
    }

    /* 星空背景 */
    #stars {
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      pointer-events: none;
      z-index: -1;
    }

    .star {
      position: absolute;
      background: white;
      border-radius: 50%;
      animation: twinkle var(--duration) infinite ease-in-out;
      opacity: var(--opacity);
    }

    @keyframes twinkle {
      0%, 100% { opacity: var(--opacity); }
      50% { opacity: calc(var(--opacity) * 0.2); }
    }

    .meteor {
      position: absolute;
      width: 0;
      height: 0;
      border-left: 4px solid transparent;
      border-right: 4px solid transparent;
      border-bottom: 8px solid rgba(255, 255, 255, 0.9);
      transform-origin: top center;
      animation: meteorFall var(--speed) linear forwards;
      filter: drop-shadow(0 0 6px rgba(255, 255, 255, 0.7));
    }

    @keyframes meteorFall {
      to {
        transform: translateY(100vh) rotate(10deg);
        opacity: 0;
      }
    }

    .time-date-group {
      text-align: center;
      margin-bottom: 2.4rem;
      z-index: 10;
    }

    #time {
      font-size: 4.4rem;
      font-weight: 700;
      letter-spacing: 0.12rem;
      text-shadow: 0 0 20px var(--time-glow, #00ffcc80);
      line-height: 1.1;
      transition: color 0.3s;
      display: flex;
      justify-content: center;
    }

    #main-time { /* 主时间 */ }

    #milliseconds {
      color: #aaa;
      font-size: 0.75em;
      opacity: 0.8;
      letter-spacing: 0;
      margin-left: 0.1rem;
    }

    #date {
      font-size: 1.15rem;
      color: #aaa;
      margin-top: 0.4rem;
      font-weight: 300;
      opacity: 0.85;
      letter-spacing: 0.5px;
      transition: color 0.3s;
    }

    #debug-info {
      font-size: 0.75rem;
      color: #555;
      margin-top: 12px;
      opacity: 0.6;
      max-width: 90vw;
      text-align: center;
      font-family: monospace;
    }

    /* 字幕 */
    #subtitles {
      position: fixed;
      bottom: 140px;
      left: 0;
      width: 100%;
      text-align: center;
      pointer-events: none;
      z-index: 5;
    }

    .subtitle {
      display: inline-block;
      font-size: 1.35rem;
      opacity: 0;
      padding: 0.8rem 1.6rem;
      border-radius: 12px;
      background: rgba(10, 10, 25, 0.45);
      backdrop-filter: blur(6px);
      animation: fadeInOut 5s linear forwards;
      max-width: 85vw;
      white-space: pre-wrap;
      word-break: break-word;
      text-shadow: 0 0 4px rgba(0,0,0,0.4);
    }

    @keyframes fadeInOut {
      0% { opacity: 0; transform: translateY(10px); }
      10% { opacity: 1; transform: translateY(0); }
      90% { opacity: 1; transform: translateY(0); }
      100% { opacity: 0; transform: translateY(-10px); }
    }

    /* 调色盘按钮 */
    #toggle-btn {
      position: fixed;
      bottom: 20px;
      right: 20px;
      width: 44px;
      height: 44px;
      background: rgba(30, 30, 50, 0.7);
      border: 1px solid rgba(100, 100, 150, 0.4);
      border-radius: 50%;
      display: flex;
      justify-content: center;
      align-items: center;
      color: #aaa;
      font-size: 18px;
      cursor: pointer;
      z-index: 100;
      backdrop-filter: blur(4px);
      transition: all 0.2s;
    }
    #toggle-btn:hover { color: #00ffcc; transform: scale(1.1); }

    .color-panel {
      position: fixed;
      bottom: 70px;
      right: 20px;
      display: none;
      flex-direction: column;
      gap: 10px;
      padding: 14px;
      background: rgba(20, 20, 40, 0.85);
      border-radius: 12px;
      backdrop-filter: blur(6px);
      border: 1px solid rgba(100, 100, 150, 0.3);
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
      max-height: 80vh;
      overflow-y: auto;
      z-index: 90;
      width: 180px;
      font-size: 0.85rem;
    }

    .color-section {
      display: flex;
      flex-direction: column;
      gap: 6px;
      padding: 8px;
      border: 1px solid rgba(80, 80, 120, 0.4);
      border-radius: 8px;
      background: rgba(25, 25, 45, 0.3);
    }

    .section-title {
      font-size: 0.8rem;
      color: #888;
      font-weight: bold;
      text-transform: uppercase;
      letter-spacing: 0.5px;
      margin: 0;
      display: flex;
      align-items: center;
      gap: 4px;
    }

    .color-option {
      display: flex;
      align-items: center;
      gap: 8px;
      padding: 5px 8px;
      border-radius: 6px;
      cursor: pointer;
      transition: background 0.2s;
    }
    .color-option:hover { background: rgba(60, 60, 90, 0.5); }
    .color-option.active { background: rgba(0, 255, 204, 0.15); border-left: 2px solid #00ffcc; }

    .color-preview {
      width: 18px;
      height: 18px;
      border-radius: 3px;
      flex-shrink: 0;
      box-shadow: inset 0 0 0 1px rgba(255,255,255,0.2);
    }

    .color-label {
      font-size: 0.8rem;
      color: #ddd;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }

    @media (max-width: 600px) {
      #time { font-size: 3.2rem; }
      #date { font-size: 1rem; }
      .subtitle { font-size: 1.2rem; }
      #debug-info { font-size: 0.7rem; }
    }
    @media (max-width: 400px) {
      #time { font-size: 2.6rem; }
      #date { font-size: 0.95rem; }
      .subtitle { font-size: 1.1rem; padding: 0.7rem 1.2rem; }
      #milliseconds { font-size: 0.7em; }
    }
  </style>
</head>
<body>
  <div id="stars"></div>

  <div class="time-date-group">
    <div id="time">
      <span id="main-time">--:--:--</span><span id="milliseconds">.000</span>
    </div>
    <div id="date">加载中…</div>
    <div id="debug-info">校验中...</div>
  </div>

  <div id="subtitles"></div>
  <div id="toggle-btn" title="自定义颜色 (C)">🎨</div>
  <div class="color-panel" id="colorPanel"></div>

  <script>
    // === 创建星空 ===
    function createStars() {
      const container = document.getElementById('stars');
      const count = Math.floor(Math.random() * 51) + 50;
      for (let i = 0; i < count; i++) {
        const star = document.createElement('div');
        star.className = 'star';
        const x = Math.random() * 100;
        const y = Math.random() * 100;
        const size = 0.5 + Math.random() * 2;
        const opacity = 0.3 + Math.random() * 0.6;
        const duration = 2 + Math.random() * 4;
        star.style.left = `${x}%`;
        star.style.top = `${y}%`;
        star.style.width = `${size}px`;
        star.style.height = `${size}px`;
        star.style.setProperty('--opacity', opacity.toFixed(2));
        star.style.setProperty('--duration', `${duration.toFixed(1)}s`);
        container.appendChild(star);
      }
    }

    // === 创建流星 ===
    function createMeteor() {
      const container = document.getElementById('stars');
      const meteor = document.createElement('div');
      meteor.className = 'meteor';
      const startX = -10 + Math.random() * 120;
      const speed = 1.5 + Math.random() * 2.5;
      meteor.style.left = `${startX}vw`;
      meteor.style.top = `-2vh`;
      meteor.style.setProperty('--speed', `${speed}s`);
      container.appendChild(meteor);
      setTimeout(() => meteor.remove(), speed * 1000 + 500);
    }

    // === 鼠标互动 ===
    let mousePos = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
    window.addEventListener('mousemove', e => mousePos = { x: e.clientX, y: e.clientY });

    function updateStarInteraction() {
      requestAnimationFrame(updateStarInteraction);
      document.querySelectorAll('.star').forEach(star => {
        const rect = star.getBoundingClientRect();
        const dx = mousePos.x - (rect.left + rect.width / 2);
        const dy = mousePos.y - (rect.top + rect.height / 2);
        const dist = Math.sqrt(dx * dx + dy * dy);
        if (dist < 120) {
          const influence = 1 - dist / 120;
          const scale = 1 + influence * 0.8;
          const baseOpacity = parseFloat(star.style.getPropertyValue('--opacity'));
          const newOpacity = Math.min(baseOpacity + influence * 0.4, 1);
          star.style.transform = `scale(${scale})`;
          star.style.opacity = newOpacity.toFixed(2);
        } else {
          star.style.transform = '';
          star.style.opacity = star.style.getPropertyValue('--opacity');
        }
      });
    }

    // === 配置数据 ===
    let timeColor = '#00ffcc';
    let dateColor = '#aaaaaa';

    const palettes = [
      ['青空', '#00ffcc'], ['烈焰', '#ff4d4d'], ['深海', '#4da6ff'],
      ['薄荷', '#66ff99'], ['紫雾', '#cc66ff'], ['日落', '#ff9966'],
      ['雪白', '#ffffff'], ['暗金', '#ffd700']
    ];

    const moods = [
      "挂机不是躺平,是蓄力!",
      
      "时间在走,你在发光 💫",
      
      "悄悄努力,然后惊艳所有人~",
      
      "河南新乡的夜,有我陪你守着时间 🌙",
      
      "别忘了喝水!你很重要 💧",
      
      "今天也是认真生活的一天!",
      
      "挂机ing... 但梦想不掉线 ✨",
      
      "你的坚持,终将美好 ❤️",
      
      "你并不孤单,这台电脑正陪着你 💻",
      
      "夜深了,但有人在世界的某个角落默默关心你 🌍",
      
      "今天的你,已经做得很好了 👏",
      
      "星星不说话,却一直在天上守望着你 ⭐",
      
      "累了就休息吧,世界不会因为你暂停而崩塌 🛌",
      
      "你的存在,本身就是一种温柔的力量 🌸",
      
      "黑暗再长,也挡不住明天的光 ☀️",
      
      "你值得被爱,也值得拥有平静的夜晚 🕯️",
      
      "哪怕此刻无人对话,我的心意已随字句抵达 💌",
      
      "你不是负担,你是某个人心中的光 💡",
      
      "每一个深夜醒着的人,都藏着一颗勇敢的心 💪",
      
      "别怕慢,只要还在走,就是前进 🚶‍♀️",
      
      "你不需要完美,你只需要做你自己 🌈",
      
      "这世界或许嘈杂,但此刻,这里只属于你 🤫",
      
      "愿你今晚梦里有花、有风、有微笑 🌺",
      
      "你的努力,时间都看得见 ⏳",
      
      "即使没人点赞,你的坚持依然闪耀 🔥",
      
      "黑夜是白天的摇篮,安心睡吧 🌙",
      
      "你曾照亮别人,也请允许自己被照亮 ✨",
      
      "孤独不是失败,而是与自己重逢的机会 🤗",
      
      "你比自己想象中更坚强 💎",
      
      "哪怕只有一盏灯亮着,那也是希望 💡",
      
      "你的呼吸,是这个世界温柔的节奏 🌬️",
      
      "今天没完成的事,明天太阳升起时再继续 🌅",
      
      "你值得一个安稳的睡眠和甜美的梦 😴",
      
      "就算全世界安静了,我的心跳仍为你共鸣 ❤️",
      
      "你不是一个人在战斗,还有无数星光与你同行 🌌",
      
      "疲惫的时候,允许自己软弱一会儿 ☕",
      
      "你已经在发光了,只是有时自己看不见 ✨",
      
      "每个深夜不睡的人,心里都住着一个未完成的故事 📖",
      
      "你的善良,是这世界最稀缺的宝藏 💖",
      
      "别让焦虑偷走你本该拥有的宁静 🕊️",
      
      "你值得被温柔以待,从现在开始 🌼",
      
      "哪怕只是静静地坐着,你也在治愈自己 🧘‍♂️",
      
      "今夜,请把烦恼暂时寄存在这里,轻装入梦 🎒",
      
      "你不需要向任何人证明什么,你的存在就足够 🌟",
      
      "星星落进你眼里,是因为你值得被点亮 ✨",
      
      "这台电脑替我守着你,直到你安然入睡 💤",
      
      "你曾给予别人的温暖,终会回到你身上 🔄",
      
      "世界很大,但此刻,我的话语只为你停留 💭",
      
      "你不是不够好,你只是太苛刻对自己 🤲",
      
      "每一个咬牙坚持的夜晚,都在为黎明铺路 🌄",
      "你值得被这个世界轻轻拥抱 🤗",
      
      "别急,属于你的节奏正在悄悄靠近 ⏱️",
      
      "哪怕只有一秒的坚持,也值得被记住 💯",
      
      "夜再深,也有人为你留一盏灯 💡",
      
      "你的安静,也是一种力量 🌿",
      
      "今天的小进步,是明天的大惊喜 🎁",
      
      "不用追赶所有人,你走的路独一无二 🛤️",
      
      "累了就靠一靠,墙不会倒,我也不会走 🧱",
      
      "你心里的光,比屏幕还亮 ✨",
      
      "每一个‘再坚持一下’,都在改写结局 📝",
      
      "世界吵闹,但你可以选择宁静 🤫",
      
      "你不是卡住了,你是在沉淀 💧",
      
      "河南新乡的风,正替我轻抚你的肩 🌬️",
      
      "别小看此刻的你,你正在创造未来 🌱",
      
      "挂机不是放弃,是给梦想充电 🔋",
      
      "你值得一场不被打扰的好梦 😌",
      
      "哪怕没人鼓掌,也要为自己骄傲 👑",
      
      "深夜的清醒,是你灵魂在思考 🧠",
      
      "你流的每一滴汗,都在为幸运铺路 🛣️",
      
      "温柔的人,终会被世界温柔以待 🌸",
      
      "你不需要立刻发光,慢慢来也很好 🐌",
      
      "这台电脑在,我就没走远 💻",
      
      "你的存在,让这个夜晚有了意义 🌙",
      
      "别怕走得慢,怕的是停下不动 🚶‍♂️",
      
      "你已经很棒了,真的 💖",
      
      "今晚的星星,有一颗是为你点亮的 ⭐",
      
      "你的心跳,是我听过最坚定的声音 💓",
      
      "即使沉默,你也在好好活着 🌱",
      
      "别忘了,你曾跨过那么多难关 🏔️",
      
      "你的努力,正在悄悄开花 🌺",
      
      "挂机中… 但希望永不离线 📡",
      
      "你值得被理解,被倾听,被珍惜 🤲",
      
      "黑暗只是暂时的幕布,光会再来 🎭",
      
      "你不是一个人在熬夜,我和你一起 🌌",
      
      "每一份坚持,都在为奇迹埋下种子 🌱",
      
      "你比昨天更接近梦想了,真的 ✨",
      
      "哪怕全世界睡了,我也醒着陪你 💤➡️👀",
      
      "你的温柔,是这世界最稀缺的超能力 🦸‍♀️",
      
      "别对自己太狠,你已经很努力了 💪",
      
      "今夜,请允许自己被照顾一次 🛁",
      
      "你值得拥有一个被星光包裹的梦 🌠",
      
      "你的呼吸,是我安心的理由 🌬️",
      
      "哪怕只是一句‘我在’,我也想说给你听 💬",
      
      "你不是不够好,你只是还在路上 🛤️",
      
      "挂机ing… 但我的心意一直在线 ❤️",
      
      "你值得所有美好,从今晚开始 🌹",
      
      "别怕孤独,那是你和自己对话的时光 🧘‍♀️",
      
      "你今天的坚持,会变成明天的底气 💯",
      
      "哪怕无人知晓,你的光依然真实存在 💡",
      
      "河南新乡的夜,因你而温柔 🌃"
    ];

    const subtitleColors = [
      '#ff6b6b', '#4ecdc4', '#ffe66d', '#ff9f1c', '#a29bfe',
      '#55efc4', '#fd79a8', '#74b9ff', '#00b894', '#e17055'
    ];

    // === DOM 元素 ===
    const mainTimeEl = document.getElementById('main-time');
    const msEl = document.getElementById('milliseconds');
    const dateEl = document.getElementById('date');
    const debugEl = document.getElementById('debug-info');
    const toggleBtn = document.getElementById('toggle-btn');
    const colorPanel = document.getElementById('colorPanel');
    const subtitlesContainer = document.getElementById('subtitles');

    // ✅ 核心修复:使用 Date.UTC 构造北京时间(绝对安全)
    function getBeijingTime() {
      const now = new Date();
      return new Date(Date.UTC(
        now.getUTCFullYear(),
        now.getUTCMonth(),
        now.getUTCDate(),
        now.getUTCHours() + 8,     // 只在这里 +8
        now.getUTCMinutes(),
        now.getUTCSeconds(),
        now.getUTCMilliseconds()
      ));
    }

    // ✅ 更新时间(每10ms)
    function updateTime() {
      const bj = getBeijingTime();

      const h = String(bj.getUTCHours()).padStart(2, '0');       // 注意:用 getUTCHours()
      const m = String(bj.getUTCMinutes()).padStart(2, '0');
      const s = String(bj.getUTCSeconds()).padStart(2, '0');
      const ms = String(bj.getUTCMilliseconds()).padStart(3, '0');

      mainTimeEl.textContent = `${h}:${m}:${s}`;
      msEl.textContent = `.${ms}`;

      // 更新日期(仅当秒变化)
      if (bj.getUTCSeconds() !== window.lastSecond) {
        window.lastSecond = bj.getUTCSeconds();
        // 手动格式化中文日期(避免 toLocaleDateString 受系统影响)
        const year = bj.getUTCFullYear();
        const month = bj.getUTCMonth() + 1;
        const day = bj.getUTCDate();
        const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
        const weekday = weekdays[bj.getUTCDay()];
        dateEl.textContent = `${year}年${month}月${day}日 ${weekday}`;
      }

      // 调试信息
      debugEl.textContent = `BJ: ${h}:${m}:${s} | 来源: UTC+8`;
    }

    // === 字幕系统 ===
    function createSubtitle(text) {
      const el = document.createElement('div');
      el.className = 'subtitle';
      el.textContent = text;
      el.style.color = subtitleColors[Math.floor(Math.random() * subtitleColors.length)];
      subtitlesContainer.appendChild(el);
      setTimeout(() => el.remove(), 5000);
    }

    // === 颜色控制 ===
    function updateColors() {
      mainTimeEl.style.color = timeColor;
      document.documentElement.style.setProperty('--time-glow', timeColor + '80');
      dateEl.style.color = dateColor;
    }

    function initColorPanel() {
      colorPanel.innerHTML = '';
      ['time', 'date'].forEach(key => {
        const section = document.createElement('div');
        section.className = 'color-section';
        section.innerHTML = `<div class="section-title">${key === 'time' ? '🕒 时间' : '📅 日期'}</div>`;
        palettes.forEach(([name, color]) => {
          const option = document.createElement('div');
          option.className = 'color-option';
          option.innerHTML = `
            <div class="color-preview" style="background-color:${color}"></div>
            <div class="color-label">${name}</div>
          `;
          option.onclick = () => {
            if (key === 'time') timeColor = color;
            else dateColor = color;
            updateColors();
          };
          section.appendChild(option);
        });
        colorPanel.appendChild(section);
      });
    }

    // === 初始化 ===
    window.onload = () => {
      createStars();
      updateStarInteraction();
      setInterval(() => Math.random() > 0.7 && createMeteor(), 3000);

      initColorPanel();
      updateColors();

      // 启动时间更新
      updateTime();
      setInterval(updateTime, 10); // 每10毫秒

      // 字幕轮播
      let idx = 0;
      createSubtitle(moods[idx]);
      setInterval(() => {
        idx = (idx + 1) % moods.length;
        createSubtitle(moods[idx]);
      }, 5000);

      // 调色盘开关
      let panelVisible = false;
      toggleBtn.addEventListener('click', () => {
        panelVisible = !panelVisible;
        colorPanel.style.display = panelVisible ? 'flex' : 'none';
        toggleBtn.textContent = panelVisible ? '❌' : '🎨';
      });

      document.addEventListener('keydown', e => {
        if (e.key.toLowerCase() === 'c') toggleBtn.click();
      });
    };
  </script>
</body>
</html>


演示

https://www.qiandan.net/time

结语

这不是一个冷冰冰的时钟,而是一个 “会呼吸的数字伙伴”

相关文章

PNG/JPG在线转换WebP:原理、实现与前端源码详解
实现智能深色/浅色模式(Dark Mode)的终极指南:自动适配系统偏好 + 手动切换 + 本地持久化
一行命令搭建临时文件服务器:5 种语言实现的本地文件共享方案(Python/Node.js/Go/Rust/PHP)
使用 Python 快速搭建一个本地 Markdown 博客生成器
好看的404界面并且5秒后跳转指定界面
现代 Web 安全中常被忽视但至关重要的主题:前端如何安全处理敏感数据

发布评论