说实话一般,而且页面卡卡的掉帧
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>天气 · iOS 18 风格</title>
<style>
:root { --spring: cubic-bezier(.32, .72, .25, 1); }
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; }
body {
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro SC",
"PingFang SC", "Helvetica Neue", "Microsoft YaHei", sans-serif;
background: #070a14;
color: #fff;
display: flex; align-items: center; justify-content: center;
overflow: hidden;
-webkit-font-smoothing: antialiased;
}
/* ========== 背景氛围光斑 ========== */
.ambient { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.blob { position: absolute; border-radius: 50%; filter: blur(110px); opacity: .55;
animation: blobFloat 16s ease-in-out infinite alternate; }
.b1 { width: 520px; height: 520px; background: #27408f; top: -180px; left: -120px; }
.b2 { width: 460px; height: 460px; background: #5b2a7a; bottom: -160px; right: -100px; animation-delay: -5s; }
.b3 { width: 380px; height: 380px; background: #175e5e; bottom: 6%; left: 34%; animation-delay: -10s; opacity: .4; }
@keyframes blobFloat { to { transform: translate(70px, 50px) scale(1.18); } }
/* ========== 顶部栏 ========== */
.stage { width: min(1240px, 94vw); position: relative; z-index: 1; }
.topbar { display: flex; align-items: flex-end; justify-content: space-between; margin: 0 6px 20px; }
.topbar h1 { font-size: 34px; font-weight: 700; letter-spacing: 1px; }
.sub { margin-top: 6px; font-size: 14px; color: rgba(255,255,255,.62);
display: flex; align-items: center; gap: 8px; }
.sub svg { opacity: .9; }
.glass-chip {
display: flex; align-items: center; gap: 9px; padding: 10px 16px;
border-radius: 999px; background: rgba(255,255,255,.08);
border: 1px solid rgba(255,255,255,.15);
backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums;
}
.live-dot { width: 8px; height: 8px; border-radius: 50%; background: #34d399;
box-shadow: 0 0 10px #34d399; animation: pulseDot 2s infinite; }
@keyframes pulseDot { 50% { opacity: .35; } }
/* ========== 卡片容器 ========== */
.cards { display: flex; gap: 18px; height: min(600px, 68vh); min-height: 480px; perspective: 1600px; }
.card {
position: relative; flex: 1; border-radius: 32px; overflow: hidden;
cursor: pointer; user-select: none; outline: none;
border: 1px solid rgba(255,255,255,.28);
box-shadow: 0 24px 48px -12px rgba(0,0,0,.5), inset 0 1px 0 rgba(255,255,255,.35);
transition: flex-grow .7s var(--spring), min-height .7s var(--spring),
box-shadow .45s ease, border-color .45s ease, transform .25s ease-out;
animation: cardIn .9s cubic-bezier(.22, 1, .36, 1) backwards;
transform-style: preserve-3d;
}
.card:nth-child(1) { animation-delay: .05s; } .card:nth-child(2) { animation-delay: .15s; }
.card:nth-child(3) { animation-delay: .25s; } .card:nth-child(4) { animation-delay: .35s; }
@keyframes cardIn { from { opacity: 0; transform: translateY(44px) scale(.96); } }
.card:hover { box-shadow: 0 30px 60px -14px rgba(0,0,0,.62); }
.card:not(.active):hover { transform: translateY(-8px); }
.card:focus-visible { border-color: rgba(255,255,255,.85); }
.card.active {
flex-grow: 2.6;
border-color: rgba(255,255,255,.5);
box-shadow: 0 34px 70px -16px rgba(0,0,0,.68), inset 0 1px 0 rgba(255,255,255,.45);
}
/* 底部压暗,保证文字可读性 */
.card::after { content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
background: linear-gradient(to top, rgba(8,15,30,.45), rgba(8,15,30,0) 48%); }
/* 四种天气底色 */
.sunny { background: linear-gradient(180deg, #1f6dd1 0%, #4f9be6 40%, #9ccdf0 72%, #ffd9a0 100%); }
.windy { background: linear-gradient(180deg, #23606e 0%, #3d8d8a 48%, #7cc4ad 82%, #b9e2cf 100%); }
.rainstorm { background: linear-gradient(180deg, #151d2c 0%, #243349 52%, #3a516c 100%); }
.blizzard { background: linear-gradient(180deg, #46688f 0%, #6e93b4 45%, #a9c7da 78%, #e6f2f8 100%); }
/* ========== 卡片内容 ========== */
.content { position: relative; z-index: 3; height: 100%; display: flex; flex-direction: column; padding: 22px; }
.row-top { display: flex; justify-content: space-between; gap: 8px; }
.chip { display: inline-flex; align-items: center; gap: 6px; padding: 6px 11px;
font-size: 12px; font-weight: 600; border-radius: 999px; white-space: nowrap;
background: rgba(255,255,255,.16); border: 1px solid rgba(255,255,255,.22);
backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }
.chip.warn { background: rgba(255,159,67,.28); border-color: rgba(255,159,67,.5); }
.chip.danger { background: rgba(255,107,107,.3); border-color: rgba(255,107,107,.52); }
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--c,#fff); box-shadow: 0 0 8px var(--c,#fff); }
.bottom { margin-top: auto; }
.w-eng { font-size: 11px; letter-spacing: 3px; opacity: .7; font-weight: 600; }
.w-name { font-size: 20px; font-weight: 600; letter-spacing: 2px; margin-top: 2px;
text-shadow: 0 2px 12px rgba(0,0,0,.3); }
.temp { font-weight: 200; line-height: 1.05; letter-spacing: -2px;
font-size: clamp(56px, 6.2vw, 78px); text-shadow: 0 4px 24px rgba(0,0,0,.28);
transition: font-size .6s var(--spring); font-variant-numeric: tabular-nums; }
.card:not(.active) .temp { font-size: clamp(40px, 4.2vw, 52px); font-weight: 300; }
.range { font-size: 13.5px; color: rgba(255,255,255,.88); margin-top: 2px; text-shadow: 0 1px 8px rgba(0,0,0,.3); }
/* 展开区域 */
.reveal { max-height: 0; opacity: 0; transform: translateY(16px); overflow: hidden;
transition: max-height .7s var(--spring), opacity .5s ease .08s, transform .65s var(--spring); }
.card.active .reveal { max-height: 340px; opacity: 1; transform: none; }
.desc { font-size: 13px; color: rgba(255,255,255,.78); margin-top: 10px; }
.metrics { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-top: 14px; }
.metric { text-align: center; padding: 10px 6px; border-radius: 16px; overflow: hidden;
background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.18);
backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); }
.metric .k { font-size: 11px; color: rgba(255,255,255,.68); }
.metric .v { font-size: 14px; font-weight: 600; margin-top: 3px; }
.hours { display: flex; gap: 8px; margin-top: 10px; }
.hour { flex: 1; text-align: center; padding: 9px 4px; border-radius: 14px;
background: rgba(255,255,255,.1); border: 1px solid rgba(255,255,255,.14); }
.hour .t { font-size: 11px; color: rgba(255,255,255,.65); }
.hour .v { font-size: 13px; font-weight: 600; margin-top: 4px; }
/* 鼠标跟随高光 */
.glare { position: absolute; inset: 0; z-index: 2; pointer-events: none; opacity: 0; transition: opacity .35s;
background: radial-gradient(420px circle at var(--mx,50%) var(--my,50%), rgba(255,255,255,.2), transparent 46%); }
.card:hover .glare { opacity: 1; }
/* ========== 场景动画层 ========== */
.scene { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
/* 云朵(通用) */
.cloud { position: absolute; width: 130px; height: 42px; background: #fff; border-radius: 40px;
filter: drop-shadow(0 16px 22px rgba(0,0,0,.22)); }
.cloud::before, .cloud::after { content: ''; position: absolute; background: inherit; border-radius: 50%; }
.cloud::before { width: 56px; height: 56px; top: -27px; left: 20px; }
.cloud::after { width: 36px; height: 36px; top: -17px; right: 22px; }
.cloud.sm { width: 84px; height: 28px; }
.cloud.sm::before { width: 38px; height: 38px; top: -18px; left: 12px; }
.cloud.sm::after { width: 26px; height: 26px; top: -12px; right: 12px; }
@keyframes cloudBob { from { transform: translateX(-12px); } to { transform: translateX(12px); } }
/* —— 晴天 —— */
.sun-anchor { position: absolute; top: 132px; left: 50%; width: 0; height: 0; }
.sun { position: absolute; left: -46px; top: -46px; width: 92px; height: 92px; border-radius: 50%;
background: radial-gradient(circle at 35% 32%, #fff8d6, #ffd94e 48%, #ffae33 78%, #ff9d1f);
animation: sunPulse 4.5s ease-in-out infinite; }
@keyframes sunPulse {
0%, 100% { transform: scale(1); box-shadow: 0 0 34px 6px rgba(255,205,80,.8), 0 0 90px 28px rgba(255,170,60,.32); }
50% { transform: scale(1.06); box-shadow: 0 0 48px 12px rgba(255,205,80,.95), 0 0 120px 40px rgba(255,170,60,.45); }
}
.rays { position: absolute; left: 0; top: 0; animation: spin 28s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
.ray { position: absolute; left: -2.5px; top: -9px; width: 5px; height: 18px; border-radius: 3px;
background: linear-gradient(to top, rgba(255,228,130,.95), rgba(255,228,130,0));
transform: rotate(var(--a)) translateY(-76px); transform-origin: 2.5px 9px; }
.sunny .c1 { top: 228px; left: 12%; opacity: .85; animation: cloudBob 9s ease-in-out infinite alternate; }
.sunny .c2 { top: 58px; right: 8%; opacity: .6; animation: cloudBob 12s ease-in-out infinite alternate-reverse; }
/* —— 大风 —— */
.windline { position: absolute; height: 3px; border-radius: 4px; opacity: 0;
background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,.85), rgba(255,255,255,0));
animation: windMove linear infinite; }
@keyframes windMove {
0% { transform: translateX(0); opacity: 0; }
10% { opacity: .85; } 80% { opacity: .85; }
100% { transform: translateX(840px); opacity: 0; }
}
.leaf { position: absolute; width: 11px; height: 11px; border-radius: 0 60% 0 60%;
background: linear-gradient(135deg, #9fdc85, #4f9e4f); animation: leafFly linear infinite; }
@keyframes leafFly { to { transform: translate(840px, 90px) rotate(680deg); } }
.windy .cloud.main { top: 128px; left: 50%; margin-left: -65px; background: #eef7f4;
animation: cloudBob 4s ease-in-out infinite alternate; }
.windy .cloud.back { top: 92px; left: 50%; margin-left: -126px; background: #d7e8e2; opacity: .75;
animation: cloudBob 5.5s ease-in-out infinite alternate-reverse; }
/* —— 暴雨 —— */
.rain { position: absolute; inset: -60px; transform: rotate(9deg); }
.drop { position: absolute; top: -30px; width: 2px; border-radius: 2px;
background: linear-gradient(to bottom, rgba(173,216,255,0), rgba(173,216,255,.75));
animation: fall linear infinite; }
@keyframes fall { to { transform: translateY(780px); } }
.rainstorm .cloud.main { top: 118px; left: 50%; margin-left: -65px; background: #39465a; }
.rainstorm .cloud.back { top: 90px; left: 50%; margin-left: -118px; background: #26303f; opacity: .92;
animation: cloudBob 7s ease-in-out infinite alternate; }
.bolt { position: absolute; top: 150px; left: 50%; margin-left: -15px; width: 30px; height: 48px;
background: linear-gradient(#ffe879, #ffb62e);
clip-path: polygon(58% 0, 0 55%, 38% 55%, 28% 100%, 100% 38%, 52% 38%);
filter: drop-shadow(0 0 14px rgba(255,210,80,.9));
opacity: 0; animation: boltIdle 7s linear infinite; }
@keyframes boltIdle { 0%, 85%, 100% { opacity: 0; } 87% { opacity: 1; } 89% { opacity: .15; } 91% { opacity: .9; } 95% { opacity: 0; } }
.flash-layer { position: absolute; inset: 0; z-index: 4; pointer-events: none; opacity: 0;
background: radial-gradient(circle at 50% 0%, rgba(255,255,255,.95), rgba(255,255,255,0) 72%); }
.card.flashing .flash-layer { animation: skyFlash .85s ease-out; }
.card.flashing .bolt { animation: boltFlash .85s ease both; }
@keyframes skyFlash { 0% { opacity: 0; } 8% { opacity: .85; } 16% { opacity: .15; } 26% { opacity: .6; } 42% { opacity: .1; } 56% { opacity: .3; } 100% { opacity: 0; } }
@keyframes boltFlash { 0% { opacity: 0; } 6% { opacity: 1; } 14% { opacity: .2; } 22% { opacity: 1; } 60% { opacity: .85; } 100% { opacity: 0; } }
/* —— 暴雪 —— */
.flake { position: absolute; top: -12px; border-radius: 50%; background: #fff; animation: snow linear infinite; }
@keyframes snow { to { transform: translate(var(--dx, 50px), 760px); } }
.blizzard .windline { filter: opacity(.45); }
.blizzard .cloud.main { top: 112px; left: 50%; margin-left: -65px; background: #f3f8fc;
animation: cloudBob 6s ease-in-out infinite alternate; }
.blizzard .cloud.back { top: 84px; left: 50%; margin-left: -116px; background: #c9d9e6; opacity: .8;
animation: cloudBob 8s ease-in-out infinite alternate-reverse; }
.snow-ground { position: absolute; left: -12%; right: -12%; height: 84px; border-radius: 50% 50% 0 0 / 56px; }
.g2 { background: rgba(210,230,243,.75); bottom: -26px; left: -20%; right: -4%; }
.g1 { background: rgba(255,255,255,.92); bottom: -40px; }
.hint { text-align: center; margin-top: 18px; font-size: 13px; color: rgba(255,255,255,.45); letter-spacing: 1px; }
/* ========== 响应式 & 无障碍 ========== */
@media (max-width: 880px) {
body { overflow: auto; padding: 28px 0; align-items: flex-start; }
.cards { flex-direction: column; height: auto; min-height: 0; }
.card { flex: none; min-height: 150px; }
.card.active { min-height: 430px; }
.metrics { grid-template-columns: repeat(2, 1fr); }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .2s !important; }
}
</style>
</head>
<body>
<div class="ambient">
<div class="blob b1"></div><div class="blob b2"></div><div class="blob b3"></div>
</div>
<div class="stage">
<header class="topbar">
<div>
<h1>天气</h1>
<div class="sub">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none">
<path d="M12 21s-7-5.1-7-11a7 7 0 1 1 14 0c0 5.9-7 11-7 11z" stroke="white" stroke-opacity=".7" stroke-width="2"/>
<circle cx="12" cy="10" r="2.6" fill="white" fill-opacity=".85"/>
</svg>
<span>北京市 · 朝阳区</span><span>·</span><span id="dateText"></span>
</div>
</div>
<div class="glass-chip"><span class="live-dot"></span><span id="clockText">--:--:--</span></div>
</header>
<main class="cards">
<!-- ☀️ 晴天 -->
<article class="card sunny active" tabindex="0" role="button" aria-expanded="true">
<div class="scene">
<div class="sun-anchor"><div class="rays"></div><div class="sun"></div></div>
<div class="cloud sm c1"></div><div class="cloud sm c2"></div>
</div>
<div class="content">
<div class="row-top">
<span class="chip"><i class="dot" style="--c:#ffd54d"></i>晴</span>
<span class="chip warn">紫外线 强</span>
</div>
<div class="bottom">
<p class="w-eng">SUNNY</p><h2 class="w-name">晴天</h2>
<div class="temp"><span class="temp-num" data-target="28">0</span>°</div>
<p class="range">最高 31° · 最低 22° · 体感 30°</p>
<div class="reveal"><div>
<p class="desc">阳光明媚,紫外线较强,外出请注意防晒与补水。</p>
<div class="metrics">
<div class="metric"><p class="k">湿度</p><p class="v">42%</p></div>
<div class="metric"><p class="k">风速</p><p class="v">8 km/h</p></div>
<div class="metric"><p class="k">气压</p><p class="v">1013 hPa</p></div>
<div class="metric"><p class="k">能见度</p><p class="v">24 km</p></div>
</div>
<div class="hours">
<div class="hour"><p class="t">现在</p><p class="v">28°</p></div>
<div class="hour"><p class="t">15时</p><p class="v">29°</p></div>
<div class="hour"><p class="t">16时</p><p class="v">29°</p></div>
<div class="hour"><p class="t">17时</p><p class="v">27°</p></div>
<div class="hour"><p class="t">18时</p><p class="v">25°</p></div>
</div>
</div></div>
</div>
</div>
<div class="glare"></div>
</article>
<!-- 🌬️ 大风 -->
<article class="card windy" tabindex="0" role="button" aria-expanded="false">
<div class="scene">
<div class="windfield"></div>
<div class="cloud back sm"></div><div class="cloud main"></div>
</div>
<div class="content">
<div class="row-top">
<span class="chip"><i class="dot" style="--c:#7fe3c4"></i>大风</span>
<span class="chip warn">阵风 7 级</span>
</div>
<div class="bottom">
<p class="w-eng">WINDY</p><h2 class="w-name">大风</h2>
<div class="temp"><span class="temp-num" data-target="21">0</span>°</div>
<p class="range">最高 23° · 最低 17° · 体感 18°</p>
<div class="reveal"><div>
<p class="desc">阵风明显,出行请远离临时搭建物,注意高空坠物。</p>
<div class="metrics">
<div class="metric"><p class="k">湿度</p><p class="v">55%</p></div>
<div class="metric"><p class="k">阵风</p><p class="v">52 km/h</p></div>
<div class="metric"><p class="k">风向</p><p class="v">西北</p></div>
<div class="metric"><p class="k">能见度</p><p class="v">18 km</p></div>
</div>
<div class="hours">
<div class="hour"><p class="t">现在</p><p class="v">21°</p></div>
<div class="hour"><p class="t">15时</p><p class="v">21°</p></div>
<div class="hour"><p class="t">16时</p><p class="v">20°</p></div>
<div class="hour"><p class="t">17时</p><p class="v">19°</p></div>
<div class="hour"><p class="t">18时</p><p class="v">18°</p></div>
</div>
</div></div>
</div>
</div>
<div class="glare"></div>
</article>
<!-- ⛈️ 暴雨 -->
<article class="card rainstorm" tabindex="0" role="button" aria-expanded="false">
<div class="scene">
<div class="rain"></div>
<div class="bolt"></div>
<div class="cloud back sm"></div><div class="cloud main"></div>
</div>
<div class="flash-layer"></div>
<div class="content">
<div class="row-top">
<span class="chip"><i class="dot" style="--c:#6db3ff"></i>暴雨</span>
<span class="chip danger">雷电预警</span>
</div>
<div class="bottom">
<p class="w-eng">STORM</p><h2 class="w-name">暴雨</h2>
<div class="temp"><span class="temp-num" data-target="23">0</span>°</div>
<p class="range">最高 24° · 最低 20° · 体感 25°</p>
<div class="reveal"><div>
<p class="desc">雷雨持续,局地伴有强雷电,请减少外出避开积水。</p>
<div class="metrics">
<div class="metric"><p class="k">湿度</p><p class="v">92%</p></div>
<div class="metric"><p class="k">降水量</p><p class="v">38 mm</p></div>
<div class="metric"><p class="k">风速</p><p class="v">24 km/h</p></div>
<div class="metric"><p class="k">能见度</p><p class="v">1.2 km</p></div>
</div>
<div class="hours">
<div class="hour"><p class="t">现在</p><p class="v">23°</p></div>
<div class="hour"><p class="t">15时</p><p class="v">22°</p></div>
<div class="hour"><p class="t">16时</p><p class="v">22°</p></div>
<div class="hour"><p class="t">17时</p><p class="v">21°</p></div>
<div class="hour"><p class="t">18时</p><p class="v">21°</p></div>
</div>
</div></div>
</div>
</div>
<div class="glare"></div>
</article>
<!-- 🌨️ 暴雪 -->
<article class="card blizzard" tabindex="0" role="button" aria-expanded="false">
<div class="scene">
<div class="snowfield"></div>
<div class="gusts"></div>
<div class="cloud back sm"></div><div class="cloud main"></div>
<div class="snow-ground g2"></div><div class="snow-ground g1"></div>
</div>
<div class="content">
<div class="row-top">
<span class="chip"><i class="dot" style="--c:#cfe9ff"></i>暴雪</span>
<span class="chip danger">暴雪预警</span>
</div>
<div class="bottom">
<p class="w-eng">BLIZZARD</p><h2 class="w-name">暴雪</h2>
<div class="temp"><span class="temp-num" data-target="-8">0</span>°</div>
<p class="range">最高 -6° · 最低 -15° · 体感 -14°</p>
<div class="reveal"><div>
<p class="desc">暴雪橙色预警,道路湿滑结冰,请注意保暖与防滑。</p>
<div class="metrics">
<div class="metric"><p class="k">湿度</p><p class="v">78%</p></div>
<div class="metric"><p class="k">风速</p><p class="v">36 km/h</p></div>
<div class="metric"><p class="k">积雪</p><p class="v">12 cm</p></div>
<div class="metric"><p class="k">能见度</p><p class="v">0.8 km</p></div>
</div>
<div class="hours">
<div class="hour"><p class="t">现在</p><p class="v">-8°</p></div>
<div class="hour"><p class="t">15时</p><p class="v">-9°</p></div>
<div class="hour"><p class="t">16时</p><p class="v">-10°</p></div>
<div class="hour"><p class="t">17时</p><p class="v">-11°</p></div>
<div class="hour"><p class="t">18时</p><p class="v">-12°</p></div>
</div>
</div></div>
</div>
</div>
<div class="glare"></div>
</article>
</main>
<p class="hint">点击卡片展开 / 收起详情 · 悬停体验光影与 3D 视差</p>
</div>
<script>
const rand = (a, b) => Math.random() * (b - a) + a;
const cards = [...document.querySelectorAll('.card')];
/* ---------- 粒子生成 ---------- */
function spawn(sel, n, fn) {
const box = document.querySelector(sel);
if (!box) return;
for (let i = 0; i < n; i++) box.appendChild(fn(i));
}
// 太阳光芒 ×12
spawn('.sunny .rays', 12, i => {
const r = document.createElement('i');
r.className = 'ray';
r.style.setProperty('--a', i * 30 + 'deg');
return r;
});
// 雨滴 ×64
spawn('.rainstorm .rain', 64, () => {
const d = document.createElement('span');
d.className = 'drop';
d.style.left = rand(0, 100) + '%';
d.style.height = rand(10, 20) + 'px';
d.style.opacity = rand(.3, .85);
d.style.animationDuration = rand(.55, 1.05) + 's';
d.style.animationDelay = -rand(0, 2) + 's';
return d;
});
// 雪花 ×46
spawn('.blizzard .snowfield', 46, () => {
const f = document.createElement('span');
f.className = 'flake';
const s = rand(3, 8);
f.style.width = f.style.height = s + 'px';
f.style.left = rand(-10, 100) + '%';
f.style.opacity = rand(.45, .95);
f.style.setProperty('--dx', rand(30, 140) + 'px');
f.style.animationDuration = rand(4.5, 9) + 's';
f.style.animationDelay = -rand(0, 9) + 's';
if (Math.random() < .3) f.style.filter = 'blur(1px)';
return f;
});
// 风线(大风 ×9,暴雪 ×5)
const makeWindline = () => {
const w = document.createElement('span');
w.className = 'windline';
w.style.top = rand(8, 82) + '%';
w.style.left = '-220px';
w.style.width = rand(80, 200) + 'px';
w.style.animationDuration = rand(1.6, 3.2) + 's';
w.style.animationDelay = -rand(0, 3) + 's';
return w;
};
spawn('.windy .windfield', 9, makeWindline);
spawn('.blizzard .gusts', 5, makeWindline);
// 落叶 ×6
spawn('.windy .windfield', 6, () => {
const l = document.createElement('span');
l.className = 'leaf';
l.style.left = '-40px';
l.style.top = rand(18, 72) + '%';
l.style.animationDuration = rand(3, 6) + 's';
l.style.animationDelay = -rand(0, 6) + 's';
return l;
});
/* ---------- 卡片交互:点击展开 + 3D 视差 + 高光 ---------- */
cards.forEach(card => {
card.addEventListener('click', () => {
const on = card.classList.contains('active');
cards.forEach(c => { c.classList.remove('active'); c.setAttribute('aria-expanded', 'false'); });
if (!on) { card.classList.add('active'); card.setAttribute('aria-expanded', 'true'); }
});
card.addEventListener('keydown', e => {
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); card.click(); }
});
card.addEventListener('mousemove', e => {
const r = card.getBoundingClientRect();
const x = e.clientX - r.left, y = e.clientY - r.top;
card.style.setProperty('--mx', x + 'px');
card.style.setProperty('--my', y + 'px');
const ry = ((x / r.width) - .5) * 8;
const rx = ((y / r.height) - .5) * -6;
card.style.transform = `translateY(-6px) rotateX(${rx}deg) rotateY(${ry}deg)`;
});
card.addEventListener('mouseleave', () => { card.style.transform = ''; });
});
/* ---------- 随机闪电 ---------- */
const storm = document.querySelector('.rainstorm');
(function lightning() {
setTimeout(() => {
storm.classList.add('flashing');
setTimeout(() => storm.classList.remove('flashing'), 900);
lightning();
}, rand(2200, 7000));
})();
/* ---------- 温度数字滚动 ---------- */
document.querySelectorAll('.temp-num').forEach(el => {
const target = parseInt(el.dataset.target, 10);
const t0 = performance.now(), dur = 1500;
(function step(now) {
const p = Math.min((now - t0) / dur, 1);
el.textContent = Math.round(target * (1 - Math.pow(1 - p, 3)));
if (p < 1) requestAnimationFrame(step);
})(t0);
});
/* ---------- 实时时钟 ---------- */
const clockEl = document.getElementById('clockText');
const dateEl = document.getElementById('dateText');
const weeks = ['周日','周一','周二','周三','周四','周五','周六'];
function tick() {
const d = new Date(), p = n => String(n).padStart(2, '0');
clockEl.textContent = `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
dateEl.textContent = `${d.getMonth() + 1}月${d.getDate()}日 ${weeks[d.getDay()]}`;
}
tick(); setInterval(tick, 1000);
</script>
</body>
</html>
15 个帖子 - 10 位参与者