以 iOS 18 的设计风格做一个带有动画效果的天气卡片,要求是使用 HTML、CSS 和基础 JavaScript,使用横板天气页面(拥有 4 个天气卡片 (晴天,大风,暴雨,暴雪))。应足够美观,实现一定的交互效果。
<!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>
@import url('https://fonts.googleapis.com/css2?family=SF+Pro+Display:wght@300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, sans-serif;
min-height: 100vh;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
padding: 20px;
opacity: 0;
transition: opacity 0.5s ease;
}
body.loaded {
opacity: 1;
}
/* 背景装饰 */
.bg-decoration {
position: fixed;
width: 600px;
height: 600px;
border-radius: 50%;
filter: blur(120px);
opacity: 0.4;
animation: float 20s ease-in-out infinite;
z-index: 0;
}
.bg-decoration:nth-child(1) {
background: linear-gradient(135deg, #ff6b6b, #feca57);
top: -200px;
left: -100px;
}
.bg-decoration:nth-child(2) {
background: linear-gradient(135deg, #667eea, #764ba2);
bottom: -200px;
right: -100px;
animation-delay: -10s;
}
@keyframes float {
0%, 100% { transform: translate(0, 0) scale(1); }
33% { transform: translate(30px, -30px) scale(1.1); }
66% { transform: translate(-20px, 20px) scale(0.9); }
}
.container {
position: relative;
z-index: 1;
width: 100%;
max-width: 1200px;
}
.header {
text-align: center;
margin-bottom: 40px;
animation: fadeInDown 0.8s ease-out;
}
.header h1 {
font-size: 2.5rem;
font-weight: 300;
color: rgba(255, 255, 255, 0.9);
letter-spacing: 2px;
margin-bottom: 8px;
}
.header p {
font-size: 1rem;
color: rgba(255, 255, 255, 0.6);
font-weight: 400;
}
/* 卡片容器 */
.cards-wrapper {
display: flex;
gap: 24px;
justify-content: center;
flex-wrap: wrap;
perspective: 1000px;
}
/* 天气卡片基础样式 */
.weather-card {
width: 260px;
height: 380px;
border-radius: 32px;
position: relative;
cursor: pointer;
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
animation: fadeInUp 0.8s ease-out backwards;
}
.weather-card:nth-child(1) { animation-delay: 0.1s; }
.weather-card:nth-child(2) { animation-delay: 0.2s; }
.weather-card:nth-child(3) { animation-delay: 0.3s; }
.weather-card:nth-child(4) { animation-delay: 0.4s; }
/* 卡片玻璃效果 */
.weather-card::before {
content: '';
position: absolute;
inset: 0;
border-radius: 32px;
padding: 1px;
background: linear-gradient(135deg, rgba(255,255,255,0.4), rgba(255,255,255,0.1));
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
z-index: 2;
}
.weather-card .card-bg {
position: absolute;
inset: 0;
border-radius: 32px;
opacity: 0.85;
transition: all 0.5s ease;
}
/* 晴天 */
.weather-card.sunny .card-bg {
background: linear-gradient(180deg, #ff9a56 0%, #ff6b35 50%, #f7931e 100%);
}
.weather-card.sunny .weather-icon {
font-size: 80px;
animation: sunPulse 3s ease-in-out infinite;
}
@keyframes sunPulse {
0%, 100% { transform: scale(1) rotate(0deg); text-shadow: 0 0 40px rgba(255, 200, 100, 0.8); }
50% { transform: scale(1.1) rotate(5deg); text-shadow: 0 0 60px rgba(255, 200, 100, 1); }
}
/* 大风 */
.weather-card.windy .card-bg {
background: linear-gradient(180deg, #4facfe 0%, #00f2fe 50%, #43e97b 100%);
}
.weather-card.windy .weather-icon {
font-size: 80px;
animation: windBlow 2s ease-in-out infinite;
}
@keyframes windBlow {
0%, 100% { transform: translateX(0) skewX(0deg); }
25% { transform: translateX(-5px) skewX(-5deg); }
75% { transform: translateX(5px) skewX(5deg); }
}
/* 暴雨 */
.weather-card.storm .card-bg {
background: linear-gradient(180deg, #434343 0%, #2c3e50 50%, #1a252f 100%);
}
.weather-card.storm .weather-icon {
font-size: 80px;
animation: rainShake 1.5s ease-in-out infinite;
}
@keyframes rainShake {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-3px); }
}
/* 暴雪 */
.weather-card.snow .card-bg {
background: linear-gradient(180deg, #e0e5ec 0%, #a8c0ce 50%, #6b8fa3 100%);
}
.weather-card.snow .weather-icon {
font-size: 80px;
animation: snowFloat 3s ease-in-out infinite;
}
.weather-card.snow .temperature,
.weather-card.snow .weather-desc,
.weather-card.snow .location,
.weather-card.snow .detail-label,
.weather-card.snow .detail-value {
color: #2c3e50 !important;
}
.weather-card.snow .card-bottom {
border-top-color: rgba(44, 62, 80, 0.2) !important;
}
@keyframes snowFloat {
0%, 100% { transform: translateY(0) rotate(0deg); opacity: 1; }
50% { transform: translateY(-5px) rotate(10deg); opacity: 0.8; }
}
/* 卡片内容 */
.card-content {
position: relative;
z-index: 1;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 28px;
color: white;
}
.card-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.location {
font-size: 0.9rem;
font-weight: 500;
opacity: 0.9;
letter-spacing: 0.5px;
}
.weather-icon {
display: block;
margin-top: 20px;
filter: drop-shadow(0 10px 20px rgba(0,0,0,0.2));
}
.card-center {
text-align: center;
}
.temperature {
font-size: 4.5rem;
font-weight: 200;
line-height: 1;
letter-spacing: -2px;
text-shadow: 0 4px 20px rgba(0,0,0,0.2);
}
.temperature span {
font-size: 2rem;
font-weight: 400;
vertical-align: super;
}
.weather-desc {
font-size: 1.1rem;
font-weight: 400;
margin-top: 8px;
opacity: 0.9;
}
.card-bottom {
display: flex;
justify-content: space-between;
padding-top: 20px;
border-top: 1px solid rgba(255,255,255,0.2);
}
.detail-item {
text-align: center;
}
.detail-label {
font-size: 0.7rem;
opacity: 0.7;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 4px;
}
.detail-value {
font-size: 1rem;
font-weight: 500;
}
/* 装饰元素 */
.card-decoration {
position: absolute;
border-radius: 50%;
opacity: 0.3;
}
.weather-card.sunny .card-decoration:nth-child(1) {
width: 150px;
height: 150px;
background: radial-gradient(circle, rgba(255,255,255,0.3) 0%, transparent 70%);
top: -50px;
right: -30px;
animation: rotate 15s linear infinite;
}
.weather-card.sunny .card-decoration:nth-child(2) {
width: 80px;
height: 80px;
background: radial-gradient(circle, rgba(255,255,255,0.4) 0%, transparent 70%);
bottom: 60px;
left: -20px;
animation: rotate 10s linear infinite reverse;
}
.weather-card.windy .card-decoration {
width: 100px;
height: 100px;
border: 2px solid rgba(255,255,255,0.2);
border-radius: 50%;
animation: windCircle 4s linear infinite;
}
.weather-card.storm .card-decoration {
width: 120px;
height: 120px;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
animation: stormPulse 2s ease-in-out infinite;
}
.weather-card.snow .card-decoration {
width: 8px;
height: 8px;
background: white;
border-radius: 50%;
animation: snowParticle 2s ease-in-out infinite;
}
.weather-card.snow .card-decoration:nth-child(1) { top: 30%; left: 20%; animation-delay: 0s; }
.weather-card.snow .card-decoration:nth-child(2) { top: 50%; right: 15%; animation-delay: 0.5s; }
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes windCircle {
0% { transform: rotate(0deg) scale(1); opacity: 0.3; }
50% { transform: rotate(180deg) scale(1.2); opacity: 0.1; }
100% { transform: rotate(360deg) scale(1); opacity: 0.3; }
}
@keyframes stormPulse {
0%, 100% { transform: scale(1); opacity: 0.3; }
50% { transform: scale(1.3); opacity: 0.1; }
}
@keyframes snowParticle {
0%, 100% { transform: translateY(0); opacity: 1; }
50% { transform: translateY(-20px); opacity: 0.3; }
}
/* 悬停效果 */
.weather-card:hover {
transform: translateY(-15px) scale(1.02);
box-shadow: 0 30px 60px rgba(0,0,0,0.3);
}
.weather-card:hover .card-bg {
opacity: 1;
transform: scale(1.05);
}
/* 动画关键帧 */
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 响应式设计 */
@media (max-width: 1200px) {
.cards-wrapper {
flex-wrap: wrap;
}
}
@media (max-width: 768px) {
.header h1 {
font-size: 1.8rem;
}
.weather-card {
width: 100%;
max-width: 300px;
height: 320px;
}
.temperature {
font-size: 3.5rem;
}
}
/* 闪光效果 */
.glint {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: linear-gradient(
135deg,
transparent 40%,
rgba(255,255,255,0.1) 45%,
rgba(255,255,255,0.2) 50%,
rgba(255,255,255,0.1) 55%,
transparent 60%
);
transform: translateX(-100%);
transition: transform 0.6s ease;
pointer-events: none;
border-radius: 32px;
z-index: 3;
}
.weather-card:hover .glint {
transform: translateX(100%);
transition: transform 0.8s ease;
}
</style>
</head>
<body>
<!-- 背景装饰 -->
<div class="bg-decoration"></div>
<div class="bg-decoration"></div>
<div class="container">
<div class="header">
<h1>Weather</h1>
<p>Beautiful weather, beautiful day</p>
</div>
<div class="cards-wrapper">
<!-- 晴天 -->
<div class="weather-card sunny">
<div class="card-bg"></div>
<div class="card-decoration"></div>
<div class="card-decoration"></div>
<div class="glint"></div>
<div class="card-content">
<div class="card-top">
<div class="location">☀️ 晴朗</div>
<div class="menu-icon">⋮</div>
</div>
<div class="weather-icon">☀️</div>
<div class="card-center">
<div class="temperature" data-temp="28">0<span>°</span></div>
<div class="weather-desc">阳光明媚</div>
</div>
<div class="card-bottom">
<div class="detail-item">
<div class="detail-label">湿度</div>
<div class="detail-value">45%</div>
</div>
<div class="detail-item">
<div class="detail-label">风速</div>
<div class="detail-value">12 km/h</div>
</div>
<div class="detail-item">
<div class="detail-label">紫外线</div>
<div class="detail-value">中等</div>
</div>
</div>
</div>
</div>
<!-- 大风 -->
<div class="weather-card windy">
<div class="card-bg"></div>
<div class="card-decoration"></div>
<div class="card-decoration"></div>
<div class="glint"></div>
<div class="card-content">
<div class="card-top">
<div class="location">💨 大风</div>
</div>
<div class="weather-icon">🌀</div>
<div class="card-center">
<div class="temperature" data-temp="19">0<span>°</span></div>
<div class="weather-desc">大风蓝色预警</div>
</div>
<div class="card-bottom">
<div class="detail-item">
<div class="detail-label">风速</div>
<div class="detail-value">45 km/h</div>
</div>
<div class="detail-item">
<div class="detail-label">阵风</div>
<div class="detail-value">65 km/h</div>
</div>
<div class="detail-item">
<div class="detail-label">风向</div>
<div class="detail-value">西北</div>
</div>
</div>
</div>
</div>
<!-- 暴雨 -->
<div class="weather-card storm">
<div class="card-bg"></div>
<div class="card-decoration"></div>
<div class="glint"></div>
<div class="card-content">
<div class="card-top">
<div class="location">⛈️ 暴雨</div>
</div>
<div class="weather-icon">⛈️</div>
<div class="card-center">
<div class="temperature" data-temp="22">0<span>°</span></div>
<div class="weather-desc">暴雨红色预警</div>
</div>
<div class="card-bottom">
<div class="detail-item">
<div class="detail-label">降水量</div>
<div class="detail-value">85 mm</div>
</div>
<div class="detail-item">
<div class="detail-label">湿度</div>
<div class="detail-value">92%</div>
</div>
<div class="detail-item">
<div class="detail-label">气压</div>
<div class="detail-value">1002 hPa</div>
</div>
</div>
</div>
</div>
<!-- 暴雪 -->
<div class="weather-card snow">
<div class="card-bg"></div>
<div class="card-decoration"></div>
<div class="card-decoration"></div>
<div class="glint"></div>
<div class="card-content">
<div class="card-top">
<div class="location">❄️ 暴雪</div>
</div>
<div class="weather-icon">🌨️</div>
<div class="card-center">
<div class="temperature" data-temp="-8">0<span>°</span></div>
<div class="weather-desc">暴雪橙色预警</div>
</div>
<div class="card-bottom">
<div class="detail-item">
<div class="detail-label">积雪</div>
<div class="detail-value">25 cm</div>
</div>
<div class="detail-item">
<div class="detail-label">能见度</div>
<div class="detail-value">200 m</div>
</div>
<div class="detail-item">
<div class="detail-label">体感</div>
<div class="detail-value">-15°</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// 温度数字动画 - 修复版
function animateTemperature(element) {
const targetTemp = parseInt(element.getAttribute('data-temp'), 10);
const isNegative = targetTemp < 0;
const absTarget = Math.abs(targetTemp);
// 起始值
let current = 0;
const duration = 1500; // 动画持续时间
const startTime = performance.now();
function update(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
// 使用缓动函数让动画更自然
const easeProgress = 1 - Math.pow(1 - progress, 3);
const currentValue = Math.round(absTarget * easeProgress);
const displayValue = isNegative ? -currentValue : currentValue;
element.innerHTML = `${displayValue}<span>°</span>`;
if (progress < 1) {
requestAnimationFrame(update);
} else {
// 确保最终显示正确的值
element.innerHTML = `${targetTemp}<span>°</span>`;
}
}
requestAnimationFrame(update);
}
// 初始化所有温度动画
document.querySelectorAll('.temperature').forEach((temp, index) => {
setTimeout(() => {
animateTemperature(temp);
}, 300 + index * 150);
});
// 卡片交互动画
document.querySelectorAll('.weather-card').forEach(card => {
// 鼠标进入动画
card.addEventListener('mouseenter', () => {
card.style.transition = 'all 0.5s cubic-bezier(0.4, 0, 0.2, 1)';
});
// 鼠标离开动画
card.addEventListener('mouseleave', () => {
card.style.transition = 'all 0.5s cubic-bezier(0.4, 0, 0.2, 1)';
});
// 点击效果
card.addEventListener('click', function(e) {
// 创建涟漪效果
const ripple = document.createElement('div');
ripple.style.cssText = `
position: absolute;
width: 10px;
height: 10px;
background: rgba(255,255,255,0.6);
border-radius: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
z-index: 10;
animation: ripple 0.6s ease-out forwards;
`;
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
ripple.style.left = x + 'px';
ripple.style.top = y + 'px';
card.appendChild(ripple);
setTimeout(() => ripple.remove(), 600);
});
});
// 添加涟漪动画
const style = document.createElement('style');
style.textContent = `
@keyframes ripple {
0% {
transform: translate(-50%, -50%) scale(0);
opacity: 1;
}
100% {
transform: translate(-50%, -50%) scale(30);
opacity: 0;
}
}
`;
document.head.appendChild(style);
// 页面加载完成
window.addEventListener('load', () => {
document.body.classList.add('loaded');
});
</script>
</body>
</html>




