<!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>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
overflow-x: hidden;
}
.weather-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 25px;
max-width: 1400px;
width: 100%;
padding: 20px;
}
.weather-card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-radius: 30px;
padding: 30px;
min-height: 400px;
position: relative;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
cursor: pointer;
}
.weather-card:hover {
transform: translateY(-10px) scale(1.02);
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.2);
}
.weather-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 100%);
border-radius: 30px;
pointer-events: none;
}
/* 晴天卡片 */
.sunny {
background: linear-gradient(135deg, #FFD93D 0%, #FFB344 100%);
}
/* 大风卡片 */
.windy {
background: linear-gradient(135deg, #6DD5FA 0%, #2980B9 100%);
}
/* 暴雨卡片 */
.rainy {
background: linear-gradient(135deg, #536976 0%, #292E49 100%);
}
/* 暴雪卡片 */
.snowy {
background: linear-gradient(135deg, #E6DADA 0%, #274046 100%);
}
.weather-icon {
font-size: 80px;
margin-bottom: 20px;
animation: float 3s ease-in-out infinite;
filter: drop-shadow(0 10px 20px rgba(0,0,0,0.2));
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
}
.temperature {
font-size: 72px;
font-weight: 200;
color: white;
margin-bottom: 10px;
text-shadow: 0 4px 10px rgba(0,0,0,0.2);
}
.weather-type {
font-size: 24px;
font-weight: 500;
color: rgba(255, 255, 255, 0.95);
margin-bottom: 20px;
letter-spacing: 1px;
}
.location {
font-size: 18px;
color: rgba(255, 255, 255, 0.8);
margin-bottom: 30px;
display: flex;
align-items: center;
gap: 8px;
}
.details {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin-top: auto;
}
.detail-item {
background: rgba(255, 255, 255, 0.15);
padding: 12px;
border-radius: 15px;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.detail-item:hover {
background: rgba(255, 255, 255, 0.25);
transform: scale(1.05);
}
.detail-label {
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 4px;
}
.detail-value {
font-size: 18px;
font-weight: 600;
color: white;
}
/* 动画效果 */
.sun-icon {
animation: rotate 20s linear infinite;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.wind-lines {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
pointer-events: none;
overflow: hidden;
}
.wind-line {
position: absolute;
height: 2px;
background: rgba(255, 255, 255, 0.3);
animation: wind 3s linear infinite;
}
@keyframes wind {
from { transform: translateX(-100%); }
to { transform: translateX(200%); }
}
.rain-container {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
pointer-events: none;
overflow: hidden;
}
.rain {
position: absolute;
width: 2px;
height: 100px;
background: linear-gradient(to bottom, transparent, rgba(255,255,255,0.4));
animation: rain 1s linear infinite;
}
@keyframes rain {
from { transform: translateY(-100px); }
to { transform: translateY(calc(100% + 100px)); }
}
.snow-container {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
pointer-events: none;
overflow: hidden;
}
.snowflake {
position: absolute;
color: white;
font-size: 1em;
animation: snowfall linear infinite;
}
@keyframes snowfall {
from {
transform: translateY(-100px);
}
to {
transform: translateY(calc(100vh + 100px));
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.weather-container {
grid-template-columns: 1fr;
}
.temperature {
font-size: 60px;
}
}
/* 脉冲动画 */
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.6; }
100% { opacity: 1; }
}
.pulse {
animation: pulse 2s ease-in-out infinite;
}
</style>
</head>
<body>
<div class="weather-container">
<!-- 晴天卡片 -->
<div class="weather-card sunny" onclick="animateCard(this)">
<div class="weather-icon sun-icon">☀️</div>
<div class="temperature">28°</div>
<div class="weather-type">晴天</div>
<div class="location">
<span>📍</span>
<span>北京</span>
</div>
<div class="details">
<div class="detail-item">
<div class="detail-label">体感温度</div>
<div class="detail-value">30°</div>
</div>
<div class="detail-item">
<div class="detail-label">紫外线</div>
<div class="detail-value">强</div>
</div>
<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">10km</div>
</div>
</div>
</div>
<!-- 大风卡片 -->
<div class="weather-card windy" onclick="animateCard(this)">
<div class="wind-lines">
<div class="wind-line" style="top: 20%; width: 60%; animation-delay: 0s;"></div>
<div class="wind-line" style="top: 40%; width: 80%; animation-delay: 0.5s;"></div>
<div class="wind-line" style="top: 60%; width: 70%; animation-delay: 1s;"></div>
<div class="wind-line" style="top: 80%; width: 90%; animation-delay: 1.5s;"></div>
</div>
<div class="weather-icon">💨</div>
<div class="temperature">22°</div>
<div class="weather-type">大风</div>
<div class="location">
<span>📍</span>
<span>上海</span>
</div>
<div class="details">
<div class="detail-item">
<div class="detail-label">风速</div>
<div class="detail-value">35km/h</div>
</div>
<div class="detail-item">
<div class="detail-label">风向</div>
<div class="detail-value">西北</div>
</div>
<div class="detail-item">
<div class="detail-label">气压</div>
<div class="detail-value">1008hPa</div>
</div>
<div class="detail-item">
<div class="detail-label">阵风</div>
<div class="detail-value">45km/h</div>
</div>
</div>
</div>
<!-- 暴雨卡片 -->
<div class="weather-card rainy" onclick="animateCard(this)">
<div class="rain-container" id="rainContainer"></div>
<div class="weather-icon">⛈️</div>
<div class="temperature">18°</div>
<div class="weather-type">暴雨</div>
<div class="location">
<span>📍</span>
<span>广州</span>
</div>
<div class="details">
<div class="detail-item">
<div class="detail-label">降雨量</div>
<div class="detail-value">25mm/h</div>
</div>
<div class="detail-item">
<div class="detail-label">雷暴概率</div>
<div class="detail-value">85%</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">2km</div>
</div>
</div>
</div>
<!-- 暴雪卡片 -->
<div class="weather-card snowy" onclick="animateCard(this)">
<div class="snow-container" id="snowContainer"></div>
<div class="weather-icon">❄️</div>
<div class="temperature">-5°</div>
<div class="weather-type">暴雪</div>
<div class="location">
<span>📍</span>
<span>哈尔滨</span>
</div>
<div class="details">
<div class="detail-item">
<div class="detail-label">降雪量</div>
<div class="detail-value">15cm</div>
</div>
<div class="detail-item">
<div class="detail-label">风寒指数</div>
<div class="detail-value">-12°</div>
</div>
<div class="detail-item">
<div class="detail-label">湿度</div>
<div class="detail-value">78%</div>
</div>
<div class="detail-item">
<div class="detail-label">能见度</div>
<div class="detail-value">500m</div>
</div>
</div>
</div>
</div>
<script>
// 创建雨滴效果
function createRain() {
const rainContainer = document.getElementById('rainContainer');
for (let i = 0; i < 20; i++) {
const rain = document.createElement('div');
rain.className = 'rain';
rain.style.left = Math.random() * 100 + '%';
rain.style.animationDelay = Math.random() * 2 + 's';
rain.style.animationDuration = 0.5 + Math.random() * 0.5 + 's';
rainContainer.appendChild(rain);
}
}
// 创建雪花效果
function createSnow() {
const snowContainer = document.getElementById('snowContainer');
const snowflakes = ['❄', '❅', '❆'];
for (let i = 0; i < 30; i++) {
const snowflake = document.createElement('div');
snowflake.className = 'snowflake';
snowflake.innerHTML = snowflakes[Math.floor(Math.random() * snowflakes.length)];
snowflake.style.left = Math.random() * 100 + '%';
snowflake.style.animationDuration = 5 + Math.random() * 10 + 's';
snowflake.style.animationDelay = Math.random() * 5 + 's';
snowflake.style.fontSize = 10 + Math.random() * 20 + 'px';
snowflake.style.opacity = 0.4 + Math.random() * 0.6;
snowContainer.appendChild(snowflake);
}
}
// 卡片点击动画
function animateCard(card) {
card.style.animation = 'none';
setTimeout(() => {
card.style.animation = 'pulse 0.5s ease-in-out';
}, 10);
// 添加涟漪效果
const ripple = document.createElement('div');
ripple.style.position = 'absolute';
ripple.style.width = '20px';
ripple.style.height = '20px';
ripple.style.background = 'rgba(255,255,255,0.5)';
ripple.style.borderRadius = '50%';
ripple.style.transform = 'translate(-50%, -50%)';
ripple.style.pointerEvents = 'none';
ripple.style.animation = 'ripple 0.6s ease-out';
const rect = card.getBoundingClientRect();
ripple.style.left = '50%';
ripple.style.top = '50%';
card.appendChild(ripple);
setTimeout(() => {
ripple.remove();
}, 600);
}
// 添加涟漪动画样式
const style = document.createElement('style');
style.textContent = `
@keyframes ripple {
from {
width: 20px;
height: 20px;
opacity: 0.5;
}
to {
width: 400px;
height: 400px;
opacity: 0;
}
}
`;
document.head.appendChild(style);
// 初始化天气效果
createRain();
createSnow();
// 添加视差滚动效果
document.addEventListener('mousemove', (e) => {
const cards = document.querySelectorAll('.weather-card');
const mouseX = e.clientX / window.innerWidth - 0.5;
const mouseY = e.clientY / window.innerHeight - 0.5;
cards.forEach((card, index) => {
const depth = (index + 1) * 0.5;
const moveX = mouseX * depth * 10;
const moveY = mouseY * depth * 10;
card.style.transform = `translateX(${moveX}px) translateY(${moveY}px)`;
});
});
// 添加触摸设备支持
let touchStartX = 0;
let touchStartY = 0;
document.addEventListener('touchstart', (e) => {
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
});
document.addEventListener('touchmove', (e) => {
if (!touchStartX || !touchStartY) return;
const touchX = e.touches[0].clientX;
const touchY = e.touches[0].clientY;
const diffX = (touchX - touchStartX) / window.innerWidth;
const diffY = (touchY - touchStartY) / window.innerHeight;
const cards = document.querySelectorAll('.weather-card');
cards.forEach((card, index) => {
const depth = (index + 1) * 0.5;
const moveX = diffX * depth * 20;
const moveY = diffY * depth * 20;
card.style.transform = `translateX(${moveX}px) translateY(${moveY}px)`;
});
});
// 实时更新时间
function updateTime() {
const now = new Date();
const hours = now.getHours();
// 根据时间改变背景
const body = document.body;
if (hours >= 6 && hours < 12) {
// 早晨
body.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
} else if (hours >= 12 && hours < 18) {
// 下午
body.style.background = 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)';
} else if (hours >= 18 && hours < 21) {
// 傍晚
body.style.background = 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)';
} else {
// 夜晚
body.style.background = 'linear-gradient(135deg, #0f2027 0%, #203a43 50%, #2c5364 100%)';
}
}
// 添加加载动画
window.addEventListener('load', () => {
const cards = document.querySelectorAll('.weather-card');
cards.forEach((card, index) => {
card.style.opacity = '0';
card.style.transform = 'translateY(50px)';
setTimeout(() => {
card.style.transition = 'all 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275)';
card.style.opacity = '1';
card.style.transform = 'translateY(0)';
}, index * 150);
});
});
// 添加卡片切换动画
let currentCard = 0;
const cards = document.querySelectorAll('.weather-card');
// 为每个卡片添加双击事件展开详情
cards.forEach((card, index) => {
card.addEventListener('dblclick', function() {
if (this.classList.contains('expanded')) {
this.classList.remove('expanded');
this.style.position = 'relative';
this.style.zIndex = 'auto';
this.style.transform = 'scale(1)';
document.body.style.overflow = 'auto';
} else {
// 关闭其他展开的卡片
cards.forEach(c => {
c.classList.remove('expanded');
c.style.position = 'relative';
c.style.zIndex = 'auto';
c.style.transform = 'scale(1)';
});
this.classList.add('expanded');
this.style.position = 'fixed';
this.style.zIndex = '1000';
this.style.transform = 'scale(1.2)';
this.style.top = '50%';
this.style.left = '50%';
this.style.transform = 'translate(-50%, -50%) scale(1.2)';
document.body.style.overflow = 'hidden';
}
});
});
// 添加键盘导航
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') {
currentCard = (currentCard - 1 + cards.length) % cards.length;
cards[currentCard].scrollIntoView({ behavior: 'smooth', block: 'center' });
animateCard(cards[currentCard]);
} else if (e.key === 'ArrowRight') {
currentCard = (currentCard + 1) % cards.length;
cards[currentCard].scrollIntoView({ behavior: 'smooth', block: 'center' });
animateCard(cards[currentCard]);
} else if (e.key === 'Escape') {
cards.forEach(card => {
card.classList.remove('expanded');
card.style.position = 'relative';
card.style.zIndex = 'auto';
card.style.transform = 'scale(1)';
});
document.body.style.overflow = 'auto';
}
});
// 添加温度单位切换功能
let isCelsius = true;
cards.forEach(card => {
const tempElement = card.querySelector('.temperature');
tempElement.addEventListener('click', (e) => {
e.stopPropagation();
const currentTemp = parseInt(tempElement.textContent);
if (isCelsius) {
const fahrenheit = Math.round(currentTemp * 9/5 + 32);
tempElement.textContent = fahrenheit + '°F';
} else {
const celsius = Math.round((currentTemp - 32) * 5/9);
tempElement.textContent = celsius + '°';
}
});
});
// 添加自动轮播功能(可选)
let autoPlayInterval;
let isAutoPlaying = false;
function startAutoPlay() {
if (!isAutoPlaying) {
isAutoPlaying = true;
autoPlayInterval = setInterval(() => {
currentCard = (currentCard + 1) % cards.length;
cards[currentCard].scrollIntoView({ behavior: 'smooth', block: 'center' });
// 添加聚焦效果
cards.forEach(c => c.style.opacity = '0.7');
cards[currentCard].style.opacity = '1';
}, 3000);
}
}
function stopAutoPlay() {
if (isAutoPlaying) {
isAutoPlaying = false;
clearInterval(autoPlayInterval);
cards.forEach(c => c.style.opacity = '1');
}
}
// 添加刷新动画
function refreshWeather(card) {
const icon = card.querySelector('.weather-icon');
icon.style.animation = 'none';
setTimeout(() => {
icon.style.animation = 'rotate 1s ease-in-out';
}, 10);
// 模拟数据更新
setTimeout(() => {
const temp = card.querySelector('.temperature');
const currentTemp = parseInt(temp.textContent);
const newTemp = currentTemp + Math.floor(Math.random() * 5 - 2);
temp.textContent = newTemp + '°';
// 添加更新提示
const updateNotice = document.createElement('div');
updateNotice.textContent = '已更新';
updateNotice.style.position = 'absolute';
updateNotice.style.top = '10px';
updateNotice.style.right = '10px';
updateNotice.style.background = 'rgba(76, 217, 100, 0.9)';
updateNotice.style.color = 'white';
updateNotice.style.padding = '5px 10px';
updateNotice.style.borderRadius = '15px';
updateNotice.style.fontSize = '12px';
updateNotice.style.animation = 'fadeInOut 2s ease-in-out';
card.appendChild(updateNotice);
setTimeout(() => {
updateNotice.remove();
}, 2000);
}, 1000);
}
// 为每个卡片添加下拉刷新手势
cards.forEach(card => {
let startY = 0;
let isPulling = false;
card.addEventListener('touchstart', (e) => {
startY = e.touches[0].clientY;
isPulling = true;
});
card.addEventListener('touchmove', (e) => {
if (!isPulling) return;
const currentY = e.touches[0].clientY;
const pullDistance = currentY - startY;
if (pullDistance > 0 && pullDistance < 100) {
card.style.transform = `translateY(${pullDistance * 0.5}px)`;
}
});
card.addEventListener('touchend', (e) => {
if (!isPulling) return;
const endY = e.changedTouches[0].clientY;
const pullDistance = endY - startY;
if (pullDistance > 80) {
refreshWeather(card);
}
card.style.transform = 'translateY(0)';
isPulling = false;
});
});
// 添加淡入淡出动画样式
const fadeStyle = document.createElement('style');
fadeStyle.textContent = `
@keyframes fadeInOut {
0% { opacity: 0; transform: translateY(-10px); }
20% { opacity: 1; transform: translateY(0); }
80% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-10px); }
}
`;
document.head.appendChild(fadeStyle);
// 初始化时间更新
updateTime();
setInterval(updateTime, 60000); // 每分钟更新一次
// 添加页面可见性变化监听
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
stopAutoPlay();
}
});
// 性能优化:使用 Intersection Observer 来优化动画
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.animationPlayState = 'running';
} else {
entry.target.style.animationPlayState = 'paused';
}
});
}, { threshold: 0.1 });
// 观察所有动画元素
document.querySelectorAll('.weather-icon, .rain, .snowflake, .wind-line').forEach(el => {
observer.observe(el);
});
console.log('iOS 18 风格天气卡片已加载完成!');
console.log('操作提示:');
console.log('- 单击卡片:脉冲动画');
console.log('- 双击卡片:展开/收起详情');
console.log('- 点击温度:切换摄氏度/华氏度');
console.log('- 方向键:切换卡片');
console.log('- ESC键:退出展开模式');
console.log('- 鼠标移动:视差效果');
</script>
</body>
</html>
index.html
index.html