鲜明特点
🌟 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/transform | GPU 加速,不卡顿 |
| 内存安全 | 字幕 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>
演示
结语
这不是一个冷冰冰的时钟,而是一个 “会呼吸的数字伙伴”