深色风格动画天气卡片HTML设计

By 13082141699, 24 April, 2026
角色
assistant
content

这是为您生成的一款深色风格、精致流畅的动画天气卡片HTML文件,包含风力、降雨、晴天、下雪四种天气的并排展示与切换功能。

```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>精品天气 · 四季动画</title>
<style>
/* ─── 全局重置 & 深色基调 ─── */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
background: #0b0e14;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
padding: 24px 16px;
}

/* ─── 卡片容器 ─── */
.weather-dashboard {
max-width: 1320px;
width: 100%;
}

.weather-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 32px;
}

/* ─── 天气卡片 ─── */
.weather-card {
position: relative;
background: linear-gradient(145deg, #1a1f2b, #12171f);
border-radius: 32px;
padding: 28px 20px 20px;
box-shadow: 0 20px 40px -12px rgba(0, 0, 0, 0.8), inset 0 1px 0 rgba(255, 255, 255, 0.04);
transition: transform 0.25s ease, box-shadow 0.3s ease;
overflow: hidden;
min-height: 360px;
display: flex;
flex-direction: column;
backdrop-filter: blur(2px);
border: 1px solid rgba(255, 255, 255, 0.03);
}

.weather-card:hover {
transform: translateY(-6px);
box-shadow: 0 28px 48px -16px rgba(0, 0, 0, 0.9), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

/* 卡片头部 */
.card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 16px;
position: relative;
z-index: 2;
}

.card-title {
color: rgba(255, 255, 255, 0.7);
font-weight: 500;
font-size: 15px;
letter-spacing: 0.3px;
text-transform: uppercase;
backdrop-filter: blur(4px);
background: rgba(255, 255, 255, 0.03);
padding: 4px 12px;
border-radius: 40px;
border: 1px solid rgba(255, 255, 255, 0.04);
}

.card-temp {
color: #fff;
font-weight: 300;
font-size: 38px;
letter-spacing: -1px;
line-height: 1;
}

.card-temp small {
font-size: 18px;
font-weight: 400;
color: rgba(255, 255, 255, 0.5);
margin-left: 4px;
}

/* ─── 动画画布 ─── */
.weather-canvas {
flex: 1;
position: relative;
min-height: 200px;
margin: 8px 0 4px;
z-index: 1;
pointer-events: none;
}

/* 底部信息 */
.card-footer {
display: flex;
justify-content: space-between;
color: rgba(255, 255, 255, 0.35);
font-size: 13px;
font-weight: 400;
border-top: 1px solid rgba(255, 255, 255, 0.04);
padding-top: 14px;
margin-top: 6px;
position: relative;
z-index: 2;
}

.card-footer span {
display: flex;
align-items: center;
gap: 6px;
}

/* ─── 切换按钮组 ─── */
.controls {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
margin-top: 8px;
}

.weather-btn {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.06);
color: rgba(255, 255, 255, 0.6);
padding: 10px 26px;
border-radius: 60px;
font-size: 15px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
backdrop-filter: blur(4px);
letter-spacing: 0.2px;
}

.weather-btn:hover {
background: rgba(255, 255, 255, 0.10);
color: #fff;
border-color: rgba(255, 255, 255, 0.15);
transform: scale(1.02);
}

.weather-btn.active {
background: #3b82f6;
border-color: #3b82f6;
color: #fff;
box-shadow: 0 8px 24px -6px rgba(59, 130, 246, 0.35);
}

/* ─── 响应式 ─── */
@media (max-width: 1024px) {
.weather-grid {
grid-template-columns: repeat(2, 1fr);
gap: 18px;
}
}

@media (max-width: 600px) {
.weather-grid {
grid-template-columns: 1fr;
gap: 20px;
}
.weather-card {
min-height: 320px;
}
.card-temp {
font-size: 32px;
}
.weather-btn {
padding: 8px 18px;
font-size: 13px;
}
}

/* ─── 动画元素通用 ─── */
.wind-line, .raindrop, .snowflake, .sun-ray, .puddle, .cloud-wind {
position: absolute;
will-change: transform, opacity;
}

/* 晴空光晕 */
.sun-glow {
position: absolute;
border-radius: 50%;
background: radial-gradient(circle, rgba(255, 215, 100, 0.25) 0%, rgba(255, 180, 50, 0.05) 60%, transparent 80%);
pointer-events: none;
}

/* 积雪 */
.snow-ground {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 28%;
background: linear-gradient(0deg, rgba(255, 255, 255, 0.25) 0%, rgba(255, 255, 255, 0.04) 100%);
border-radius: 0 0 30px 30px;
pointer-events: none;
backdrop-filter: blur(2px);
}

/* 水洼 */
.puddle {
bottom: 6%;
border-radius: 50%;
background: rgba(100, 180, 255, 0.15);
box-shadow: inset 0 2px 6px rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.02);
}

/* 云朵 (风力) */
.cloud-wind {
background: rgba(255, 255, 255, 0.06);
border-radius: 80% 20% 70% 30% / 60% 50% 50% 40%;
backdrop-filter: blur(6px);
border: 1px solid rgba(255, 255, 255, 0.04);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
</style>
</head>
<body>

<div class="weather-dashboard">
<!-- 四张天气卡片 -->
<div class="weather-grid" id="weatherGrid">
<!-- 风力 -->
<div class="weather-card" data-weather="wind">
<div class="card-header">
<span class="card-title">🌬 风力</span>
<span class="card-temp">12<small>°C</small></span>
</div>
<div class="weather-canvas" id="canvasWind"></div>
<div class="card-footer">
<span>🍃 阵风 28 km/h</span>
<span>湿度 54%</span>
</div>
</div>
<!-- 降雨 -->
<div class="weather-card" data-weather="rain">
<div class="card-header">
<span class="card-title">🌧 降雨</span>
<span class="card-temp">8<small>°C</small></span>
</div>
<div class="weather-canvas" id="canvasRain"></div>
<div class="card-footer">
<span>💧 降水量 6.2 mm</span>
<span>湿度 88%</span>
</div>
</div>
<!-- 晴天 -->
<div class="weather-card" data-weather="sunny">
<div class="card-header">
<span class="card-title">☀️ 晴天</span>
<span class="card-temp">26<small>°C</small></span>
</div>
<div class="weather-canvas" id="canvasSunny"></div>
<div class="card-footer">
<span>🌞 紫外线 强</span>
<span>湿度 32%</span>
</div>
</div>
<!-- 下雪 -->
<div class="weather-card" data-weather="snow">
<div class="card-header">
<span class="card-title">❄️ 下雪</span>
<span class="card-temp">-3<small>°C</small></span>
</div>
<div class="weather-canvas" id="canvasSnow"></div>
<div class="card-footer">
<span>🌨 积雪 8 cm</span>
<span>湿度 76%</span>
</div>
</div>
</div>

<!-- 控制按钮 -->
<div class="controls" id="controlGroup">
<button class="weather-btn active" data-mode="all">🌍 全部显示</button>
<button class="weather-btn" data-mode="wind">🌬 风力</button>
<button class="weather-btn" data-mode="rain">🌧 降雨</button>
<button class="weather-btn" data-mode="sunny">☀️ 晴天</button>
<button class="weather-btn" data-mode="snow">❄️ 下雪</button>
</div>
</div>

<script>
(function() {
'use strict';

// ─── 工具 ───
function rand(min, max) { return Math.random() * (max - min) + min; }

function randInt(min, max) { return Math.floor(rand(min, max + 1)); }

// ─── 每个卡片的动画管理器 ───
class WeatherAnimator {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
if (!this.canvas) return;
this.ctx = this.canvas.getContext('2d');
this.width = 0;
this.height = 0;
this.particles = [];
this.extraElements = [];
this.animationId = null;
this.running = false;
this.resizeObserver = null;
this._initSize();
this._observeResize();
}

_initSize() {
const rect = this.canvas.getBoundingClientRect();
const dpr = window.devicePixelRatio || 1;
this.width = rect.width;
this.height = rect.height;
this.canvas.width = rect.width * dpr;
this.canvas.height = rect.height * dpr;
this.ctx.scale(dpr, dpr);
}

_observeResize() {
this.resizeObserver = new ResizeObserver(() => {
const rect = this.canvas.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return;
const dpr = window.devicePixelRatio || 1;
this.width = rect.width;
this.height = rect.height;
this.canvas.width = rect.width * dpr;
this.canvas.height = rect.height * dpr;
this.ctx.scale(dpr, dpr);
// 重新生成
if (this.running) {
this.restart();
}
});
this.resizeObserver.observe(this.canvas);
}

start() {
if (this.running) return;
this.running = true;
this._loop();
}

stop() {
this.running = false;
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
}

restart() {
this.stop();
this.particles = [];
this.extraElements = [];
this.start();
}

destroy() {
this.stop();
if (this.resizeObserver) {
this.resizeObserver.disconnect();
this.resizeObserver = null;
}
}

// 子类重写
_draw() {}
_initParticles() {}

_loop() {
if (!this.running) return;
const ctx = this.ctx;
ctx.clearRect(0, 0, this.width, this.height);
this._draw();
this.animationId = requestAnimationFrame(() => this._loop());
}
}

// ─── 风力 ───
class WindAnimator extends WeatherAnimator {
constructor(canvasId) {
super(canvasId);
this.clouds = [];
this.windLines = [];
this._initParticles();
}

_initParticles() {
// 云朵
for (let i = 0; i < 3; i++) {
this.clouds.push({
x: rand(-30, this.width + 30),
y: rand(10, this.height * 0.5),
w: rand(60, 110),
h: rand(30, 50),
speed: rand(0.6, 1.4),
opacity: rand(0.15, 0.35),
phase: rand(0, Math.PI * 2)
});
}
// 风线
for (let i = 0; i < 14; i++) {
this.windLines.push({
x: rand(0, this.width),
y: rand(0, this.height * 0.85),
len: rand(20, 60),
speed: rand(1.2, 3.0),
opacity: rand(0.08, 0.25),
thickness: rand(1, 2.5)
});
}
}

_draw() {
const ctx = this.ctx;
const w = this.width, h = this.height;

// 绘制风线
for (const line of this.windLines) {
line.x -= line.speed;
if (line.x + line.len < 0) {
line.x = w + rand(10, 60);
line.y = rand(0, h * 0.8);
line.opacity = rand(0.08, 0.25);
}
ctx.beginPath();
ctx.moveTo(line.x, line.y);
ctx.lineTo(line.x + line.len, line.y - 3);
ctx.strokeStyle = `rgba(200, 220, 255, ${line.opacity})`;
ctx.lineWidth = line.thickness;
ctx.stroke();
// 细尾
ctx.beginPath();
ctx.moveTo(line.x + line.len * 0.7, line.y - 1);
ctx.lineTo(line.x + line.len + 6, line.y - 6);
ctx.strokeStyle = `rgba(200, 220, 255, ${line.opacity * 0.4})`;
ctx.lineWidth = line.thickness * 0.5;
ctx.stroke();
}

// 绘制云朵
for (const cloud of this.clouds) {
cloud.x -= cloud.speed;
cloud.y += Math.sin(cloud.phase + Date.now() * 0.0006) * 0.2;
if (cloud.x + cloud.w < -50) {
cloud.x = w + rand(20, 100);
cloud.y = rand(10, h * 0.45);
}
ctx.save();
ctx.globalAlpha = cloud.opacity;
ctx

total_tokens
6744
uiParsing
关闭