deepseek V4 Pro 最新天气卡片速览!

2026-04-29 10:362阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐
问题描述:

深度思考140秒出来的结果:
image1467×557 63.6 KB
image270×109 2.41 KB
以 iOS 18 的设计风格做一个带有动画效果的天气卡片,要求是使用 HTML、CSS 和基础 JavaScript,使用横板天气页面(拥有 4 个天气卡片 (晴天,大风,暴雨,暴雪))。应足够美观,实现一定的交互效果。

html

<!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 { --bg-dark: #1c1c1e; --card-radius: 22px; --transition-smooth: 0.4s cubic-bezier(0.25, 0.1, 0.25, 1); --transition-bounce: 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', 'PingFang SC', sans-serif; min-height: 100vh; display: flex; align-items: center; justify-content: center; background: #0d0d0f; overflow-x: hidden; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent; } /* 背景层 */ .bg-layer { position: fixed; inset: 0; pointer-events: none; z-index: 0; overflow: hidden; } .bg-orb { position: absolute; border-radius: 50%; filter: blur(100px); opacity: 0.55; animation: orbFloat 12s ease-in-out infinite; will-change: transform; } .bg-orb.orb-1 { width: 420px; height: 420px; background: radial-gradient(circle, #ff9a56 0%, #ff6b35 40%, transparent 70%); top: -15%; left: 8%; animation-delay: 0s; animation-duration: 14s; } .bg-orb.orb-2 { width: 350px; height: 350px; background: radial-gradient(circle, #7eb8ff 0%, #4a90d9 40%, transparent 70%); bottom: -18%; right: 5%; animation-delay: -4s; animation-duration: 16s; } .bg-orb.orb-3 { width: 300px; height: 300px; background: radial-gradient(circle, #c4b5fd 0%, #8b7cf0 40%, transparent 70%); top: 40%; left: 50%; animation-delay: -8s; animation-duration: 13s; } .bg-orb.orb-4 { width: 260px; height: 260px; background: radial-gradient(circle, #67e8f9 0%, #22d3ee 35%, transparent 70%); bottom: 10%; left: 20%; animation-delay: -6s; animation-duration: 15s; } .bg-orb.orb-5 { width: 200px; height: 200px; background: radial-gradient(circle, #fde68a 0%, #fbbf24 35%, transparent 70%); top: 15%; right: 18%; animation-delay: -10s; animation-duration: 11s; } @keyframes orbFloat { 0%, 100% { transform: translate(0, 0) scale(1); } 20% { transform: translate(30px, -25px) scale(1.08); } 40% { transform: translate(-20px, 15px) scale(0.94); } 60% { transform: translate(15px, 30px) scale(1.05); } 80% { transform: translate(-28px, -10px) scale(0.96); } } /* 主容器 */ .main-container { position: relative; z-index: 1; width: 100%; max-width: 1400px; padding: 30px 24px; } /* 顶部标题栏 */ .header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 28px; padding: 0 8px; flex-wrap: wrap; gap: 14px; } .header-title { font-size: 1.7rem; font-weight: 600; color: #ffffff; letter-spacing: -0.3px; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); } .header-subtitle { font-size: 0.9rem; font-weight: 400; color: rgba(255, 255, 255, 0.6); letter-spacing: 0.1px; } .temp-toggle-btn { display: flex; align-items: center; gap: 8px; padding: 10px 18px; border-radius: 100px; border: 1.5px solid rgba(255, 255, 255, 0.25); background: rgba(255, 255, 255, 0.08); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); color: #ffffff; cursor: pointer; font-size: 0.9rem; font-weight: 500; letter-spacing: -0.2px; transition: all var(--transition-smooth); white-space: nowrap; font-family: inherit; } .temp-toggle-btn:hover { background: rgba(255, 255, 255, 0.16); border-color: rgba(255, 255, 255, 0.45); transform: translateY(-1px); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25); } .temp-toggle-btn:active { transform: scale(0.96); transition: transform 0.15s ease; } .toggle-icon { font-size: 1.1rem; transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); } .temp-toggle-btn:active .toggle-icon { transform: rotate(180deg); } /* 卡片滚动容器 */ .cards-scroll-container { overflow-x: auto; overflow-y: visible; scroll-behavior: smooth; -webkit-overflow-scrolling: touch; padding: 10px 4px 20px; scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.2) transparent; } .cards-scroll-container::-webkit-scrollbar { height: 6px; } .cards-scroll-container::-webkit-scrollbar-track { background: transparent; border-radius: 10px; } .cards-scroll-container::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 10px; transition: background 0.3s; } .cards-scroll-container::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.4); } /* 卡片网格 */ .cards-grid { display: flex; gap: 20px; padding: 4px 6px; min-width: min-content; } /* 单张天气卡片 */ .weather-card { position: relative; flex: 0 0 auto; width: 300px; height: 200px; border-radius: var(--card-radius); cursor: pointer; overflow: hidden; transition: all var(--transition-bounce); border: 1px solid rgba(255, 255, 255, 0.18); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.06); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); background: rgba(30, 30, 35, 0.45); transform: translateY(0); z-index: 1; will-change: transform, box-shadow; } .weather-card:hover { transform: translateY(-10px) scale(1.025); box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45), 0 6px 16px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1); z-index: 10; border-color: rgba(255, 255, 255, 0.35); } .weather-card:active { transform: translateY(-4px) scale(0.985); transition: all 0.15s ease; box-shadow: 0 10px 28px rgba(0, 0, 0, 0.38), 0 3px 10px rgba(0, 0, 0, 0.25); } /* 卡片选中状态 */ .weather-card.selected { border-color: rgba(255, 255, 255, 0.55); box-shadow: 0 18px 44px rgba(0, 0, 0, 0.4), 0 4px 14px rgba(0, 0, 0, 0.28), 0 0 0 3px rgba(255, 255, 255, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.1); transform: translateY(-6px) scale(1.018); } /* 卡片内部天空动画区 */ .card-sky { position: absolute; top: 0; left: 0; right: 0; height: 68%; overflow: hidden; pointer-events: none; z-index: 1; } /* 各天气卡片天空背景 */ .card-sky.sky-sunny { background: linear-gradient(180deg, #ffb347 0%, #ff8c42 25%, #ff7030 55%, #e85d3a 100%); } .card-sky.sky-windy { background: linear-gradient(180deg, #b0c4de 0%, #8fa8c8 30%, #7a94b5 60%, #5f7d9e 100%); } .card-sky.sky-rain { background: linear-gradient(180deg, #4a5568 0%, #3d4855 30%, #2d3748 60%, #1a2332 100%); } .card-sky.sky-snow { background: linear-gradient(180deg, #c8d6e5 0%, #b0c0d4 30%, #95a8bf 55%, #7a8fa3 100%); } /* 信息栏 */ .card-info-bar { position: absolute; bottom: 0; left: 0; right: 0; height: 32%; display: flex; align-items: center; justify-content: space-between; padding: 0 18px; z-index: 3; background: rgba(20, 20, 25, 0.5); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border-top: 1px solid rgba(255, 255, 255, 0.1); } .card-info-left { display: flex; flex-direction: column; gap: 2px; } .card-city { font-size: 0.85rem; font-weight: 500; color: rgba(255, 255, 255, 0.85); letter-spacing: -0.1px; } .card-weather-label { font-size: 0.7rem; font-weight: 400; color: rgba(255, 255, 255, 0.5); letter-spacing: 0.2px; } .card-temp { font-size: 2.6rem; font-weight: 300; color: #ffffff; letter-spacing: -2px; line-height: 1; transition: all 0.3s ease; } .card-temp-unit { font-size: 1rem; font-weight: 400; color: rgba(255, 255, 255, 0.7); letter-spacing: 0; margin-left: 1px; vertical-align: super; } /* ============ 晴天太阳动画 ============ */ .sun-container { position: absolute; top: 50%; left: 38%; transform: translate(-50%, -50%); width: 70px; height: 70px; z-index: 2; } .sun-core { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 44px; height: 44px; border-radius: 50%; background: radial-gradient(circle at 40% 35%, #fffde7 0%, #ffe082 30%, #ffb300 70%, #ff8f00 100%); box-shadow: 0 0 30px rgba(255, 180, 40, 0.8), 0 0 60px rgba(255, 160, 20, 0.5), 0 0 90px rgba(255, 140, 10, 0.3), 0 0 130px rgba(255, 120, 0, 0.2); animation: sunPulse 3s ease-in-out infinite; z-index: 3; } @keyframes sunPulse { 0%, 100% { box-shadow: 0 0 30px rgba(255, 180, 40, 0.8), 0 0 60px rgba(255, 160, 20, 0.5), 0 0 90px rgba(255, 140, 10, 0.3), 0 0 130px rgba(255, 120, 0, 0.2); } 50% { box-shadow: 0 0 40px rgba(255, 200, 50, 1), 0 0 75px rgba(255, 175, 30, 0.65), 0 0 105px rgba(255, 150, 15, 0.4), 0 0 150px rgba(255, 130, 5, 0.28); } } .sun-ring { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); border-radius: 50%; border: 2px solid rgba(255, 220, 150, 0.5); animation: ringSpin 8s linear infinite; pointer-events: none; } .sun-ring.ring-1 { width: 58px; height: 58px; animation-duration: 7s; border-color: rgba(255, 210, 130, 0.45); } .sun-ring.ring-2 { width: 72px; height: 72px; animation-duration: 10s; animation-direction: reverse; border-color: rgba(255, 200, 120, 0.3); border-style: dashed; border-width: 1.5px; } .sun-ring.ring-3 { width: 86px; height: 86px; animation-duration: 13s; border-color: rgba(255, 190, 110, 0.22); border-width: 1px; } @keyframes ringSpin { from { transform: translate(-50%, -50%) rotate(0deg); } to { transform: translate(-50%, -50%) rotate(360deg); } } /* 阳光粒子 */ .sun-particle { position: absolute; top: 50%; left: 38%; width: 4px; height: 4px; border-radius: 50%; background: #ffe9a0; pointer-events: none; z-index: 1; animation: particleFloat 2.5s ease-in-out infinite; box-shadow: 0 0 6px 2px rgba(255, 210, 100, 0.7); } @keyframes particleFloat { 0%, 100% { opacity: 0; transform: translate(0, 0) scale(0.5); } 30% { opacity: 1; } 60% { opacity: 0.7; transform: translate(var(--px), var(--py)) scale(1.3); } 100% { opacity: 0; transform: translate(var(--px-end), var(--py-end)) scale(0.3); } } /* ============ 大风动画 ============ */ .wind-line { position: absolute; height: 2px; border-radius: 4px; background: rgba(255, 255, 255, 0.7); pointer-events: none; z-index: 2; animation: windBlow linear infinite; filter: blur(0.5px); } .wind-line.thin { height: 1px; background: rgba(255, 255, 255, 0.5); filter: blur(1px); } .wind-line.thick { height: 3px; background: rgba(255, 255, 255, 0.75); filter: blur(0px); } @keyframes windBlow { 0% { transform: translateX(-120%); opacity: 0; } 8% { opacity: 1; } 85% { opacity: 1; } 100% { transform: translateX(420%); opacity: 0; } } /* 风中的碎屑 */ .wind-debris { position: absolute; border-radius: 50%; background: rgba(255, 255, 255, 0.6); pointer-events: none; z-index: 2; animation: debrisFly linear infinite; } @keyframes debrisFly { 0% { transform: translateX(-150%) translateY(0) rotate(0deg); opacity: 0; } 10% { opacity: 1; } 50% { transform: translateX(100%) translateY(-18px) rotate(180deg); opacity: 0.8; } 90% { opacity: 1; } 100% { transform: translateX(380%) translateY(5px) rotate(360deg); opacity: 0; } } /* ============ 暴雨动画 ============ */ .rain-drop { position: absolute; top: -30px; width: 2px; border-radius: 0 0 3px 3px; background: linear-gradient(180deg, transparent 0%, rgba(200, 220, 240, 0.8) 30%, rgba(180, 210, 235, 0.9) 100%); pointer-events: none; z-index: 2; animation: rainFall linear infinite; box-shadow: 0 0 3px rgba(180, 210, 240, 0.4); } @keyframes rainFall { 0% { transform: translateY(-40px); opacity: 0; } 5% { opacity: 1; } 85% { opacity: 1; } 100% { transform: translateY(160px); opacity: 0; } } /* 雨滴溅落 */ .rain-splash { position: absolute; bottom: 8px; width: 6px; height: 6px; border-radius: 50%; background: rgba(200, 215, 235, 0.5); pointer-events: none; z-index: 2; animation: splashFade 0.6s ease-out infinite; animation-delay: var(--splash-delay); } @keyframes splashFade { 0% { transform: scale(0.3); opacity: 0.8; } 100% { transform: scale(4); opacity: 0; } } /* ============ 暴雪动画 ============ */ .snow-flake { position: absolute; top: -20px; border-radius: 50%; background: #ffffff; pointer-events: none; z-index: 2; animation: snowFall linear infinite; box-shadow: 0 0 6px 2px rgba(255, 255, 255, 0.5); } @keyframes snowFall { 0% { transform: translateY(-30px) translateX(0) rotate(0deg); opacity: 0; } 8% { opacity: 1; } 50% { transform: translateY(80px) translateX(var(--drift)) rotate(180deg); opacity: 0.9; } 88% { opacity: 1; } 100% { transform: translateY(175px) translateX(var(--drift-end)) rotate(360deg); opacity: 0; } } /* 卡片上的微光扫过效果 */ .weather-card::after { content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; background: radial-gradient(ellipse at center, rgba(255, 255, 255, 0.06) 0%, transparent 60%); pointer-events: none; z-index: 4; opacity: 0; transition: opacity 0.6s ease; border-radius: inherit; } .weather-card:hover::after { opacity: 1; } /* 响应式 */ @media (max-width: 768px) { .cards-grid { gap: 14px; } .weather-card { width: 250px; height: 170px; border-radius: 18px; } .card-temp { font-size: 2.2rem; } .card-city { font-size: 0.75rem; } .card-weather-label { font-size: 0.65rem; } .header-title { font-size: 1.35rem; } .card-info-bar { padding: 0 14px; } .sun-container { left: 35%; transform: translate(-50%, -50%) scale(0.8); } .sun-core { width: 36px; height: 36px; } } @media (max-width: 480px) { .weather-card { width: 210px; height: 148px; border-radius: 16px; } .card-temp { font-size: 1.8rem; } .card-city { font-size: 0.7rem; } .card-weather-label { font-size: 0.6rem; } .cards-grid { gap: 10px; } .header { margin-bottom: 16px; } .header-title { font-size: 1.2rem; } .temp-toggle-btn { padding: 8px 14px; font-size: 0.78rem; } .sun-container { left: 32%; transform: translate(-50%, -50%) scale(0.65); } .card-info-bar { padding: 0 10px; } } </style> </head> <body> <!-- 背景光斑 --> <div class="bg-layer"> <div class="bg-orb orb-1"></div> <div class="bg-orb orb-2"></div> <div class="bg-orb orb-3"></div> <div class="bg-orb orb-4"></div> <div class="bg-orb orb-5"></div> </div> <!-- 主容器 --> <div class="main-container"> <!-- 标题栏 --> <div class="header"> <div> <div class="header-title">🌤️ 天气预览</div> <div class="header-subtitle">今日天气概览 · 四城气象</div> </div> <button class="temp-toggle-btn" id="tempToggle" title="切换温度单位"> <span class="toggle-icon">🌡️</span> <span id="toggleLabel">切换 °F</span> </button> </div> <!-- 卡片横向滚动区 --> <div class="cards-scroll-container"> <div class="cards-grid" id="cardsGrid"> <!-- 卡片由JS动态生成 --> </div> </div> </div> <script> (function() { // ============ 数据配置 ============ const weatherData = [{ id: 'sunny', type: 'sunny', city: '三亚', tempC: 29, label: '晴朗', skyClass: 'sky-sunny', icon: '☀️', }, { id: 'windy', type: 'windy', city: '青岛', tempC: 16, label: '大风', skyClass: 'sky-windy', icon: '💨', }, { id: 'rain', type: 'rain', city: '广州', tempC: 24, label: '暴雨', skyClass: 'sky-rain', icon: '🌧️', }, { id: 'snow', type: 'snow', city: '哈尔滨', tempC: -15, label: '暴雪', skyClass: 'sky-snow', icon: '❄️', }, ]; let isCelsius = true; let selectedCardId = null; // ============ DOM引用 ============ const cardsGrid = document.getElementById('cardsGrid'); const tempToggleBtn = document.getElementById('tempToggle'); const toggleLabel = document.getElementById('toggleLabel'); // ============ 温度转换 ============ function cToF(c) { return Math.round(c * 9 / 5 + 32); } function getDisplayTemp(data) { return isCelsius ? data.tempC : cToF(data.tempC); } function getTempUnit() { return isCelsius ? '°C' : '°F'; } // ============ 构建卡片 ============ function buildCards() { cardsGrid.innerHTML = ''; weatherData.forEach((data, index) => { const card = document.createElement('div'); card.className = 'weather-card'; card.dataset.type = data.type; card.dataset.id = data.id; card.dataset.index = index; if (data.id === selectedCardId) { card.classList.add('selected'); } const displayTemp = getDisplayTemp(data); const tempUnit = getTempUnit(); card.innerHTML = ` <div class="card-sky ${data.skyClass}" data-sky></div> <div class="card-info-bar"> <div class="card-info-left"> <span class="card-city">${data.icon} ${data.city}</span> <span class="card-weather-label">${data.label}</span> </div> <div class="card-temp"> ${displayTemp}<span class="card-temp-unit">${tempUnit}</span> </div> </div> `; // 点击事件 card.addEventListener('click', () => { handleCardClick(card, data); }); cardsGrid.appendChild(card); }); // 重建后初始化天气动画 initAllAnimations(); // 恢复选中状态 if (selectedCardId) { const selectedCard = cardsGrid.querySelector(`[data-id="${selectedCardId}"]`); if (selectedCard) { selectedCard.classList.add('selected'); } } } // ============ 卡片点击 ============ function handleCardClick(card, data) { // 移除其他卡片的选中状态 const allCards = cardsGrid.querySelectorAll('.weather-card'); allCards.forEach(c => c.classList.remove('selected')); if (selectedCardId === data.id) { // 取消选中 selectedCardId = null; } else { // 选中当前 selectedCardId = data.id; card.classList.add('selected'); // 添加短暂的脉冲动画 card.style.transition = 'all 0.15s ease'; card.style.transform = 'translateY(-8px) scale(1.03)'; setTimeout(() => { card.style.transition = 'all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)'; card.style.transform = ''; if (selectedCardId === data.id) { card.classList.add('selected'); } }, 150); } // 滚动卡片到可见位置 card.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); } // ============ 温度切换 ============ function toggleTemperature() { isCelsius = !isCelsius; toggleLabel.textContent = isCelsius ? '切换 °F' : '切换 °C'; updateAllTemperatures(); // 按钮动画 tempToggleBtn.style.transform = 'scale(0.94)'; setTimeout(() => { tempToggleBtn.style.transform = ''; }, 180); } function updateAllTemperatures() { const allCards = cardsGrid.querySelectorAll('.weather-card'); allCards.forEach(card => { const index = parseInt(card.dataset.index); if (index >= 0 && index < weatherData.length) { const data = weatherData[index]; const tempEl = card.querySelector('.card-temp'); if (tempEl) { const displayTemp = getDisplayTemp(data); const tempUnit = getTempUnit(); tempEl.innerHTML = `${displayTemp}<span class="card-temp-unit">${tempUnit}</span>`; // 微动画 tempEl.style.transform = 'scale(1.08)'; tempEl.style.color = 'rgba(255,255,255,1)'; setTimeout(() => { tempEl.style.transform = 'scale(1)'; tempEl.style.color = '#ffffff'; }, 200); } } }); } tempToggleBtn.addEventListener('click', toggleTemperature); // ============ 键盘快捷键 ============ document.addEventListener('keydown', (e) => { if (e.key === 't' || e.key === 'T') { toggleTemperature(); } if (e.key === 'Escape') { selectedCardId = null; const allCards = cardsGrid.querySelectorAll('.weather-card'); allCards.forEach(c => c.classList.remove('selected')); } // 数字键1-4选择卡片 const num = parseInt(e.key); if (num >= 1 && num <= 4) { const card = cardsGrid.querySelector(`[data-index="${num - 1}"]`); if (card) { const data = weatherData[num - 1]; handleCardClick(card, data); } } }); // ============ 天气动画初始化 ============ function initAllAnimations() { initSunnyAnimations(); initWindyAnimations(); initRainAnimations(); initSnowAnimations(); } // --- 晴天动画 --- function initSunnyAnimations() { const sunnyCard = cardsGrid.querySelector('[data-type="sunny"]'); if (!sunnyCard) return; const sky = sunnyCard.querySelector('[data-sky]'); if (!sky) return; // 移除旧粒子 sky.querySelectorAll('.sun-particle').forEach(el => el.remove()); // 太阳容器 let sunContainer = sky.querySelector('.sun-container'); if (!sunContainer) { sunContainer = document.createElement('div'); sunContainer.className = 'sun-container'; sunContainer.innerHTML = ` <div class="sun-core"></div> <div class="sun-ring ring-1"></div> <div class="sun-ring ring-2"></div> <div class="sun-ring ring-3"></div> `; sky.appendChild(sunContainer); } // 阳光粒子 for (let i = 0; i < 14; i++) { const particle = document.createElement('div'); particle.className = 'sun-particle'; const angle = Math.random() * Math.PI * 2; const distance = 40 + Math.random() * 55; const px = Math.cos(angle) * distance; const py = Math.sin(angle) * distance; const pxEnd = Math.cos(angle) * (distance + 30); const pyEnd = Math.sin(angle) * (distance + 30); particle.style.setProperty('--px', px + 'px'); particle.style.setProperty('--py', py + 'px'); particle.style.setProperty('--px-end', pxEnd + 'px'); particle.style.setProperty('--py-end', pyEnd + 'px'); particle.style.animationDelay = Math.random() * 2.5 + 's'; particle.style.animationDuration = (2 + Math.random() * 3) + 's'; particle.style.width = (2.5 + Math.random() * 4) + 'px'; particle.style.height = particle.style.width; sky.appendChild(particle); } } // --- 大风动画 --- function initWindyAnimations() { const windyCard = cardsGrid.querySelector('[data-type="windy"]'); if (!windyCard) return; const sky = windyCard.querySelector('[data-sky]'); if (!sky) return; // 移除旧元素 sky.querySelectorAll('.wind-line, .wind-debris').forEach(el => el.remove()); // 风线 const windLineConfigs = [ { top: '22%', width: '60px', duration: 2.2, delay: 0, cls: 'thick' }, { top: '35%', width: '90px', duration: 1.6, delay: 0.6, cls: '' }, { top: '48%', width: '50px', duration: 2.8, delay: 1.2, cls: 'thin' }, { top: '55%', width: '75px', duration: 1.9, delay: 0.3, cls: '' }, { top: '68%', width: '100px', duration: 2.5, delay: 1.8, cls: 'thin' }, { top: '40%', width: '55px', duration: 3.1, delay: 2.2, cls: 'thick' }, { top: '28%', width: '70px', duration: 1.4, delay: 1.5, cls: '' }, { top: '60%', width: '85px', duration: 2.0, delay: 0.9, cls: 'thin' }, ]; windLineConfigs.forEach(config => { const line = document.createElement('div'); line.className = 'wind-line'; if (config.cls) line.classList.add(config.cls); line.style.top = config.top; line.style.width = config.width; line.style.left = '-80px'; line.style.animationDuration = config.duration + 's'; line.style.animationDelay = config.delay + 's'; sky.appendChild(line); }); // 碎屑 for (let i = 0; i < 10; i++) { const debris = document.createElement('div'); debris.className = 'wind-debris'; debris.style.top = (18 + Math.random() * 55) + '%'; debris.style.width = (3 + Math.random() * 6) + 'px'; debris.style.height = debris.style.width; debris.style.left = '-40px'; debris.style.animationDuration = (1.5 + Math.random() * 2.5) + 's'; debris.style.animationDelay = Math.random() * 3 + 's'; debris.style.opacity = (0.4 + Math.random() * 0.5); sky.appendChild(debris); } } // --- 暴雨动画 --- function initRainAnimations() { const rainCard = cardsGrid.querySelector('[data-type="rain"]'); if (!rainCard) return; const sky = rainCard.querySelector('[data-sky]'); if (!sky) return; // 移除旧雨滴 sky.querySelectorAll('.rain-drop, .rain-splash').forEach(el => el.remove()); // 雨滴 const dropCount = 35; for (let i = 0; i < dropCount; i++) { const drop = document.createElement('div'); drop.className = 'rain-drop'; drop.style.left = (Math.random() * 92) + '%'; drop.style.height = (8 + Math.random() * 18) + 'px'; drop.style.width = (1.2 + Math.random() * 1.6) + 'px'; drop.style.animationDuration = (0.5 + Math.random() * 0.9) + 's'; drop.style.animationDelay = Math.random() * 2 + 's'; drop.style.opacity = (0.6 + Math.random() * 0.4); sky.appendChild(drop); } // 溅落点 for (let i = 0; i < 8; i++) { const splash = document.createElement('div'); splash.className = 'rain-splash'; splash.style.left = (5 + Math.random() * 85) + '%'; splash.style.setProperty('--splash-delay', Math.random() * 1.5 + 's'); splash.style.animationDuration = (0.5 + Math.random() * 0.7) + 's'; sky.appendChild(splash); } } // --- 暴雪动画 --- function initSnowAnimations() { const snowCard = cardsGrid.querySelector('[data-type="snow"]'); if (!snowCard) return; const sky = snowCard.querySelector('[data-sky]'); if (!sky) return; // 移除旧雪花 sky.querySelectorAll('.snow-flake').forEach(el => el.remove()); const flakeCount = 30; for (let i = 0; i < flakeCount; i++) { const flake = document.createElement('div'); flake.className = 'snow-flake'; flake.style.left = (Math.random() * 90) + '%'; const size = (3 + Math.random() * 10) + 'px'; flake.style.width = size; flake.style.height = size; flake.style.animationDuration = (3 + Math.random() * 5) + 's'; flake.style.animationDelay = Math.random() * 4 + 's'; flake.style.setProperty('--drift', (Math.random() * 30 - 15) + 'px'); flake.style.setProperty('--drift-end', (Math.random() * 40 - 20) + 'px'); flake.style.opacity = (0.55 + Math.random() * 0.45); flake.style.boxShadow = `0 0 ${4 + Math.random() * 8}px ${1 + Math.random() * 3}px rgba(255,255,255,${0.3 + Math.random() * 0.5})`; sky.appendChild(flake); } } // ============ 初始化 ============ function init() { buildCards(); // 默认选中第一张卡片(晴天) setTimeout(() => { const firstCard = cardsGrid.querySelector('[data-index="0"]'); if (firstCard) { selectedCardId = 'sunny'; firstCard.classList.add('selected'); } }, 300); } // ============ 窗口大小变化时重新初始化动画 ============ let resizeDebounce; window.addEventListener('resize', () => { clearTimeout(resizeDebounce); resizeDebounce = setTimeout(() => { initAllAnimations(); }, 400); }); // ============ 启动 ============ init(); console.log('🌤️ iOS 18 风格天气卡片已就绪'); console.log(' 🖱️ 点击卡片选中 | 再次点击取消选中'); console.log(' 🌡️ 点击右上角按钮切换 °C/°F'); console.log(' ⌨️ 键盘快捷键: 1-4 选择卡片 | T 切换温度 | Esc 取消选中'); console.log(' 📱 支持横向滚动查看所有卡片'); })(); </script> </body> </html>

image878×670 60.5 KB
设计一个现代、极简风格的天气应用UI界面卡片,要求所有代码存放至一个html文件内 使用浅色模式,背景为白色,带有柔和的圆角、毛玻璃效果和轻微的投影。 布局清晰,顶部是地点和日期信息。 左侧主区域显示当前天气,包含一个色彩柔和的大图标(晴转多云)和一个非常大的温度读数(28°)。 右侧用一个2x2的网格展示湿度、风速等次要数据。 中间部分用三个水平排列的彩色(淡黄、淡橙、淡紫)卡片分别展示紫外线指数、日出和日落时间。 底部是小时天气预报,以水平卡片列表的形式呈现。 整体设计语言干净,使用无衬线字体,图标风格为简约的线形或双色调图标,色彩方案柔和、饱和度低,给人清新舒适的感觉。UI, UX design, weather app, dashboard, data visualization, minimalist, clean, light mode, pastel colors, soft shadow, rounded corners, grid layout.

网友解答:
--【壹】--:

粗的一看感觉还可以,一细看这个太阳跟个乒乓球一样


--【贰】--:

社区怎么上传视频啊,找了半天找不到上传视频的入口


--【叁】--: Mrzqd:

以 iOS 18 的设计风格做一个带有动画效果的天气卡片,要求是使用 HTML、CSS 和基础 JavaScript,使用横板天气页面(拥有 4 个天气卡片 (晴天,大风,暴雨,暴雪))。应足够美观,实现一定的交互效果。

参考佬友的提示词并稍作修改:

以 iOS 26 的设计风格做一个带有动画效果的天气卡片,要求是使用 HTML、CSS 和基础 JavaScript,使用横板天气页面(拥有 4 个天气卡片 (晴天,大风,暴雨,暴雪))。应足够美观,实现一定的交互效果,体现月薪2万美元的程序员价值

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>iOS 26 风格 · 天气卡片</title> <style> :root { --bg-deep: #0a0e17; --bg-mid: #111827; --text-primary: #f5f5f7; --text-secondary: #b0b0b8; --text-tertiary: #7b7b86; --glass-border: rgba(255, 255, 255, 0.12); --glass-highlight: rgba(255, 255, 255, 0.06); --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3); --shadow-md: 0 8px 32px rgba(0, 0, 0, 0.4); --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.55); --spring-ease: cubic-bezier(0.34, 1.56, 0.64, 1); --smooth-ease: cubic-bezier(0.25, 0.1, 0.25, 1); --card-width: 280px; --card-height: 430px; --card-radius: 28px; --transition-card: 0.55s var(--spring-ease); --transition-subtle: 0.35s var(--smooth-ease); } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', 'PingFang SC', sans-serif; background: var(--bg-deep); min-height: 100vh; display: flex; align-items: center; justify-content: center; overflow-x: hidden; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent; } /* 背景光晕 */ .bg-aurora { position: fixed; inset: 0; pointer-events: none; z-index: 0; overflow: hidden; } .bg-aurora .orb { position: absolute; border-radius: 50%; filter: blur(120px); opacity: 0.18; animation: orbDrift 20s ease-in-out infinite; } .bg-aurora .orb:nth-child(1) { width: 600px; height: 600px; background: radial-gradient(circle, #ff9a56 0%, transparent 70%); top: -15%; left: -10%; animation-delay: 0s; } .bg-aurora .orb:nth-child(2) { width: 500px; height: 500px; background: radial-gradient(circle, #5b8def 0%, transparent 70%); bottom: -20%; right: -8%; animation-delay: -7s; animation-duration: 24s; } .bg-aurora .orb:nth-child(3) { width: 400px; height: 400px; background: radial-gradient(circle, #b8d4e3 0%, transparent 70%); top: 40%; left: 55%; animation-delay: -14s; animation-duration: 18s; } @keyframes orbDrift { 0%, 100% { transform: translate(0, 0) scale(1); } 25% { transform: translate(40px, -30px) scale(1.08); } 50% { transform: translate(-20px, 25px) scale(0.94); } 75% { transform: translate(-35px, -15px) scale(1.05); } } /* 主容器 */ .main-container { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; gap: 28px; padding: 20px; width: 100%; max-width: 1300px; } /* 顶部标题栏 */ .header { display: flex; align-items: center; justify-content: space-between; width: 100%; max-width: 1240px; padding: 0 10px; } .header .title-group { display: flex; align-items: baseline; gap: 12px; } .header .title { font-size: 2.2rem; font-weight: 700; color: var(--text-primary); letter-spacing: -0.02em; line-height: 1; } .header .subtitle { font-size: 0.95rem; font-weight: 500; color: var(--text-tertiary); letter-spacing: 0.01em; } .header .location-badge { display: flex; align-items: center; gap: 7px; padding: 10px 18px; background: rgba(255, 255, 255, 0.07); border: 1px solid var(--glass-border); border-radius: 100px; color: var(--text-secondary); font-size: 0.9rem; font-weight: 500; backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); transition: all var(--transition-subtle); cursor: pointer; letter-spacing: 0.01em; } .header .location-badge:hover { background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 255, 255, 0.25); color: #fff; transform: translateY(-1px); box-shadow: var(--shadow-sm); } .header .location-dot { width: 7px; height: 7px; border-radius: 50%; background: #34c759; box-shadow: 0 0 10px #34c75988; animation: pulseDot 2s ease-in-out infinite; } @keyframes pulseDot { 0%, 100% { box-shadow: 0 0 8px #34c75966; } 50% { box-shadow: 0 0 18px #34c759bb; } } /* 卡片网格 */ .cards-grid { display: flex; gap: 20px; flex-wrap: nowrap; justify-content: center; width: 100%; perspective: 1200px; } /* 天气卡片 */ .weather-card { position: relative; width: var(--card-width); min-width: var(--card-width); height: var(--card-height); border-radius: var(--card-radius); background: rgba(255, 255, 255, 0.06); border: 1px solid var(--glass-border); backdrop-filter: blur(24px) saturate(160%); -webkit-backdrop-filter: blur(24px) saturate(160%); box-shadow: var(--shadow-md), inset 0 1px 0 rgba(255, 255, 255, 0.08); cursor: pointer; transition: all var(--transition-card); display: flex; flex-direction: column; align-items: center; overflow: hidden; z-index: 1; transform-origin: center center; -webkit-tap-highlight-color: transparent; } .weather-card::before { content: ''; position: absolute; inset: 0; border-radius: var(--card-radius); background: linear-gradient(180deg, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.01) 30%, rgba(255, 255, 255, 0.0) 60%, rgba(0, 0, 0, 0.05) 100%); pointer-events: none; z-index: 2; transition: opacity var(--transition-subtle); } .weather-card::after { content: ''; position: absolute; top: 0; left: 20px; right: 20px; height: 1px; background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.25) 20%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.25) 80%, transparent 100%); pointer-events: none; z-index: 3; border-radius: 50%; opacity: 0.7; transition: opacity var(--transition-subtle); } /* 卡片悬停效果 */ .weather-card:hover { transform: translateY(-12px) scale(1.04); box-shadow: var(--shadow-lg), inset 0 1px 0 rgba(255, 255, 255, 0.14); border-color: rgba(255, 255, 255, 0.25); z-index: 10; background: rgba(255, 255, 255, 0.1); } .weather-card:hover::after { opacity: 1; } .weather-card:active { transform: translateY(-4px) scale(0.98); transition: all 0.15s var(--smooth-ease); } /* 相邻卡片在hover时略微后退 */ .cards-grid:hover .weather-card:not(:hover) { transform: scale(0.96); opacity: 0.75; filter: brightness(0.85) blur(1px); transition: all 0.5s var(--smooth-ease); } .weather-card:hover~.weather-card { transform: scale(0.96); opacity: 0.75; filter: brightness(0.85) blur(1px); transition: all 0.5s var(--smooth-ease); } /* 卡片选中状态 */ .weather-card.selected { border-color: rgba(255, 255, 255, 0.45); box-shadow: var(--shadow-lg), 0 0 0 3px rgba(255, 255, 255, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.18); background: rgba(255, 255, 255, 0.13); z-index: 8; transform: translateY(-6px) scale(1.025); } .weather-card.selected::after { opacity: 1; background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.35) 20%, rgba(255, 255, 255, 0.55) 50%, rgba(255, 255, 255, 0.35) 80%, transparent 100%); } /* 卡片顶部彩色光晕 */ .card-glow { position: absolute; top: -40px; left: 50%; transform: translateX(-50%); width: 140px; height: 80px; border-radius: 50%; pointer-events: none; z-index: 0; filter: blur(35px); opacity: 0.4; transition: all var(--transition-card); } .weather-card:hover .card-glow { opacity: 0.7; width: 170px; height: 100px; top: -50px; filter: blur(30px); } .card-glow.sunny { background: #ff9f43; } .card-glow.windy { background: #5b8def; } .card-glow.rainstorm { background: #4a6fa5; } .card-glow.blizzard { background: #a8d8ea; } /* 城市名称 */ .card-city { position: relative; z-index: 4; margin-top: 22px; font-size: 0.85rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-tertiary); transition: color var(--transition-subtle); } .weather-card:hover .card-city, .weather-card.selected .card-city { color: var(--text-secondary); } /* Canvas 动画区域 */ .card-canvas-wrap { position: relative; z-index: 1; width: 100%; height: 170px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin: 4px 0; } .card-canvas-wrap canvas { display: block; width: 100%; height: 100%; pointer-events: none; } /* 温度显示 */ .card-temp { position: relative; z-index: 4; font-size: 4.2rem; font-weight: 200; letter-spacing: -0.04em; color: var(--text-primary); line-height: 1; transition: all var(--transition-subtle); display: flex; align-items: flex-start; gap: 2px; } .card-temp .degree { font-size: 1.6rem; font-weight: 300; margin-top: 6px; opacity: 0.8; } .weather-card:hover .card-temp { transform: scale(1.06); text-shadow: 0 0 40px rgba(255, 255, 255, 0.3); } /* 天气描述 */ .card-desc { position: relative; z-index: 4; font-size: 1rem; font-weight: 500; color: var(--text-secondary); letter-spacing: 0.02em; margin-top: 2px; transition: color var(--transition-subtle); } .weather-card:hover .card-desc, .weather-card.selected .card-desc { color: #d5d5dc; } /* 底部详情行 */ .card-details { position: relative; z-index: 4; display: flex; gap: 20px; margin-top: 14px; padding-top: 14px; border-top: 1px solid rgba(255, 255, 255, 0.08); width: 75%; justify-content: center; transition: border-color var(--transition-subtle); } .weather-card:hover .card-details, .weather-card.selected .card-details { border-top-color: rgba(255, 255, 255, 0.18); } .card-detail-item { display: flex; flex-direction: column; align-items: center; gap: 3px; } .card-detail-item .detail-value { font-size: 0.85rem; font-weight: 600; color: var(--text-secondary); letter-spacing: 0.01em; transition: color var(--transition-subtle); } .card-detail-item .detail-label { font-size: 0.68rem; font-weight: 500; color: var(--text-tertiary); letter-spacing: 0.03em; text-transform: uppercase; } .weather-card:hover .detail-value, .weather-card.selected .detail-value { color: #e0e0e6; } /* 响应式 */ @media (max-width: 1280px) { .cards-grid { gap: 14px; } :root { --card-width: 240px; --card-height: 390px; --card-radius: 24px; } .card-temp { font-size: 3.4rem; } .card-canvas-wrap { height: 140px; } .header .title { font-size: 1.7rem; } } @media (max-width: 1050px) { .cards-grid { gap: 10px; } :root { --card-width: 200px; --card-height: 350px; --card-radius: 20px; } .card-temp { font-size: 2.8rem; } .card-temp .degree { font-size: 1.2rem; margin-top: 5px; } .card-canvas-wrap { height: 115px; } .card-desc { font-size: 0.85rem; } .card-details { gap: 12px; width: 85%; } .card-detail-item .detail-value { font-size: 0.75rem; } .card-detail-item .detail-label { font-size: 0.6rem; } .header .title { font-size: 1.4rem; } .card-city { font-size: 0.7rem; margin-top: 16px; } } @media (max-width: 860px) { .main-container { padding: 10px; gap: 16px; } .cards-grid { flex-wrap: nowrap; overflow-x: auto; justify-content: flex-start; padding: 10px 6px 20px; gap: 12px; scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch; scrollbar-width: none; } .cards-grid::-webkit-scrollbar { display: none; } :root { --card-width: 210px; --card-height: 370px; --card-radius: 22px; } .weather-card { scroll-snap-align: center; flex-shrink: 0; } .card-temp { font-size: 3rem; } .card-canvas-wrap { height: 130px; } .header { padding: 0 6px; } .header .title { font-size: 1.5rem; } .cards-grid:hover .weather-card:not(:hover) { transform: scale(0.94); opacity: 0.7; filter: brightness(0.8) blur(0.5px); } } @media (max-width: 480px) { :root { --card-width: 175px; --card-height: 320px; --card-radius: 18px; } .card-temp { font-size: 2.4rem; } .card-temp .degree { font-size: 1rem; margin-top: 3px; } .card-canvas-wrap { height: 100px; } .card-desc { font-size: 0.75rem; } .card-details { gap: 8px; width: 88%; margin-top: 10px; padding-top: 10px; } .card-detail-item .detail-value { font-size: 0.7rem; } .card-detail-item .detail-label { font-size: 0.55rem; } .card-city { font-size: 0.65rem; margin-top: 12px; } .header .title { font-size: 1.2rem; } .header .subtitle { font-size: 0.7rem; } .header .location-badge { font-size: 0.7rem; padding: 7px 12px; gap: 4px; } } </style> </head> <body> <!-- 背景光晕 --> <div class="bg-aurora"> <div class="orb"></div> <div class="orb"></div> <div class="orb"></div> </div> <!-- 主容器 --> <div class="main-container"> <!-- 顶部标题 --> <header class="header"> <div class="title-group"> <span class="title">Weather</span> <span class="subtitle">实时天气</span> </div> <div class="location-badge" title="当前定位"> <span class="location-dot"></span> <span>自动定位</span> <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="opacity:0.7;"> <polyline points="6 9 12 15 18 9"></polyline> </svg> </div> </header> <!-- 卡片网格 --> <div class="cards-grid" id="cardsGrid"> <!-- 晴天卡片 --> <div class="weather-card" data-weather="sunny" data-city="Los Angeles" data-temp="28" data-feels="30" data-humidity="38" data-wind="8"> <div class="card-glow sunny"></div> <span class="card-city">Los Angeles</span> <div class="card-canvas-wrap"><canvas id="canvasSunny" aria-hidden="true"></canvas></div> <div class="card-temp">28<span class="degree">°C</span></div> <div class="card-desc">晴空万里</div> <div class="card-details"> <div class="card-detail-item"><span class="detail-value">30°</span><span class="detail-label">体感</span></div> <div class="card-detail-item"><span class="detail-value">38%</span><span class="detail-label">湿度</span></div> <div class="card-detail-item"><span class="detail-value">8km/h</span><span class="detail-label">风速</span></div> </div> </div> <!-- 大风卡片 --> <div class="weather-card" data-weather="windy" data-city="Chicago" data-temp="16" data-feels="12" data-humidity="52" data-wind="42"> <div class="card-glow windy"></div> <span class="card-city">Chicago</span> <div class="card-canvas-wrap"><canvas id="canvasWindy" aria-hidden="true"></canvas></div> <div class="card-temp">16<span class="degree">°C</span></div> <div class="card-desc">大风呼啸</div> <div class="card-details"> <div class="card-detail-item"><span class="detail-value">12°</span><span class="detail-label">体感</span></div> <div class="card-detail-item"><span class="detail-value">52%</span><span class="detail-label">湿度</span></div> <div class="card-detail-item"><span class="detail-value">42km/h</span><span class="detail-label">风速</span></div> </div> </div> <!-- 暴雨卡片 --> <div class="weather-card" data-weather="rainstorm" data-city="Tokyo" data-temp="22" data-feels="23" data-humidity="89" data-wind="25"> <div class="card-glow rainstorm"></div> <span class="card-city">Tokyo</span> <div class="card-canvas-wrap"><canvas id="canvasRainstorm" aria-hidden="true"></canvas></div> <div class="card-temp">22<span class="degree">°C</span></div> <div class="card-desc">暴雨倾盆</div> <div class="card-details"> <div class="card-detail-item"><span class="detail-value">23°</span><span class="detail-label">体感</span></div> <div class="card-detail-item"><span class="detail-value">89%</span><span class="detail-label">湿度</span></div> <div class="card-detail-item"><span class="detail-value">25km/h</span><span class="detail-label">风速</span></div> </div> </div> <!-- 暴雪卡片 --> <div class="weather-card" data-weather="blizzard" data-city="Reykjavik" data-temp="-8" data-feels="-16" data-humidity="78" data-wind="35"> <div class="card-glow blizzard"></div> <span class="card-city">Reykjavík</span> <div class="card-canvas-wrap"><canvas id="canvasBlizzard" aria-hidden="true"></canvas></div> <div class="card-temp">-8<span class="degree">°C</span></div> <div class="card-desc">暴雪纷飞</div> <div class="card-details"> <div class="card-detail-item"><span class="detail-value">-16°</span><span class="detail-label">体感</span></div> <div class="card-detail-item"><span class="detail-value">78%</span><span class="detail-label">湿度</span></div> <div class="card-detail-item"><span class="detail-value">35km/h</span><span class="detail-label">风速</span></div> </div> </div> </div> </div> <script> (function() { // ============ 工具函数 ============ const dpr = Math.min(window.devicePixelRatio || 1, 2); function setupCanvas(canvasId, width, height) { const canvas = document.getElementById(canvasId); if (!canvas) return null; const rect = canvas.parentElement.getBoundingClientRect(); const w = rect.width || width; const h = rect.height || height; canvas.width = w * dpr; canvas.height = h * dpr; canvas.style.width = w + 'px'; canvas.style.height = h + 'px'; const ctx = canvas.getContext('2d'); ctx.scale(dpr, dpr); return { canvas, ctx, w, h }; } function resizeCanvas(state) { if (!state || !state.canvas) return; const rect = state.canvas.parentElement.getBoundingClientRect(); const w = rect.width || state.w; const h = rect.height || state.h; state.w = w; state.h = h; state.canvas.width = w * dpr; state.canvas.height = h * dpr; state.canvas.style.width = w + 'px'; state.canvas.style.height = h + 'px'; state.ctx.setTransform(1, 0, 0, 1, 0, 0); state.ctx.scale(dpr, dpr); } // ============ 晴天动画 ============ function createSunnyAnimation(canvasId) { const state = setupCanvas(canvasId, 260, 170); if (!state) return; const { ctx, w, h } = state; const particles = []; const sunX = w / 2; const sunY = h * 0.45; const sunRadius = Math.min(w, h) * 0.22; // 光粒子 for (let i = 0; i < 45; i++) { particles.push({ x: Math.random() * w, y: Math.random() * h, r: Math.random() * 2.2 + 0.8, speedX: (Math.random() - 0.5) * 0.5, speedY: (Math.random() - 0.5) * 0.4 - 0.3, alpha: Math.random() * 0.6 + 0.2, phase: Math.random() * Math.PI * 2, twinkleSpeed: Math.random() * 0.03 + 0.01, }); } const rayCount = 10; let rotationAngle = 0; function drawSunRays(cx, cy, radius, angle) { ctx.save(); ctx.translate(cx, cy); ctx.rotate(angle); for (let i = 0; i < rayCount; i++) { const rayAngle = (i / rayCount) * Math.PI * 2; const innerR = radius * 1.05; const outerR = radius * 1.55 + Math.sin(Date.now() * 0.003 + i) * radius * 0.2; const x1 = Math.cos(rayAngle) * innerR; const y1 = Math.sin(rayAngle) * innerR; const x2 = Math.cos(rayAngle) * outerR; const y2 = Math.sin(rayAngle) * outerR; const grad = ctx.createLinearGradient(x1, y1, x2, y2); grad.addColorStop(0, 'rgba(255,200,120,0.7)'); grad.addColorStop(0.5, 'rgba(255,180,90,0.35)'); grad.addColorStop(1, 'rgba(255,150,60,0)'); ctx.strokeStyle = grad; ctx.lineWidth = 3 + Math.sin(Date.now() * 0.004 + i) * 1.5; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); } ctx.restore(); } function drawGlow(cx, cy, radius) { const glowGrad = ctx.createRadialGradient(cx, cy, radius * 0.3, cx, cy, radius * 2.4); glowGrad.addColorStop(0, 'rgba(255,240,210,0.9)'); glowGrad.addColorStop(0.25, 'rgba(255,200,140,0.55)'); glowGrad.addColorStop(0.5, 'rgba(255,160,80,0.2)'); glowGrad.addColorStop(0.75, 'rgba(255,120,40,0.05)'); glowGrad.addColorStop(1, 'rgba(255,100,30,0)'); ctx.fillStyle = glowGrad; ctx.beginPath(); ctx.arc(cx, cy, radius * 2.4, 0, Math.PI * 2); ctx.fill(); } function animate() { ctx.clearRect(0, 0, w, h); rotationAngle += 0.008; // 光晕 drawGlow(sunX, sunY, sunRadius); // 太阳本体 const sunGrad = ctx.createRadialGradient(sunX, sunY, sunRadius * 0.1, sunX, sunY, sunRadius); sunGrad.addColorStop(0, '#fffef5'); sunGrad.addColorStop(0.3, '#ffecc8'); sunGrad.addColorStop(0.6, '#ffc878'); sunGrad.addColorStop(0.85, '#ffa040'); sunGrad.addColorStop(1, '#ff8830'); ctx.fillStyle = sunGrad; ctx.beginPath(); ctx.arc(sunX, sunY, sunRadius, 0, Math.PI * 2); ctx.fill(); // 光线 drawSunRays(sunX, sunY, sunRadius, rotationAngle); // 内核高光 const coreGrad = ctx.createRadialGradient(sunX - sunRadius * 0.2, sunY - sunRadius * 0.2, sunRadius * 0.05, sunX, sunY, sunRadius * 0.7); coreGrad.addColorStop(0, 'rgba(255,255,255,0.85)'); coreGrad.addColorStop(1, 'rgba(255,255,255,0)'); ctx.fillStyle = coreGrad; ctx.beginPath(); ctx.arc(sunX, sunY, sunRadius * 0.7, 0, Math.PI * 2); ctx.fill(); // 光粒子 particles.forEach(p => { p.x += p.speedX; p.y += p.speedY; p.alpha += Math.sin(Date.now() * p.twinkleSpeed + p.phase) * 0.015; p.alpha = Math.max(0.08, Math.min(0.85, p.alpha)); if (p.y < -10) { p.y = h + 10; p.x = Math.random() * w; } if (p.y > h + 10) { p.y = -10; p.x = Math.random() * w; } if (p.x < -10) p.x = w + 10; if (p.x > w + 10) p.x = -10; ctx.fillStyle = `rgba(255,230,180,${p.alpha})`; ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); ctx.fill(); // 微光晕 ctx.fillStyle = `rgba(255,220,160,${p.alpha * 0.35})`; ctx.beginPath(); ctx.arc(p.x, p.y, p.r * 3, 0, Math.PI * 2); ctx.fill(); }); state._animId = requestAnimationFrame(animate); } state._animId = requestAnimationFrame(animate); return state; } // ============ 大风动画 ============ function createWindyAnimation(canvasId) { const state = setupCanvas(canvasId, 260, 170); if (!state) return; const { ctx, w, h } = state; const windLines = []; const debris = []; for (let i = 0; i < 22; i++) { windLines.push({ x: Math.random() * w, y: Math.random() * h, length: Math.random() * 90 + 40, speed: Math.random() * 4 + 2.5, alpha: Math.random() * 0.4 + 0.15, thickness: Math.random() * 1.8 + 0.6, waveAmp: Math.random() * 12 + 4, waveFreq: Math.random() * 0.04 + 0.02, phase: Math.random() * Math.PI * 2, }); } for (let i = 0; i < 14; i++) { debris.push({ x: Math.random() * w, y: Math.random() * h, r: Math.random() * 2.5 + 1, speed: Math.random() * 3.5 + 1.5, alpha: Math.random() * 0.6 + 0.25, wobble: Math.random() * Math.PI * 2, wobbleSpeed: Math.random() * 0.06 + 0.03, }); } function animate() { ctx.clearRect(0, 0, w, h); const time = Date.now() * 0.001; // 风线 windLines.forEach(line => { line.x += line.speed; if (line.x - line.length > w + 20) { line.x = -line.length - 10; line.y = Math.random() * h; } const waveY = Math.sin(time * line.waveFreq * 15 + line.phase) * line.waveAmp; const startX = line.x; const startY = line.y + waveY; const endX = line.x - line.length; const endY = line.y + waveY * 0.6; const grad = ctx.createLinearGradient(startX, startY, endX, endY); grad.addColorStop(0, `rgba(200,215,235,${line.alpha})`); grad.addColorStop(1, `rgba(180,200,225,0)`); ctx.strokeStyle = grad; ctx.lineWidth = line.thickness; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(startX, startY); ctx.quadraticCurveTo( (startX + endX) / 2, (startY + endY) / 2 + waveY * 0.5, endX, endY ); ctx.stroke(); }); // 碎片 debris.forEach(d => { d.x += d.speed; d.wobble += d.wobbleSpeed; d.y += Math.sin(d.wobble) * 0.7; if (d.x > w + 20) { d.x = -15; d.y = Math.random() * h; } ctx.fillStyle = `rgba(210,220,235,${d.alpha})`; ctx.beginPath(); ctx.arc(d.x, d.y, d.r, 0, Math.PI * 2); ctx.fill(); // 拖尾 ctx.fillStyle = `rgba(200,210,225,${d.alpha * 0.3})`; ctx.beginPath(); ctx.arc(d.x - d.r * 1.5, d.y, d.r * 1.4, 0, Math.PI * 2); ctx.fill(); }); state._animId = requestAnimationFrame(animate); } state._animId = requestAnimationFrame(animate); return state; } // ============ 暴雨动画 ============ function createRainstormAnimation(canvasId) { const state = setupCanvas(canvasId, 260, 170); if (!state) return; const { ctx, w, h } = state; const raindrops = []; const splashParticles = []; let lightningTimer = 0; let lightningAlpha = 0; let lightningFlash = 0; for (let i = 0; i < 130; i++) { raindrops.push({ x: Math.random() * w, y: Math.random() * h - h, length: Math.random() * 16 + 8, speed: Math.random() * 8 + 10, thickness: Math.random() * 1.3 + 0.4, alpha: Math.random() * 0.5 + 0.35, }); } function triggerLightning() { lightningAlpha = 0.7 + Math.random() * 0.3; lightningFlash = 1; lightningTimer = 0.06 + Math.random() * 0.12; } function animate(timestamp) { ctx.clearRect(0, 0, w, h); const dt = Math.min(0.05, (timestamp - (state._lastTime || timestamp)) / 1000); state._lastTime = timestamp; // 闪电计时器 if (lightningFlash > 0) { lightningFlash -= dt; lightningAlpha *= 0.8; if (lightningFlash <= 0) lightningAlpha = 0; } lightningTimer -= dt; if (lightningTimer <= 0 && Math.random() < 0.015) { triggerLightning(); } // 闪电照亮背景 if (lightningAlpha > 0.01) { const flashGrad = ctx.createRadialGradient(w * 0.55, h * 0.2, w * 0.1, w * 0.5, h * 0.5, w * 0.9); flashGrad.addColorStop(0, `rgba(255,255,240,${lightningAlpha * 0.5})`); flashGrad.addColorStop(1, 'rgba(255,255,240,0)'); ctx.fillStyle = flashGrad; ctx.fillRect(0, 0, w, h); // 闪电线条 ctx.strokeStyle = `rgba(255,255,240,${lightningAlpha})`; ctx.lineWidth = 2.5; ctx.lineCap = 'round'; ctx.shadowColor = `rgba(255,255,240,${lightningAlpha})`; ctx.shadowBlur = 18; ctx.beginPath(); let lx = w * 0.55; let ly = -5; ctx.moveTo(lx, ly); for (let i = 0; i < 6; i++) { lx += (Math.random() - 0.45) * 50; ly += h / 5.5 + Math.random() * 10; ctx.lineTo(lx, ly); } ctx.stroke(); ctx.shadowBlur = 0; } // 雨滴 raindrops.forEach(drop => { drop.y += drop.speed; drop.x += (Math.random() - 0.5) * 0.6; if (drop.y > h + 10) { drop.y = -drop.length - Math.random() * 30; drop.x = Math.random() * w; // 溅起水花 if (Math.random() < 0.3 && drop.y > h - 5) { for (let s = 0; s < 3; s++) { splashParticles.push({ x: drop.x, y: h - Math.random() * 6, vx: (Math.random() - 0.5) * 3, vy: -(Math.random() * 3 + 1), life: 0.5 + Math.random() * 0.4, r: Math.random() * 1.5 + 0.5, alpha: 0.7, }); } } } const grad = ctx.createLinearGradient(drop.x, drop.y, drop.x, drop.y + drop.length); grad.addColorStop(0, `rgba(180,200,225,${drop.alpha})`); grad.addColorStop(1, `rgba(140,170,210,${drop.alpha * 0.3})`); ctx.strokeStyle = grad; ctx.lineWidth = drop.thickness; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(drop.x, drop.y); ctx.lineTo(drop.x, drop.y + drop.length); ctx.stroke(); }); // 水花粒子 for (let i = splashParticles.length - 1; i >= 0; i--) { const sp = splashParticles[i]; sp.x += sp.vx; sp.y += sp.vy; sp.vy += 0.25; sp.life -= dt; sp.alpha = sp.life / 0.9; if (sp.life <= 0 || sp.y > h + 5) { splashParticles.splice(i, 1); continue; } ctx.fillStyle = `rgba(190,210,230,${sp.alpha})`; ctx.beginPath(); ctx.arc(sp.x, sp.y, sp.r, 0, Math.PI * 2); ctx.fill(); } // 限制水花数量 if (splashParticles.length > 60) splashParticles.splice(0, splashParticles.length - 60); state._animId = requestAnimationFrame(animate); } state._lastTime = performance.now(); state._animId = requestAnimationFrame(animate); return state; } // ============ 暴雪动画 ============ function createBlizzardAnimation(canvasId) { const state = setupCanvas(canvasId, 260, 170); if (!state) return; const { ctx, w, h } = state; const snowflakes = []; const snowAccumulation = []; const maxAccum = 70; for (let i = 0; i < 75; i++) { snowflakes.push({ x: Math.random() * w, y: Math.random() * h - h, r: Math.random() * 3.5 + 1.2, speedY: Math.random() * 1.6 + 0.6, speedX: Math.random() * 2.5 + 1.2, alpha: Math.random() * 0.7 + 0.3, wobbleAmp: Math.random() * 1.8 + 0.4, wobbleFreq: Math.random() * 0.05 + 0.02, phase: Math.random() * Math.PI * 2, crystal: Math.random() < 0.35, }); } function animate(timestamp) { ctx.clearRect(0, 0, w, h); const time = timestamp * 0.001; // 积雪 if (snowAccumulation.length < maxAccum && Math.random() < 0.25) { snowAccumulation.push({ x: Math.random() * w, y: h - Math.random() * 8, r: Math.random() * 4 + 1.5, alpha: 0.7 + Math.random() * 0.3, }); } if (snowAccumulation.length > maxAccum) { snowAccumulation.splice(0, snowAccumulation.length - maxAccum); } // 绘制积雪 snowAccumulation.forEach(sa => { ctx.fillStyle = `rgba(235,245,255,${sa.alpha})`; ctx.beginPath(); ctx.arc(sa.x, sa.y, sa.r, 0, Math.PI * 2); ctx.fill(); }); // 积雪连成片 ctx.fillStyle = 'rgba(225,238,250,0.5)'; ctx.beginPath(); const baseY = h - 2; ctx.moveTo(0, baseY); for (let x = 0; x <= w; x += 8) { const yOff = Math.sin(x * 0.06 + time * 0.3) * 5 + Math.cos(x * 0.13) * 3; ctx.lineTo(x, baseY - 6 + yOff); } ctx.lineTo(w, h + 5); ctx.lineTo(0, h + 5); ctx.closePath(); ctx.fill(); // 雪花 snowflakes.forEach(sf => { sf.y += sf.speedY; sf.x += sf.speedX + Math.sin(time * sf.wobbleFreq * 10 + sf.phase) * sf.wobbleAmp; if (sf.y > h + 10) { sf.y = -sf.r * 3 - Math.random() * 20; sf.x = Math.random() * w; } if (sf.x > w + 15) sf.x = -15; if (sf.x < -15) sf.x = w + 15; ctx.fillStyle = `rgba(240,245,255,${sf.alpha})`; ctx.beginPath(); if (sf.crystal) { // 小星形雪花 const cx = sf.x; const cy = sf.y; const rr = sf.r; for (let j = 0; j < 6; j++) { const angle = (j / 6) * Math.PI * 2 + time * 0.4; const tipX = cx + Math.cos(angle) * rr; const tipY = cy + Math.sin(angle) * rr; const midX = cx + Math.cos(angle + 0.35) * rr * 0.4; const midY = cy + Math.sin(angle + 0.35) * rr * 0.4; if (j === 0) ctx.moveTo(tipX, tipY); else ctx.lineTo(tipX, tipY); ctx.lineTo(midX, midY); } ctx.closePath(); ctx.fill(); ctx.fillStyle = `rgba(255,255,255,${sf.alpha * 0.6})`; ctx.beginPath(); ctx.arc(sf.x, sf.y, sf.r * 0.45, 0, Math.PI * 2); ctx.fill(); } else { ctx.arc(sf.x, sf.y, sf.r, 0, Math.PI * 2); ctx.fill(); // 微光 ctx.fillStyle = `rgba(255,255,255,${sf.alpha * 0.35})`; ctx.beginPath(); ctx.arc(sf.x, sf.y, sf.r * 2.2, 0, Math.PI * 2); ctx.fill(); } }); state._animId = requestAnimationFrame(animate); } state._animId = requestAnimationFrame(animate); return state; } // ============ 初始化所有动画 ============ const animationStates = {}; function initAllAnimations() { // 清理旧动画 Object.values(animationStates).forEach(s => { if (s && s._animId) cancelAnimationFrame(s._animId); }); animationStates.sunny = createSunnyAnimation('canvasSunny'); animationStates.windy = createWindyAnimation('canvasWindy'); animationStates.rainstorm = createRainstormAnimation('canvasRainstorm'); animationStates.blizzard = createBlizzardAnimation('canvasBlizzard'); } initAllAnimations(); // ============ 响应式处理 ============ let resizeTimeout; function handleResize() { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { Object.values(animationStates).forEach(s => { if (s && s.canvas) resizeCanvas(s); }); }, 250); } window.addEventListener('resize', handleResize); window.addEventListener('orientationchange', () => setTimeout(handleResize, 400)); // ============ 卡片交互 ============ const cardsGrid = document.getElementById('cardsGrid'); const allCards = cardsGrid.querySelectorAll('.weather-card'); allCards.forEach(card => { // 点击选中/取消 card.addEventListener('click', function(e) { const wasSelected = this.classList.contains('selected'); // 取消所有选中 allCards.forEach(c => c.classList.remove('selected')); // 如果之前未选中,则选中当前卡片 if (!wasSelected) { this.classList.add('selected'); // 轻微振动反馈模拟(视觉) this.style.transition = 'all 0.12s cubic-bezier(0.34,1.56,0.64,1)'; this.style.transform = 'translateY(-10px) scale(1.05)'; setTimeout(() => { this.style.transition = ''; this.style.transform = ''; }, 150); } }); // 触摸设备的长按效果 let pressTimer; card.addEventListener('touchstart', function() { pressTimer = setTimeout(() => { this.style.transform = 'translateY(-6px) scale(1.03)'; this.style.transition = 'all 0.3s cubic-bezier(0.34,1.56,0.64,1)'; }, 150); }, { passive: true }); card.addEventListener('touchend', function() { clearTimeout(pressTimer); this.style.transition = 'all 0.5s cubic-bezier(0.34,1.56,0.64,1)'; this.style.transform = ''; }); card.addEventListener('touchcancel', function() { clearTimeout(pressTimer); this.style.transition = 'all 0.5s cubic-bezier(0.34,1.56,0.64,1)'; this.style.transform = ''; }); }); // 键盘导航 let focusedCardIndex = -1; document.addEventListener('keydown', function(e) { if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') { e.preventDefault(); allCards.forEach(c => c.classList.remove('selected')); if (e.key === 'ArrowRight') { focusedCardIndex = Math.min(focusedCardIndex + 1, allCards.length - 1); } else { focusedCardIndex = Math.max(focusedCardIndex - 1, 0); } if (focusedCardIndex >= 0 && focusedCardIndex < allCards.length) { allCards[focusedCardIndex].classList.add('selected'); allCards[focusedCardIndex].scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); } } if (e.key === 'Escape') { allCards.forEach(c => c.classList.remove('selected')); focusedCardIndex = -1; } }); // ============ 性能监控 & 页面可见性 ============ document.addEventListener('visibilitychange', () => { if (document.hidden) { // 暂停所有动画 Object.values(animationStates).forEach(s => { if (s && s._animId) { cancelAnimationFrame(s._animId); s._animId = null; s._paused = true; } }); } else { // 恢复动画 Object.entries(animationStates).forEach(([key, s]) => { if (s && s._paused) { s._paused = false; if (key === 'sunny') s._animId = requestAnimationFrame(function loop( t) { createSunnyAnimation('canvasSunny'); }); // 简化恢复:重新初始化 } }); if (Object.values(animationStates).some(s => s && s._paused)) { initAllAnimations(); } } }); console.log('%c🌤️ iOS 26 Weather Cards %c已就绪 %c| %c晴天 · 大风 · 暴雨 · 暴雪', 'font-size:1.1em;font-weight:700;', 'color:#888;', '', 'color:#aaa;'); console.log('%c✨ 点击卡片切换选中 · 悬停查看动效 · 方向键导航', 'color:#b0b0b8;font-style:italic;'); })(); </script> </body> </html>


--【肆】--:

你这一说我想起来了 gpt 给我做的挂灯也用的这个黄色圆球


--【伍】--:

赞同老哥的想法,逻辑确实好的话能解决一些问题了就


--【陆】--:

可以把视频压缩转换成avif动图,非常省空间还能上传。


--【柒】--:


压缩的太狠了,旋转效果都压缩没有了,不过我这个不是v4写的,这是上一个他出思考模式后写的


--【捌】--:

效果还不如前员工萝莉的小米v2.5做的呀:

image1333×853 127 KB


--【玖】--:

有点拉看着,感觉前端不是很行,而且佬是咋确定是v4 pro的?


--【拾】--:

佬:做一个3D效果的,6阶魔方的打乱和还原动态演示html 试试效果


--【拾壹】--:

宣传说的比claude opus 4.6还强,看起来还不如claude 4.5,用的不对吗


--【拾贰】--:

image1614×901 167 KB
GPT5.5


--【拾叁】--:

感觉依然是很普通,没什么大的进步啊这和3.2没区别


--【拾肆】--:

关键看逻辑能力,前端能力没那么重要,openai系的模型前端也不行但不影响SOTA


--【拾伍】--:

开深度思考,四个天气都有动效,有鼠标悬停动画

SnowShot2026-04-2411-20-071920×1036 150 KB

关闭深度思考,第一个太阳无动效,鼠标也是太阳没有悬停动画

SnowShot2026-04-2411-22-381920×1026 139 KB

标签:人工智能
问题描述:

深度思考140秒出来的结果:
image1467×557 63.6 KB
image270×109 2.41 KB
以 iOS 18 的设计风格做一个带有动画效果的天气卡片,要求是使用 HTML、CSS 和基础 JavaScript,使用横板天气页面(拥有 4 个天气卡片 (晴天,大风,暴雨,暴雪))。应足够美观,实现一定的交互效果。

html

<!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 { --bg-dark: #1c1c1e; --card-radius: 22px; --transition-smooth: 0.4s cubic-bezier(0.25, 0.1, 0.25, 1); --transition-bounce: 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', 'PingFang SC', sans-serif; min-height: 100vh; display: flex; align-items: center; justify-content: center; background: #0d0d0f; overflow-x: hidden; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent; } /* 背景层 */ .bg-layer { position: fixed; inset: 0; pointer-events: none; z-index: 0; overflow: hidden; } .bg-orb { position: absolute; border-radius: 50%; filter: blur(100px); opacity: 0.55; animation: orbFloat 12s ease-in-out infinite; will-change: transform; } .bg-orb.orb-1 { width: 420px; height: 420px; background: radial-gradient(circle, #ff9a56 0%, #ff6b35 40%, transparent 70%); top: -15%; left: 8%; animation-delay: 0s; animation-duration: 14s; } .bg-orb.orb-2 { width: 350px; height: 350px; background: radial-gradient(circle, #7eb8ff 0%, #4a90d9 40%, transparent 70%); bottom: -18%; right: 5%; animation-delay: -4s; animation-duration: 16s; } .bg-orb.orb-3 { width: 300px; height: 300px; background: radial-gradient(circle, #c4b5fd 0%, #8b7cf0 40%, transparent 70%); top: 40%; left: 50%; animation-delay: -8s; animation-duration: 13s; } .bg-orb.orb-4 { width: 260px; height: 260px; background: radial-gradient(circle, #67e8f9 0%, #22d3ee 35%, transparent 70%); bottom: 10%; left: 20%; animation-delay: -6s; animation-duration: 15s; } .bg-orb.orb-5 { width: 200px; height: 200px; background: radial-gradient(circle, #fde68a 0%, #fbbf24 35%, transparent 70%); top: 15%; right: 18%; animation-delay: -10s; animation-duration: 11s; } @keyframes orbFloat { 0%, 100% { transform: translate(0, 0) scale(1); } 20% { transform: translate(30px, -25px) scale(1.08); } 40% { transform: translate(-20px, 15px) scale(0.94); } 60% { transform: translate(15px, 30px) scale(1.05); } 80% { transform: translate(-28px, -10px) scale(0.96); } } /* 主容器 */ .main-container { position: relative; z-index: 1; width: 100%; max-width: 1400px; padding: 30px 24px; } /* 顶部标题栏 */ .header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 28px; padding: 0 8px; flex-wrap: wrap; gap: 14px; } .header-title { font-size: 1.7rem; font-weight: 600; color: #ffffff; letter-spacing: -0.3px; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); } .header-subtitle { font-size: 0.9rem; font-weight: 400; color: rgba(255, 255, 255, 0.6); letter-spacing: 0.1px; } .temp-toggle-btn { display: flex; align-items: center; gap: 8px; padding: 10px 18px; border-radius: 100px; border: 1.5px solid rgba(255, 255, 255, 0.25); background: rgba(255, 255, 255, 0.08); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); color: #ffffff; cursor: pointer; font-size: 0.9rem; font-weight: 500; letter-spacing: -0.2px; transition: all var(--transition-smooth); white-space: nowrap; font-family: inherit; } .temp-toggle-btn:hover { background: rgba(255, 255, 255, 0.16); border-color: rgba(255, 255, 255, 0.45); transform: translateY(-1px); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25); } .temp-toggle-btn:active { transform: scale(0.96); transition: transform 0.15s ease; } .toggle-icon { font-size: 1.1rem; transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); } .temp-toggle-btn:active .toggle-icon { transform: rotate(180deg); } /* 卡片滚动容器 */ .cards-scroll-container { overflow-x: auto; overflow-y: visible; scroll-behavior: smooth; -webkit-overflow-scrolling: touch; padding: 10px 4px 20px; scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.2) transparent; } .cards-scroll-container::-webkit-scrollbar { height: 6px; } .cards-scroll-container::-webkit-scrollbar-track { background: transparent; border-radius: 10px; } .cards-scroll-container::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 10px; transition: background 0.3s; } .cards-scroll-container::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.4); } /* 卡片网格 */ .cards-grid { display: flex; gap: 20px; padding: 4px 6px; min-width: min-content; } /* 单张天气卡片 */ .weather-card { position: relative; flex: 0 0 auto; width: 300px; height: 200px; border-radius: var(--card-radius); cursor: pointer; overflow: hidden; transition: all var(--transition-bounce); border: 1px solid rgba(255, 255, 255, 0.18); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.06); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); background: rgba(30, 30, 35, 0.45); transform: translateY(0); z-index: 1; will-change: transform, box-shadow; } .weather-card:hover { transform: translateY(-10px) scale(1.025); box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45), 0 6px 16px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1); z-index: 10; border-color: rgba(255, 255, 255, 0.35); } .weather-card:active { transform: translateY(-4px) scale(0.985); transition: all 0.15s ease; box-shadow: 0 10px 28px rgba(0, 0, 0, 0.38), 0 3px 10px rgba(0, 0, 0, 0.25); } /* 卡片选中状态 */ .weather-card.selected { border-color: rgba(255, 255, 255, 0.55); box-shadow: 0 18px 44px rgba(0, 0, 0, 0.4), 0 4px 14px rgba(0, 0, 0, 0.28), 0 0 0 3px rgba(255, 255, 255, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.1); transform: translateY(-6px) scale(1.018); } /* 卡片内部天空动画区 */ .card-sky { position: absolute; top: 0; left: 0; right: 0; height: 68%; overflow: hidden; pointer-events: none; z-index: 1; } /* 各天气卡片天空背景 */ .card-sky.sky-sunny { background: linear-gradient(180deg, #ffb347 0%, #ff8c42 25%, #ff7030 55%, #e85d3a 100%); } .card-sky.sky-windy { background: linear-gradient(180deg, #b0c4de 0%, #8fa8c8 30%, #7a94b5 60%, #5f7d9e 100%); } .card-sky.sky-rain { background: linear-gradient(180deg, #4a5568 0%, #3d4855 30%, #2d3748 60%, #1a2332 100%); } .card-sky.sky-snow { background: linear-gradient(180deg, #c8d6e5 0%, #b0c0d4 30%, #95a8bf 55%, #7a8fa3 100%); } /* 信息栏 */ .card-info-bar { position: absolute; bottom: 0; left: 0; right: 0; height: 32%; display: flex; align-items: center; justify-content: space-between; padding: 0 18px; z-index: 3; background: rgba(20, 20, 25, 0.5); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border-top: 1px solid rgba(255, 255, 255, 0.1); } .card-info-left { display: flex; flex-direction: column; gap: 2px; } .card-city { font-size: 0.85rem; font-weight: 500; color: rgba(255, 255, 255, 0.85); letter-spacing: -0.1px; } .card-weather-label { font-size: 0.7rem; font-weight: 400; color: rgba(255, 255, 255, 0.5); letter-spacing: 0.2px; } .card-temp { font-size: 2.6rem; font-weight: 300; color: #ffffff; letter-spacing: -2px; line-height: 1; transition: all 0.3s ease; } .card-temp-unit { font-size: 1rem; font-weight: 400; color: rgba(255, 255, 255, 0.7); letter-spacing: 0; margin-left: 1px; vertical-align: super; } /* ============ 晴天太阳动画 ============ */ .sun-container { position: absolute; top: 50%; left: 38%; transform: translate(-50%, -50%); width: 70px; height: 70px; z-index: 2; } .sun-core { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 44px; height: 44px; border-radius: 50%; background: radial-gradient(circle at 40% 35%, #fffde7 0%, #ffe082 30%, #ffb300 70%, #ff8f00 100%); box-shadow: 0 0 30px rgba(255, 180, 40, 0.8), 0 0 60px rgba(255, 160, 20, 0.5), 0 0 90px rgba(255, 140, 10, 0.3), 0 0 130px rgba(255, 120, 0, 0.2); animation: sunPulse 3s ease-in-out infinite; z-index: 3; } @keyframes sunPulse { 0%, 100% { box-shadow: 0 0 30px rgba(255, 180, 40, 0.8), 0 0 60px rgba(255, 160, 20, 0.5), 0 0 90px rgba(255, 140, 10, 0.3), 0 0 130px rgba(255, 120, 0, 0.2); } 50% { box-shadow: 0 0 40px rgba(255, 200, 50, 1), 0 0 75px rgba(255, 175, 30, 0.65), 0 0 105px rgba(255, 150, 15, 0.4), 0 0 150px rgba(255, 130, 5, 0.28); } } .sun-ring { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); border-radius: 50%; border: 2px solid rgba(255, 220, 150, 0.5); animation: ringSpin 8s linear infinite; pointer-events: none; } .sun-ring.ring-1 { width: 58px; height: 58px; animation-duration: 7s; border-color: rgba(255, 210, 130, 0.45); } .sun-ring.ring-2 { width: 72px; height: 72px; animation-duration: 10s; animation-direction: reverse; border-color: rgba(255, 200, 120, 0.3); border-style: dashed; border-width: 1.5px; } .sun-ring.ring-3 { width: 86px; height: 86px; animation-duration: 13s; border-color: rgba(255, 190, 110, 0.22); border-width: 1px; } @keyframes ringSpin { from { transform: translate(-50%, -50%) rotate(0deg); } to { transform: translate(-50%, -50%) rotate(360deg); } } /* 阳光粒子 */ .sun-particle { position: absolute; top: 50%; left: 38%; width: 4px; height: 4px; border-radius: 50%; background: #ffe9a0; pointer-events: none; z-index: 1; animation: particleFloat 2.5s ease-in-out infinite; box-shadow: 0 0 6px 2px rgba(255, 210, 100, 0.7); } @keyframes particleFloat { 0%, 100% { opacity: 0; transform: translate(0, 0) scale(0.5); } 30% { opacity: 1; } 60% { opacity: 0.7; transform: translate(var(--px), var(--py)) scale(1.3); } 100% { opacity: 0; transform: translate(var(--px-end), var(--py-end)) scale(0.3); } } /* ============ 大风动画 ============ */ .wind-line { position: absolute; height: 2px; border-radius: 4px; background: rgba(255, 255, 255, 0.7); pointer-events: none; z-index: 2; animation: windBlow linear infinite; filter: blur(0.5px); } .wind-line.thin { height: 1px; background: rgba(255, 255, 255, 0.5); filter: blur(1px); } .wind-line.thick { height: 3px; background: rgba(255, 255, 255, 0.75); filter: blur(0px); } @keyframes windBlow { 0% { transform: translateX(-120%); opacity: 0; } 8% { opacity: 1; } 85% { opacity: 1; } 100% { transform: translateX(420%); opacity: 0; } } /* 风中的碎屑 */ .wind-debris { position: absolute; border-radius: 50%; background: rgba(255, 255, 255, 0.6); pointer-events: none; z-index: 2; animation: debrisFly linear infinite; } @keyframes debrisFly { 0% { transform: translateX(-150%) translateY(0) rotate(0deg); opacity: 0; } 10% { opacity: 1; } 50% { transform: translateX(100%) translateY(-18px) rotate(180deg); opacity: 0.8; } 90% { opacity: 1; } 100% { transform: translateX(380%) translateY(5px) rotate(360deg); opacity: 0; } } /* ============ 暴雨动画 ============ */ .rain-drop { position: absolute; top: -30px; width: 2px; border-radius: 0 0 3px 3px; background: linear-gradient(180deg, transparent 0%, rgba(200, 220, 240, 0.8) 30%, rgba(180, 210, 235, 0.9) 100%); pointer-events: none; z-index: 2; animation: rainFall linear infinite; box-shadow: 0 0 3px rgba(180, 210, 240, 0.4); } @keyframes rainFall { 0% { transform: translateY(-40px); opacity: 0; } 5% { opacity: 1; } 85% { opacity: 1; } 100% { transform: translateY(160px); opacity: 0; } } /* 雨滴溅落 */ .rain-splash { position: absolute; bottom: 8px; width: 6px; height: 6px; border-radius: 50%; background: rgba(200, 215, 235, 0.5); pointer-events: none; z-index: 2; animation: splashFade 0.6s ease-out infinite; animation-delay: var(--splash-delay); } @keyframes splashFade { 0% { transform: scale(0.3); opacity: 0.8; } 100% { transform: scale(4); opacity: 0; } } /* ============ 暴雪动画 ============ */ .snow-flake { position: absolute; top: -20px; border-radius: 50%; background: #ffffff; pointer-events: none; z-index: 2; animation: snowFall linear infinite; box-shadow: 0 0 6px 2px rgba(255, 255, 255, 0.5); } @keyframes snowFall { 0% { transform: translateY(-30px) translateX(0) rotate(0deg); opacity: 0; } 8% { opacity: 1; } 50% { transform: translateY(80px) translateX(var(--drift)) rotate(180deg); opacity: 0.9; } 88% { opacity: 1; } 100% { transform: translateY(175px) translateX(var(--drift-end)) rotate(360deg); opacity: 0; } } /* 卡片上的微光扫过效果 */ .weather-card::after { content: ''; position: absolute; top: -50%; left: -50%; width: 200%; height: 200%; background: radial-gradient(ellipse at center, rgba(255, 255, 255, 0.06) 0%, transparent 60%); pointer-events: none; z-index: 4; opacity: 0; transition: opacity 0.6s ease; border-radius: inherit; } .weather-card:hover::after { opacity: 1; } /* 响应式 */ @media (max-width: 768px) { .cards-grid { gap: 14px; } .weather-card { width: 250px; height: 170px; border-radius: 18px; } .card-temp { font-size: 2.2rem; } .card-city { font-size: 0.75rem; } .card-weather-label { font-size: 0.65rem; } .header-title { font-size: 1.35rem; } .card-info-bar { padding: 0 14px; } .sun-container { left: 35%; transform: translate(-50%, -50%) scale(0.8); } .sun-core { width: 36px; height: 36px; } } @media (max-width: 480px) { .weather-card { width: 210px; height: 148px; border-radius: 16px; } .card-temp { font-size: 1.8rem; } .card-city { font-size: 0.7rem; } .card-weather-label { font-size: 0.6rem; } .cards-grid { gap: 10px; } .header { margin-bottom: 16px; } .header-title { font-size: 1.2rem; } .temp-toggle-btn { padding: 8px 14px; font-size: 0.78rem; } .sun-container { left: 32%; transform: translate(-50%, -50%) scale(0.65); } .card-info-bar { padding: 0 10px; } } </style> </head> <body> <!-- 背景光斑 --> <div class="bg-layer"> <div class="bg-orb orb-1"></div> <div class="bg-orb orb-2"></div> <div class="bg-orb orb-3"></div> <div class="bg-orb orb-4"></div> <div class="bg-orb orb-5"></div> </div> <!-- 主容器 --> <div class="main-container"> <!-- 标题栏 --> <div class="header"> <div> <div class="header-title">🌤️ 天气预览</div> <div class="header-subtitle">今日天气概览 · 四城气象</div> </div> <button class="temp-toggle-btn" id="tempToggle" title="切换温度单位"> <span class="toggle-icon">🌡️</span> <span id="toggleLabel">切换 °F</span> </button> </div> <!-- 卡片横向滚动区 --> <div class="cards-scroll-container"> <div class="cards-grid" id="cardsGrid"> <!-- 卡片由JS动态生成 --> </div> </div> </div> <script> (function() { // ============ 数据配置 ============ const weatherData = [{ id: 'sunny', type: 'sunny', city: '三亚', tempC: 29, label: '晴朗', skyClass: 'sky-sunny', icon: '☀️', }, { id: 'windy', type: 'windy', city: '青岛', tempC: 16, label: '大风', skyClass: 'sky-windy', icon: '💨', }, { id: 'rain', type: 'rain', city: '广州', tempC: 24, label: '暴雨', skyClass: 'sky-rain', icon: '🌧️', }, { id: 'snow', type: 'snow', city: '哈尔滨', tempC: -15, label: '暴雪', skyClass: 'sky-snow', icon: '❄️', }, ]; let isCelsius = true; let selectedCardId = null; // ============ DOM引用 ============ const cardsGrid = document.getElementById('cardsGrid'); const tempToggleBtn = document.getElementById('tempToggle'); const toggleLabel = document.getElementById('toggleLabel'); // ============ 温度转换 ============ function cToF(c) { return Math.round(c * 9 / 5 + 32); } function getDisplayTemp(data) { return isCelsius ? data.tempC : cToF(data.tempC); } function getTempUnit() { return isCelsius ? '°C' : '°F'; } // ============ 构建卡片 ============ function buildCards() { cardsGrid.innerHTML = ''; weatherData.forEach((data, index) => { const card = document.createElement('div'); card.className = 'weather-card'; card.dataset.type = data.type; card.dataset.id = data.id; card.dataset.index = index; if (data.id === selectedCardId) { card.classList.add('selected'); } const displayTemp = getDisplayTemp(data); const tempUnit = getTempUnit(); card.innerHTML = ` <div class="card-sky ${data.skyClass}" data-sky></div> <div class="card-info-bar"> <div class="card-info-left"> <span class="card-city">${data.icon} ${data.city}</span> <span class="card-weather-label">${data.label}</span> </div> <div class="card-temp"> ${displayTemp}<span class="card-temp-unit">${tempUnit}</span> </div> </div> `; // 点击事件 card.addEventListener('click', () => { handleCardClick(card, data); }); cardsGrid.appendChild(card); }); // 重建后初始化天气动画 initAllAnimations(); // 恢复选中状态 if (selectedCardId) { const selectedCard = cardsGrid.querySelector(`[data-id="${selectedCardId}"]`); if (selectedCard) { selectedCard.classList.add('selected'); } } } // ============ 卡片点击 ============ function handleCardClick(card, data) { // 移除其他卡片的选中状态 const allCards = cardsGrid.querySelectorAll('.weather-card'); allCards.forEach(c => c.classList.remove('selected')); if (selectedCardId === data.id) { // 取消选中 selectedCardId = null; } else { // 选中当前 selectedCardId = data.id; card.classList.add('selected'); // 添加短暂的脉冲动画 card.style.transition = 'all 0.15s ease'; card.style.transform = 'translateY(-8px) scale(1.03)'; setTimeout(() => { card.style.transition = 'all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)'; card.style.transform = ''; if (selectedCardId === data.id) { card.classList.add('selected'); } }, 150); } // 滚动卡片到可见位置 card.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); } // ============ 温度切换 ============ function toggleTemperature() { isCelsius = !isCelsius; toggleLabel.textContent = isCelsius ? '切换 °F' : '切换 °C'; updateAllTemperatures(); // 按钮动画 tempToggleBtn.style.transform = 'scale(0.94)'; setTimeout(() => { tempToggleBtn.style.transform = ''; }, 180); } function updateAllTemperatures() { const allCards = cardsGrid.querySelectorAll('.weather-card'); allCards.forEach(card => { const index = parseInt(card.dataset.index); if (index >= 0 && index < weatherData.length) { const data = weatherData[index]; const tempEl = card.querySelector('.card-temp'); if (tempEl) { const displayTemp = getDisplayTemp(data); const tempUnit = getTempUnit(); tempEl.innerHTML = `${displayTemp}<span class="card-temp-unit">${tempUnit}</span>`; // 微动画 tempEl.style.transform = 'scale(1.08)'; tempEl.style.color = 'rgba(255,255,255,1)'; setTimeout(() => { tempEl.style.transform = 'scale(1)'; tempEl.style.color = '#ffffff'; }, 200); } } }); } tempToggleBtn.addEventListener('click', toggleTemperature); // ============ 键盘快捷键 ============ document.addEventListener('keydown', (e) => { if (e.key === 't' || e.key === 'T') { toggleTemperature(); } if (e.key === 'Escape') { selectedCardId = null; const allCards = cardsGrid.querySelectorAll('.weather-card'); allCards.forEach(c => c.classList.remove('selected')); } // 数字键1-4选择卡片 const num = parseInt(e.key); if (num >= 1 && num <= 4) { const card = cardsGrid.querySelector(`[data-index="${num - 1}"]`); if (card) { const data = weatherData[num - 1]; handleCardClick(card, data); } } }); // ============ 天气动画初始化 ============ function initAllAnimations() { initSunnyAnimations(); initWindyAnimations(); initRainAnimations(); initSnowAnimations(); } // --- 晴天动画 --- function initSunnyAnimations() { const sunnyCard = cardsGrid.querySelector('[data-type="sunny"]'); if (!sunnyCard) return; const sky = sunnyCard.querySelector('[data-sky]'); if (!sky) return; // 移除旧粒子 sky.querySelectorAll('.sun-particle').forEach(el => el.remove()); // 太阳容器 let sunContainer = sky.querySelector('.sun-container'); if (!sunContainer) { sunContainer = document.createElement('div'); sunContainer.className = 'sun-container'; sunContainer.innerHTML = ` <div class="sun-core"></div> <div class="sun-ring ring-1"></div> <div class="sun-ring ring-2"></div> <div class="sun-ring ring-3"></div> `; sky.appendChild(sunContainer); } // 阳光粒子 for (let i = 0; i < 14; i++) { const particle = document.createElement('div'); particle.className = 'sun-particle'; const angle = Math.random() * Math.PI * 2; const distance = 40 + Math.random() * 55; const px = Math.cos(angle) * distance; const py = Math.sin(angle) * distance; const pxEnd = Math.cos(angle) * (distance + 30); const pyEnd = Math.sin(angle) * (distance + 30); particle.style.setProperty('--px', px + 'px'); particle.style.setProperty('--py', py + 'px'); particle.style.setProperty('--px-end', pxEnd + 'px'); particle.style.setProperty('--py-end', pyEnd + 'px'); particle.style.animationDelay = Math.random() * 2.5 + 's'; particle.style.animationDuration = (2 + Math.random() * 3) + 's'; particle.style.width = (2.5 + Math.random() * 4) + 'px'; particle.style.height = particle.style.width; sky.appendChild(particle); } } // --- 大风动画 --- function initWindyAnimations() { const windyCard = cardsGrid.querySelector('[data-type="windy"]'); if (!windyCard) return; const sky = windyCard.querySelector('[data-sky]'); if (!sky) return; // 移除旧元素 sky.querySelectorAll('.wind-line, .wind-debris').forEach(el => el.remove()); // 风线 const windLineConfigs = [ { top: '22%', width: '60px', duration: 2.2, delay: 0, cls: 'thick' }, { top: '35%', width: '90px', duration: 1.6, delay: 0.6, cls: '' }, { top: '48%', width: '50px', duration: 2.8, delay: 1.2, cls: 'thin' }, { top: '55%', width: '75px', duration: 1.9, delay: 0.3, cls: '' }, { top: '68%', width: '100px', duration: 2.5, delay: 1.8, cls: 'thin' }, { top: '40%', width: '55px', duration: 3.1, delay: 2.2, cls: 'thick' }, { top: '28%', width: '70px', duration: 1.4, delay: 1.5, cls: '' }, { top: '60%', width: '85px', duration: 2.0, delay: 0.9, cls: 'thin' }, ]; windLineConfigs.forEach(config => { const line = document.createElement('div'); line.className = 'wind-line'; if (config.cls) line.classList.add(config.cls); line.style.top = config.top; line.style.width = config.width; line.style.left = '-80px'; line.style.animationDuration = config.duration + 's'; line.style.animationDelay = config.delay + 's'; sky.appendChild(line); }); // 碎屑 for (let i = 0; i < 10; i++) { const debris = document.createElement('div'); debris.className = 'wind-debris'; debris.style.top = (18 + Math.random() * 55) + '%'; debris.style.width = (3 + Math.random() * 6) + 'px'; debris.style.height = debris.style.width; debris.style.left = '-40px'; debris.style.animationDuration = (1.5 + Math.random() * 2.5) + 's'; debris.style.animationDelay = Math.random() * 3 + 's'; debris.style.opacity = (0.4 + Math.random() * 0.5); sky.appendChild(debris); } } // --- 暴雨动画 --- function initRainAnimations() { const rainCard = cardsGrid.querySelector('[data-type="rain"]'); if (!rainCard) return; const sky = rainCard.querySelector('[data-sky]'); if (!sky) return; // 移除旧雨滴 sky.querySelectorAll('.rain-drop, .rain-splash').forEach(el => el.remove()); // 雨滴 const dropCount = 35; for (let i = 0; i < dropCount; i++) { const drop = document.createElement('div'); drop.className = 'rain-drop'; drop.style.left = (Math.random() * 92) + '%'; drop.style.height = (8 + Math.random() * 18) + 'px'; drop.style.width = (1.2 + Math.random() * 1.6) + 'px'; drop.style.animationDuration = (0.5 + Math.random() * 0.9) + 's'; drop.style.animationDelay = Math.random() * 2 + 's'; drop.style.opacity = (0.6 + Math.random() * 0.4); sky.appendChild(drop); } // 溅落点 for (let i = 0; i < 8; i++) { const splash = document.createElement('div'); splash.className = 'rain-splash'; splash.style.left = (5 + Math.random() * 85) + '%'; splash.style.setProperty('--splash-delay', Math.random() * 1.5 + 's'); splash.style.animationDuration = (0.5 + Math.random() * 0.7) + 's'; sky.appendChild(splash); } } // --- 暴雪动画 --- function initSnowAnimations() { const snowCard = cardsGrid.querySelector('[data-type="snow"]'); if (!snowCard) return; const sky = snowCard.querySelector('[data-sky]'); if (!sky) return; // 移除旧雪花 sky.querySelectorAll('.snow-flake').forEach(el => el.remove()); const flakeCount = 30; for (let i = 0; i < flakeCount; i++) { const flake = document.createElement('div'); flake.className = 'snow-flake'; flake.style.left = (Math.random() * 90) + '%'; const size = (3 + Math.random() * 10) + 'px'; flake.style.width = size; flake.style.height = size; flake.style.animationDuration = (3 + Math.random() * 5) + 's'; flake.style.animationDelay = Math.random() * 4 + 's'; flake.style.setProperty('--drift', (Math.random() * 30 - 15) + 'px'); flake.style.setProperty('--drift-end', (Math.random() * 40 - 20) + 'px'); flake.style.opacity = (0.55 + Math.random() * 0.45); flake.style.boxShadow = `0 0 ${4 + Math.random() * 8}px ${1 + Math.random() * 3}px rgba(255,255,255,${0.3 + Math.random() * 0.5})`; sky.appendChild(flake); } } // ============ 初始化 ============ function init() { buildCards(); // 默认选中第一张卡片(晴天) setTimeout(() => { const firstCard = cardsGrid.querySelector('[data-index="0"]'); if (firstCard) { selectedCardId = 'sunny'; firstCard.classList.add('selected'); } }, 300); } // ============ 窗口大小变化时重新初始化动画 ============ let resizeDebounce; window.addEventListener('resize', () => { clearTimeout(resizeDebounce); resizeDebounce = setTimeout(() => { initAllAnimations(); }, 400); }); // ============ 启动 ============ init(); console.log('🌤️ iOS 18 风格天气卡片已就绪'); console.log(' 🖱️ 点击卡片选中 | 再次点击取消选中'); console.log(' 🌡️ 点击右上角按钮切换 °C/°F'); console.log(' ⌨️ 键盘快捷键: 1-4 选择卡片 | T 切换温度 | Esc 取消选中'); console.log(' 📱 支持横向滚动查看所有卡片'); })(); </script> </body> </html>

image878×670 60.5 KB
设计一个现代、极简风格的天气应用UI界面卡片,要求所有代码存放至一个html文件内 使用浅色模式,背景为白色,带有柔和的圆角、毛玻璃效果和轻微的投影。 布局清晰,顶部是地点和日期信息。 左侧主区域显示当前天气,包含一个色彩柔和的大图标(晴转多云)和一个非常大的温度读数(28°)。 右侧用一个2x2的网格展示湿度、风速等次要数据。 中间部分用三个水平排列的彩色(淡黄、淡橙、淡紫)卡片分别展示紫外线指数、日出和日落时间。 底部是小时天气预报,以水平卡片列表的形式呈现。 整体设计语言干净,使用无衬线字体,图标风格为简约的线形或双色调图标,色彩方案柔和、饱和度低,给人清新舒适的感觉。UI, UX design, weather app, dashboard, data visualization, minimalist, clean, light mode, pastel colors, soft shadow, rounded corners, grid layout.

网友解答:
--【壹】--:

粗的一看感觉还可以,一细看这个太阳跟个乒乓球一样


--【贰】--:

社区怎么上传视频啊,找了半天找不到上传视频的入口


--【叁】--: Mrzqd:

以 iOS 18 的设计风格做一个带有动画效果的天气卡片,要求是使用 HTML、CSS 和基础 JavaScript,使用横板天气页面(拥有 4 个天气卡片 (晴天,大风,暴雨,暴雪))。应足够美观,实现一定的交互效果。

参考佬友的提示词并稍作修改:

以 iOS 26 的设计风格做一个带有动画效果的天气卡片,要求是使用 HTML、CSS 和基础 JavaScript,使用横板天气页面(拥有 4 个天气卡片 (晴天,大风,暴雨,暴雪))。应足够美观,实现一定的交互效果,体现月薪2万美元的程序员价值

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>iOS 26 风格 · 天气卡片</title> <style> :root { --bg-deep: #0a0e17; --bg-mid: #111827; --text-primary: #f5f5f7; --text-secondary: #b0b0b8; --text-tertiary: #7b7b86; --glass-border: rgba(255, 255, 255, 0.12); --glass-highlight: rgba(255, 255, 255, 0.06); --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3); --shadow-md: 0 8px 32px rgba(0, 0, 0, 0.4); --shadow-lg: 0 20px 60px rgba(0, 0, 0, 0.55); --spring-ease: cubic-bezier(0.34, 1.56, 0.64, 1); --smooth-ease: cubic-bezier(0.25, 0.1, 0.25, 1); --card-width: 280px; --card-height: 430px; --card-radius: 28px; --transition-card: 0.55s var(--spring-ease); --transition-subtle: 0.35s var(--smooth-ease); } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', 'PingFang SC', sans-serif; background: var(--bg-deep); min-height: 100vh; display: flex; align-items: center; justify-content: center; overflow-x: hidden; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent; } /* 背景光晕 */ .bg-aurora { position: fixed; inset: 0; pointer-events: none; z-index: 0; overflow: hidden; } .bg-aurora .orb { position: absolute; border-radius: 50%; filter: blur(120px); opacity: 0.18; animation: orbDrift 20s ease-in-out infinite; } .bg-aurora .orb:nth-child(1) { width: 600px; height: 600px; background: radial-gradient(circle, #ff9a56 0%, transparent 70%); top: -15%; left: -10%; animation-delay: 0s; } .bg-aurora .orb:nth-child(2) { width: 500px; height: 500px; background: radial-gradient(circle, #5b8def 0%, transparent 70%); bottom: -20%; right: -8%; animation-delay: -7s; animation-duration: 24s; } .bg-aurora .orb:nth-child(3) { width: 400px; height: 400px; background: radial-gradient(circle, #b8d4e3 0%, transparent 70%); top: 40%; left: 55%; animation-delay: -14s; animation-duration: 18s; } @keyframes orbDrift { 0%, 100% { transform: translate(0, 0) scale(1); } 25% { transform: translate(40px, -30px) scale(1.08); } 50% { transform: translate(-20px, 25px) scale(0.94); } 75% { transform: translate(-35px, -15px) scale(1.05); } } /* 主容器 */ .main-container { position: relative; z-index: 1; display: flex; flex-direction: column; align-items: center; gap: 28px; padding: 20px; width: 100%; max-width: 1300px; } /* 顶部标题栏 */ .header { display: flex; align-items: center; justify-content: space-between; width: 100%; max-width: 1240px; padding: 0 10px; } .header .title-group { display: flex; align-items: baseline; gap: 12px; } .header .title { font-size: 2.2rem; font-weight: 700; color: var(--text-primary); letter-spacing: -0.02em; line-height: 1; } .header .subtitle { font-size: 0.95rem; font-weight: 500; color: var(--text-tertiary); letter-spacing: 0.01em; } .header .location-badge { display: flex; align-items: center; gap: 7px; padding: 10px 18px; background: rgba(255, 255, 255, 0.07); border: 1px solid var(--glass-border); border-radius: 100px; color: var(--text-secondary); font-size: 0.9rem; font-weight: 500; backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); transition: all var(--transition-subtle); cursor: pointer; letter-spacing: 0.01em; } .header .location-badge:hover { background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 255, 255, 0.25); color: #fff; transform: translateY(-1px); box-shadow: var(--shadow-sm); } .header .location-dot { width: 7px; height: 7px; border-radius: 50%; background: #34c759; box-shadow: 0 0 10px #34c75988; animation: pulseDot 2s ease-in-out infinite; } @keyframes pulseDot { 0%, 100% { box-shadow: 0 0 8px #34c75966; } 50% { box-shadow: 0 0 18px #34c759bb; } } /* 卡片网格 */ .cards-grid { display: flex; gap: 20px; flex-wrap: nowrap; justify-content: center; width: 100%; perspective: 1200px; } /* 天气卡片 */ .weather-card { position: relative; width: var(--card-width); min-width: var(--card-width); height: var(--card-height); border-radius: var(--card-radius); background: rgba(255, 255, 255, 0.06); border: 1px solid var(--glass-border); backdrop-filter: blur(24px) saturate(160%); -webkit-backdrop-filter: blur(24px) saturate(160%); box-shadow: var(--shadow-md), inset 0 1px 0 rgba(255, 255, 255, 0.08); cursor: pointer; transition: all var(--transition-card); display: flex; flex-direction: column; align-items: center; overflow: hidden; z-index: 1; transform-origin: center center; -webkit-tap-highlight-color: transparent; } .weather-card::before { content: ''; position: absolute; inset: 0; border-radius: var(--card-radius); background: linear-gradient(180deg, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.01) 30%, rgba(255, 255, 255, 0.0) 60%, rgba(0, 0, 0, 0.05) 100%); pointer-events: none; z-index: 2; transition: opacity var(--transition-subtle); } .weather-card::after { content: ''; position: absolute; top: 0; left: 20px; right: 20px; height: 1px; background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.25) 20%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.25) 80%, transparent 100%); pointer-events: none; z-index: 3; border-radius: 50%; opacity: 0.7; transition: opacity var(--transition-subtle); } /* 卡片悬停效果 */ .weather-card:hover { transform: translateY(-12px) scale(1.04); box-shadow: var(--shadow-lg), inset 0 1px 0 rgba(255, 255, 255, 0.14); border-color: rgba(255, 255, 255, 0.25); z-index: 10; background: rgba(255, 255, 255, 0.1); } .weather-card:hover::after { opacity: 1; } .weather-card:active { transform: translateY(-4px) scale(0.98); transition: all 0.15s var(--smooth-ease); } /* 相邻卡片在hover时略微后退 */ .cards-grid:hover .weather-card:not(:hover) { transform: scale(0.96); opacity: 0.75; filter: brightness(0.85) blur(1px); transition: all 0.5s var(--smooth-ease); } .weather-card:hover~.weather-card { transform: scale(0.96); opacity: 0.75; filter: brightness(0.85) blur(1px); transition: all 0.5s var(--smooth-ease); } /* 卡片选中状态 */ .weather-card.selected { border-color: rgba(255, 255, 255, 0.45); box-shadow: var(--shadow-lg), 0 0 0 3px rgba(255, 255, 255, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.18); background: rgba(255, 255, 255, 0.13); z-index: 8; transform: translateY(-6px) scale(1.025); } .weather-card.selected::after { opacity: 1; background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.35) 20%, rgba(255, 255, 255, 0.55) 50%, rgba(255, 255, 255, 0.35) 80%, transparent 100%); } /* 卡片顶部彩色光晕 */ .card-glow { position: absolute; top: -40px; left: 50%; transform: translateX(-50%); width: 140px; height: 80px; border-radius: 50%; pointer-events: none; z-index: 0; filter: blur(35px); opacity: 0.4; transition: all var(--transition-card); } .weather-card:hover .card-glow { opacity: 0.7; width: 170px; height: 100px; top: -50px; filter: blur(30px); } .card-glow.sunny { background: #ff9f43; } .card-glow.windy { background: #5b8def; } .card-glow.rainstorm { background: #4a6fa5; } .card-glow.blizzard { background: #a8d8ea; } /* 城市名称 */ .card-city { position: relative; z-index: 4; margin-top: 22px; font-size: 0.85rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--text-tertiary); transition: color var(--transition-subtle); } .weather-card:hover .card-city, .weather-card.selected .card-city { color: var(--text-secondary); } /* Canvas 动画区域 */ .card-canvas-wrap { position: relative; z-index: 1; width: 100%; height: 170px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin: 4px 0; } .card-canvas-wrap canvas { display: block; width: 100%; height: 100%; pointer-events: none; } /* 温度显示 */ .card-temp { position: relative; z-index: 4; font-size: 4.2rem; font-weight: 200; letter-spacing: -0.04em; color: var(--text-primary); line-height: 1; transition: all var(--transition-subtle); display: flex; align-items: flex-start; gap: 2px; } .card-temp .degree { font-size: 1.6rem; font-weight: 300; margin-top: 6px; opacity: 0.8; } .weather-card:hover .card-temp { transform: scale(1.06); text-shadow: 0 0 40px rgba(255, 255, 255, 0.3); } /* 天气描述 */ .card-desc { position: relative; z-index: 4; font-size: 1rem; font-weight: 500; color: var(--text-secondary); letter-spacing: 0.02em; margin-top: 2px; transition: color var(--transition-subtle); } .weather-card:hover .card-desc, .weather-card.selected .card-desc { color: #d5d5dc; } /* 底部详情行 */ .card-details { position: relative; z-index: 4; display: flex; gap: 20px; margin-top: 14px; padding-top: 14px; border-top: 1px solid rgba(255, 255, 255, 0.08); width: 75%; justify-content: center; transition: border-color var(--transition-subtle); } .weather-card:hover .card-details, .weather-card.selected .card-details { border-top-color: rgba(255, 255, 255, 0.18); } .card-detail-item { display: flex; flex-direction: column; align-items: center; gap: 3px; } .card-detail-item .detail-value { font-size: 0.85rem; font-weight: 600; color: var(--text-secondary); letter-spacing: 0.01em; transition: color var(--transition-subtle); } .card-detail-item .detail-label { font-size: 0.68rem; font-weight: 500; color: var(--text-tertiary); letter-spacing: 0.03em; text-transform: uppercase; } .weather-card:hover .detail-value, .weather-card.selected .detail-value { color: #e0e0e6; } /* 响应式 */ @media (max-width: 1280px) { .cards-grid { gap: 14px; } :root { --card-width: 240px; --card-height: 390px; --card-radius: 24px; } .card-temp { font-size: 3.4rem; } .card-canvas-wrap { height: 140px; } .header .title { font-size: 1.7rem; } } @media (max-width: 1050px) { .cards-grid { gap: 10px; } :root { --card-width: 200px; --card-height: 350px; --card-radius: 20px; } .card-temp { font-size: 2.8rem; } .card-temp .degree { font-size: 1.2rem; margin-top: 5px; } .card-canvas-wrap { height: 115px; } .card-desc { font-size: 0.85rem; } .card-details { gap: 12px; width: 85%; } .card-detail-item .detail-value { font-size: 0.75rem; } .card-detail-item .detail-label { font-size: 0.6rem; } .header .title { font-size: 1.4rem; } .card-city { font-size: 0.7rem; margin-top: 16px; } } @media (max-width: 860px) { .main-container { padding: 10px; gap: 16px; } .cards-grid { flex-wrap: nowrap; overflow-x: auto; justify-content: flex-start; padding: 10px 6px 20px; gap: 12px; scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch; scrollbar-width: none; } .cards-grid::-webkit-scrollbar { display: none; } :root { --card-width: 210px; --card-height: 370px; --card-radius: 22px; } .weather-card { scroll-snap-align: center; flex-shrink: 0; } .card-temp { font-size: 3rem; } .card-canvas-wrap { height: 130px; } .header { padding: 0 6px; } .header .title { font-size: 1.5rem; } .cards-grid:hover .weather-card:not(:hover) { transform: scale(0.94); opacity: 0.7; filter: brightness(0.8) blur(0.5px); } } @media (max-width: 480px) { :root { --card-width: 175px; --card-height: 320px; --card-radius: 18px; } .card-temp { font-size: 2.4rem; } .card-temp .degree { font-size: 1rem; margin-top: 3px; } .card-canvas-wrap { height: 100px; } .card-desc { font-size: 0.75rem; } .card-details { gap: 8px; width: 88%; margin-top: 10px; padding-top: 10px; } .card-detail-item .detail-value { font-size: 0.7rem; } .card-detail-item .detail-label { font-size: 0.55rem; } .card-city { font-size: 0.65rem; margin-top: 12px; } .header .title { font-size: 1.2rem; } .header .subtitle { font-size: 0.7rem; } .header .location-badge { font-size: 0.7rem; padding: 7px 12px; gap: 4px; } } </style> </head> <body> <!-- 背景光晕 --> <div class="bg-aurora"> <div class="orb"></div> <div class="orb"></div> <div class="orb"></div> </div> <!-- 主容器 --> <div class="main-container"> <!-- 顶部标题 --> <header class="header"> <div class="title-group"> <span class="title">Weather</span> <span class="subtitle">实时天气</span> </div> <div class="location-badge" title="当前定位"> <span class="location-dot"></span> <span>自动定位</span> <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="opacity:0.7;"> <polyline points="6 9 12 15 18 9"></polyline> </svg> </div> </header> <!-- 卡片网格 --> <div class="cards-grid" id="cardsGrid"> <!-- 晴天卡片 --> <div class="weather-card" data-weather="sunny" data-city="Los Angeles" data-temp="28" data-feels="30" data-humidity="38" data-wind="8"> <div class="card-glow sunny"></div> <span class="card-city">Los Angeles</span> <div class="card-canvas-wrap"><canvas id="canvasSunny" aria-hidden="true"></canvas></div> <div class="card-temp">28<span class="degree">°C</span></div> <div class="card-desc">晴空万里</div> <div class="card-details"> <div class="card-detail-item"><span class="detail-value">30°</span><span class="detail-label">体感</span></div> <div class="card-detail-item"><span class="detail-value">38%</span><span class="detail-label">湿度</span></div> <div class="card-detail-item"><span class="detail-value">8km/h</span><span class="detail-label">风速</span></div> </div> </div> <!-- 大风卡片 --> <div class="weather-card" data-weather="windy" data-city="Chicago" data-temp="16" data-feels="12" data-humidity="52" data-wind="42"> <div class="card-glow windy"></div> <span class="card-city">Chicago</span> <div class="card-canvas-wrap"><canvas id="canvasWindy" aria-hidden="true"></canvas></div> <div class="card-temp">16<span class="degree">°C</span></div> <div class="card-desc">大风呼啸</div> <div class="card-details"> <div class="card-detail-item"><span class="detail-value">12°</span><span class="detail-label">体感</span></div> <div class="card-detail-item"><span class="detail-value">52%</span><span class="detail-label">湿度</span></div> <div class="card-detail-item"><span class="detail-value">42km/h</span><span class="detail-label">风速</span></div> </div> </div> <!-- 暴雨卡片 --> <div class="weather-card" data-weather="rainstorm" data-city="Tokyo" data-temp="22" data-feels="23" data-humidity="89" data-wind="25"> <div class="card-glow rainstorm"></div> <span class="card-city">Tokyo</span> <div class="card-canvas-wrap"><canvas id="canvasRainstorm" aria-hidden="true"></canvas></div> <div class="card-temp">22<span class="degree">°C</span></div> <div class="card-desc">暴雨倾盆</div> <div class="card-details"> <div class="card-detail-item"><span class="detail-value">23°</span><span class="detail-label">体感</span></div> <div class="card-detail-item"><span class="detail-value">89%</span><span class="detail-label">湿度</span></div> <div class="card-detail-item"><span class="detail-value">25km/h</span><span class="detail-label">风速</span></div> </div> </div> <!-- 暴雪卡片 --> <div class="weather-card" data-weather="blizzard" data-city="Reykjavik" data-temp="-8" data-feels="-16" data-humidity="78" data-wind="35"> <div class="card-glow blizzard"></div> <span class="card-city">Reykjavík</span> <div class="card-canvas-wrap"><canvas id="canvasBlizzard" aria-hidden="true"></canvas></div> <div class="card-temp">-8<span class="degree">°C</span></div> <div class="card-desc">暴雪纷飞</div> <div class="card-details"> <div class="card-detail-item"><span class="detail-value">-16°</span><span class="detail-label">体感</span></div> <div class="card-detail-item"><span class="detail-value">78%</span><span class="detail-label">湿度</span></div> <div class="card-detail-item"><span class="detail-value">35km/h</span><span class="detail-label">风速</span></div> </div> </div> </div> </div> <script> (function() { // ============ 工具函数 ============ const dpr = Math.min(window.devicePixelRatio || 1, 2); function setupCanvas(canvasId, width, height) { const canvas = document.getElementById(canvasId); if (!canvas) return null; const rect = canvas.parentElement.getBoundingClientRect(); const w = rect.width || width; const h = rect.height || height; canvas.width = w * dpr; canvas.height = h * dpr; canvas.style.width = w + 'px'; canvas.style.height = h + 'px'; const ctx = canvas.getContext('2d'); ctx.scale(dpr, dpr); return { canvas, ctx, w, h }; } function resizeCanvas(state) { if (!state || !state.canvas) return; const rect = state.canvas.parentElement.getBoundingClientRect(); const w = rect.width || state.w; const h = rect.height || state.h; state.w = w; state.h = h; state.canvas.width = w * dpr; state.canvas.height = h * dpr; state.canvas.style.width = w + 'px'; state.canvas.style.height = h + 'px'; state.ctx.setTransform(1, 0, 0, 1, 0, 0); state.ctx.scale(dpr, dpr); } // ============ 晴天动画 ============ function createSunnyAnimation(canvasId) { const state = setupCanvas(canvasId, 260, 170); if (!state) return; const { ctx, w, h } = state; const particles = []; const sunX = w / 2; const sunY = h * 0.45; const sunRadius = Math.min(w, h) * 0.22; // 光粒子 for (let i = 0; i < 45; i++) { particles.push({ x: Math.random() * w, y: Math.random() * h, r: Math.random() * 2.2 + 0.8, speedX: (Math.random() - 0.5) * 0.5, speedY: (Math.random() - 0.5) * 0.4 - 0.3, alpha: Math.random() * 0.6 + 0.2, phase: Math.random() * Math.PI * 2, twinkleSpeed: Math.random() * 0.03 + 0.01, }); } const rayCount = 10; let rotationAngle = 0; function drawSunRays(cx, cy, radius, angle) { ctx.save(); ctx.translate(cx, cy); ctx.rotate(angle); for (let i = 0; i < rayCount; i++) { const rayAngle = (i / rayCount) * Math.PI * 2; const innerR = radius * 1.05; const outerR = radius * 1.55 + Math.sin(Date.now() * 0.003 + i) * radius * 0.2; const x1 = Math.cos(rayAngle) * innerR; const y1 = Math.sin(rayAngle) * innerR; const x2 = Math.cos(rayAngle) * outerR; const y2 = Math.sin(rayAngle) * outerR; const grad = ctx.createLinearGradient(x1, y1, x2, y2); grad.addColorStop(0, 'rgba(255,200,120,0.7)'); grad.addColorStop(0.5, 'rgba(255,180,90,0.35)'); grad.addColorStop(1, 'rgba(255,150,60,0)'); ctx.strokeStyle = grad; ctx.lineWidth = 3 + Math.sin(Date.now() * 0.004 + i) * 1.5; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke(); } ctx.restore(); } function drawGlow(cx, cy, radius) { const glowGrad = ctx.createRadialGradient(cx, cy, radius * 0.3, cx, cy, radius * 2.4); glowGrad.addColorStop(0, 'rgba(255,240,210,0.9)'); glowGrad.addColorStop(0.25, 'rgba(255,200,140,0.55)'); glowGrad.addColorStop(0.5, 'rgba(255,160,80,0.2)'); glowGrad.addColorStop(0.75, 'rgba(255,120,40,0.05)'); glowGrad.addColorStop(1, 'rgba(255,100,30,0)'); ctx.fillStyle = glowGrad; ctx.beginPath(); ctx.arc(cx, cy, radius * 2.4, 0, Math.PI * 2); ctx.fill(); } function animate() { ctx.clearRect(0, 0, w, h); rotationAngle += 0.008; // 光晕 drawGlow(sunX, sunY, sunRadius); // 太阳本体 const sunGrad = ctx.createRadialGradient(sunX, sunY, sunRadius * 0.1, sunX, sunY, sunRadius); sunGrad.addColorStop(0, '#fffef5'); sunGrad.addColorStop(0.3, '#ffecc8'); sunGrad.addColorStop(0.6, '#ffc878'); sunGrad.addColorStop(0.85, '#ffa040'); sunGrad.addColorStop(1, '#ff8830'); ctx.fillStyle = sunGrad; ctx.beginPath(); ctx.arc(sunX, sunY, sunRadius, 0, Math.PI * 2); ctx.fill(); // 光线 drawSunRays(sunX, sunY, sunRadius, rotationAngle); // 内核高光 const coreGrad = ctx.createRadialGradient(sunX - sunRadius * 0.2, sunY - sunRadius * 0.2, sunRadius * 0.05, sunX, sunY, sunRadius * 0.7); coreGrad.addColorStop(0, 'rgba(255,255,255,0.85)'); coreGrad.addColorStop(1, 'rgba(255,255,255,0)'); ctx.fillStyle = coreGrad; ctx.beginPath(); ctx.arc(sunX, sunY, sunRadius * 0.7, 0, Math.PI * 2); ctx.fill(); // 光粒子 particles.forEach(p => { p.x += p.speedX; p.y += p.speedY; p.alpha += Math.sin(Date.now() * p.twinkleSpeed + p.phase) * 0.015; p.alpha = Math.max(0.08, Math.min(0.85, p.alpha)); if (p.y < -10) { p.y = h + 10; p.x = Math.random() * w; } if (p.y > h + 10) { p.y = -10; p.x = Math.random() * w; } if (p.x < -10) p.x = w + 10; if (p.x > w + 10) p.x = -10; ctx.fillStyle = `rgba(255,230,180,${p.alpha})`; ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); ctx.fill(); // 微光晕 ctx.fillStyle = `rgba(255,220,160,${p.alpha * 0.35})`; ctx.beginPath(); ctx.arc(p.x, p.y, p.r * 3, 0, Math.PI * 2); ctx.fill(); }); state._animId = requestAnimationFrame(animate); } state._animId = requestAnimationFrame(animate); return state; } // ============ 大风动画 ============ function createWindyAnimation(canvasId) { const state = setupCanvas(canvasId, 260, 170); if (!state) return; const { ctx, w, h } = state; const windLines = []; const debris = []; for (let i = 0; i < 22; i++) { windLines.push({ x: Math.random() * w, y: Math.random() * h, length: Math.random() * 90 + 40, speed: Math.random() * 4 + 2.5, alpha: Math.random() * 0.4 + 0.15, thickness: Math.random() * 1.8 + 0.6, waveAmp: Math.random() * 12 + 4, waveFreq: Math.random() * 0.04 + 0.02, phase: Math.random() * Math.PI * 2, }); } for (let i = 0; i < 14; i++) { debris.push({ x: Math.random() * w, y: Math.random() * h, r: Math.random() * 2.5 + 1, speed: Math.random() * 3.5 + 1.5, alpha: Math.random() * 0.6 + 0.25, wobble: Math.random() * Math.PI * 2, wobbleSpeed: Math.random() * 0.06 + 0.03, }); } function animate() { ctx.clearRect(0, 0, w, h); const time = Date.now() * 0.001; // 风线 windLines.forEach(line => { line.x += line.speed; if (line.x - line.length > w + 20) { line.x = -line.length - 10; line.y = Math.random() * h; } const waveY = Math.sin(time * line.waveFreq * 15 + line.phase) * line.waveAmp; const startX = line.x; const startY = line.y + waveY; const endX = line.x - line.length; const endY = line.y + waveY * 0.6; const grad = ctx.createLinearGradient(startX, startY, endX, endY); grad.addColorStop(0, `rgba(200,215,235,${line.alpha})`); grad.addColorStop(1, `rgba(180,200,225,0)`); ctx.strokeStyle = grad; ctx.lineWidth = line.thickness; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(startX, startY); ctx.quadraticCurveTo( (startX + endX) / 2, (startY + endY) / 2 + waveY * 0.5, endX, endY ); ctx.stroke(); }); // 碎片 debris.forEach(d => { d.x += d.speed; d.wobble += d.wobbleSpeed; d.y += Math.sin(d.wobble) * 0.7; if (d.x > w + 20) { d.x = -15; d.y = Math.random() * h; } ctx.fillStyle = `rgba(210,220,235,${d.alpha})`; ctx.beginPath(); ctx.arc(d.x, d.y, d.r, 0, Math.PI * 2); ctx.fill(); // 拖尾 ctx.fillStyle = `rgba(200,210,225,${d.alpha * 0.3})`; ctx.beginPath(); ctx.arc(d.x - d.r * 1.5, d.y, d.r * 1.4, 0, Math.PI * 2); ctx.fill(); }); state._animId = requestAnimationFrame(animate); } state._animId = requestAnimationFrame(animate); return state; } // ============ 暴雨动画 ============ function createRainstormAnimation(canvasId) { const state = setupCanvas(canvasId, 260, 170); if (!state) return; const { ctx, w, h } = state; const raindrops = []; const splashParticles = []; let lightningTimer = 0; let lightningAlpha = 0; let lightningFlash = 0; for (let i = 0; i < 130; i++) { raindrops.push({ x: Math.random() * w, y: Math.random() * h - h, length: Math.random() * 16 + 8, speed: Math.random() * 8 + 10, thickness: Math.random() * 1.3 + 0.4, alpha: Math.random() * 0.5 + 0.35, }); } function triggerLightning() { lightningAlpha = 0.7 + Math.random() * 0.3; lightningFlash = 1; lightningTimer = 0.06 + Math.random() * 0.12; } function animate(timestamp) { ctx.clearRect(0, 0, w, h); const dt = Math.min(0.05, (timestamp - (state._lastTime || timestamp)) / 1000); state._lastTime = timestamp; // 闪电计时器 if (lightningFlash > 0) { lightningFlash -= dt; lightningAlpha *= 0.8; if (lightningFlash <= 0) lightningAlpha = 0; } lightningTimer -= dt; if (lightningTimer <= 0 && Math.random() < 0.015) { triggerLightning(); } // 闪电照亮背景 if (lightningAlpha > 0.01) { const flashGrad = ctx.createRadialGradient(w * 0.55, h * 0.2, w * 0.1, w * 0.5, h * 0.5, w * 0.9); flashGrad.addColorStop(0, `rgba(255,255,240,${lightningAlpha * 0.5})`); flashGrad.addColorStop(1, 'rgba(255,255,240,0)'); ctx.fillStyle = flashGrad; ctx.fillRect(0, 0, w, h); // 闪电线条 ctx.strokeStyle = `rgba(255,255,240,${lightningAlpha})`; ctx.lineWidth = 2.5; ctx.lineCap = 'round'; ctx.shadowColor = `rgba(255,255,240,${lightningAlpha})`; ctx.shadowBlur = 18; ctx.beginPath(); let lx = w * 0.55; let ly = -5; ctx.moveTo(lx, ly); for (let i = 0; i < 6; i++) { lx += (Math.random() - 0.45) * 50; ly += h / 5.5 + Math.random() * 10; ctx.lineTo(lx, ly); } ctx.stroke(); ctx.shadowBlur = 0; } // 雨滴 raindrops.forEach(drop => { drop.y += drop.speed; drop.x += (Math.random() - 0.5) * 0.6; if (drop.y > h + 10) { drop.y = -drop.length - Math.random() * 30; drop.x = Math.random() * w; // 溅起水花 if (Math.random() < 0.3 && drop.y > h - 5) { for (let s = 0; s < 3; s++) { splashParticles.push({ x: drop.x, y: h - Math.random() * 6, vx: (Math.random() - 0.5) * 3, vy: -(Math.random() * 3 + 1), life: 0.5 + Math.random() * 0.4, r: Math.random() * 1.5 + 0.5, alpha: 0.7, }); } } } const grad = ctx.createLinearGradient(drop.x, drop.y, drop.x, drop.y + drop.length); grad.addColorStop(0, `rgba(180,200,225,${drop.alpha})`); grad.addColorStop(1, `rgba(140,170,210,${drop.alpha * 0.3})`); ctx.strokeStyle = grad; ctx.lineWidth = drop.thickness; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(drop.x, drop.y); ctx.lineTo(drop.x, drop.y + drop.length); ctx.stroke(); }); // 水花粒子 for (let i = splashParticles.length - 1; i >= 0; i--) { const sp = splashParticles[i]; sp.x += sp.vx; sp.y += sp.vy; sp.vy += 0.25; sp.life -= dt; sp.alpha = sp.life / 0.9; if (sp.life <= 0 || sp.y > h + 5) { splashParticles.splice(i, 1); continue; } ctx.fillStyle = `rgba(190,210,230,${sp.alpha})`; ctx.beginPath(); ctx.arc(sp.x, sp.y, sp.r, 0, Math.PI * 2); ctx.fill(); } // 限制水花数量 if (splashParticles.length > 60) splashParticles.splice(0, splashParticles.length - 60); state._animId = requestAnimationFrame(animate); } state._lastTime = performance.now(); state._animId = requestAnimationFrame(animate); return state; } // ============ 暴雪动画 ============ function createBlizzardAnimation(canvasId) { const state = setupCanvas(canvasId, 260, 170); if (!state) return; const { ctx, w, h } = state; const snowflakes = []; const snowAccumulation = []; const maxAccum = 70; for (let i = 0; i < 75; i++) { snowflakes.push({ x: Math.random() * w, y: Math.random() * h - h, r: Math.random() * 3.5 + 1.2, speedY: Math.random() * 1.6 + 0.6, speedX: Math.random() * 2.5 + 1.2, alpha: Math.random() * 0.7 + 0.3, wobbleAmp: Math.random() * 1.8 + 0.4, wobbleFreq: Math.random() * 0.05 + 0.02, phase: Math.random() * Math.PI * 2, crystal: Math.random() < 0.35, }); } function animate(timestamp) { ctx.clearRect(0, 0, w, h); const time = timestamp * 0.001; // 积雪 if (snowAccumulation.length < maxAccum && Math.random() < 0.25) { snowAccumulation.push({ x: Math.random() * w, y: h - Math.random() * 8, r: Math.random() * 4 + 1.5, alpha: 0.7 + Math.random() * 0.3, }); } if (snowAccumulation.length > maxAccum) { snowAccumulation.splice(0, snowAccumulation.length - maxAccum); } // 绘制积雪 snowAccumulation.forEach(sa => { ctx.fillStyle = `rgba(235,245,255,${sa.alpha})`; ctx.beginPath(); ctx.arc(sa.x, sa.y, sa.r, 0, Math.PI * 2); ctx.fill(); }); // 积雪连成片 ctx.fillStyle = 'rgba(225,238,250,0.5)'; ctx.beginPath(); const baseY = h - 2; ctx.moveTo(0, baseY); for (let x = 0; x <= w; x += 8) { const yOff = Math.sin(x * 0.06 + time * 0.3) * 5 + Math.cos(x * 0.13) * 3; ctx.lineTo(x, baseY - 6 + yOff); } ctx.lineTo(w, h + 5); ctx.lineTo(0, h + 5); ctx.closePath(); ctx.fill(); // 雪花 snowflakes.forEach(sf => { sf.y += sf.speedY; sf.x += sf.speedX + Math.sin(time * sf.wobbleFreq * 10 + sf.phase) * sf.wobbleAmp; if (sf.y > h + 10) { sf.y = -sf.r * 3 - Math.random() * 20; sf.x = Math.random() * w; } if (sf.x > w + 15) sf.x = -15; if (sf.x < -15) sf.x = w + 15; ctx.fillStyle = `rgba(240,245,255,${sf.alpha})`; ctx.beginPath(); if (sf.crystal) { // 小星形雪花 const cx = sf.x; const cy = sf.y; const rr = sf.r; for (let j = 0; j < 6; j++) { const angle = (j / 6) * Math.PI * 2 + time * 0.4; const tipX = cx + Math.cos(angle) * rr; const tipY = cy + Math.sin(angle) * rr; const midX = cx + Math.cos(angle + 0.35) * rr * 0.4; const midY = cy + Math.sin(angle + 0.35) * rr * 0.4; if (j === 0) ctx.moveTo(tipX, tipY); else ctx.lineTo(tipX, tipY); ctx.lineTo(midX, midY); } ctx.closePath(); ctx.fill(); ctx.fillStyle = `rgba(255,255,255,${sf.alpha * 0.6})`; ctx.beginPath(); ctx.arc(sf.x, sf.y, sf.r * 0.45, 0, Math.PI * 2); ctx.fill(); } else { ctx.arc(sf.x, sf.y, sf.r, 0, Math.PI * 2); ctx.fill(); // 微光 ctx.fillStyle = `rgba(255,255,255,${sf.alpha * 0.35})`; ctx.beginPath(); ctx.arc(sf.x, sf.y, sf.r * 2.2, 0, Math.PI * 2); ctx.fill(); } }); state._animId = requestAnimationFrame(animate); } state._animId = requestAnimationFrame(animate); return state; } // ============ 初始化所有动画 ============ const animationStates = {}; function initAllAnimations() { // 清理旧动画 Object.values(animationStates).forEach(s => { if (s && s._animId) cancelAnimationFrame(s._animId); }); animationStates.sunny = createSunnyAnimation('canvasSunny'); animationStates.windy = createWindyAnimation('canvasWindy'); animationStates.rainstorm = createRainstormAnimation('canvasRainstorm'); animationStates.blizzard = createBlizzardAnimation('canvasBlizzard'); } initAllAnimations(); // ============ 响应式处理 ============ let resizeTimeout; function handleResize() { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { Object.values(animationStates).forEach(s => { if (s && s.canvas) resizeCanvas(s); }); }, 250); } window.addEventListener('resize', handleResize); window.addEventListener('orientationchange', () => setTimeout(handleResize, 400)); // ============ 卡片交互 ============ const cardsGrid = document.getElementById('cardsGrid'); const allCards = cardsGrid.querySelectorAll('.weather-card'); allCards.forEach(card => { // 点击选中/取消 card.addEventListener('click', function(e) { const wasSelected = this.classList.contains('selected'); // 取消所有选中 allCards.forEach(c => c.classList.remove('selected')); // 如果之前未选中,则选中当前卡片 if (!wasSelected) { this.classList.add('selected'); // 轻微振动反馈模拟(视觉) this.style.transition = 'all 0.12s cubic-bezier(0.34,1.56,0.64,1)'; this.style.transform = 'translateY(-10px) scale(1.05)'; setTimeout(() => { this.style.transition = ''; this.style.transform = ''; }, 150); } }); // 触摸设备的长按效果 let pressTimer; card.addEventListener('touchstart', function() { pressTimer = setTimeout(() => { this.style.transform = 'translateY(-6px) scale(1.03)'; this.style.transition = 'all 0.3s cubic-bezier(0.34,1.56,0.64,1)'; }, 150); }, { passive: true }); card.addEventListener('touchend', function() { clearTimeout(pressTimer); this.style.transition = 'all 0.5s cubic-bezier(0.34,1.56,0.64,1)'; this.style.transform = ''; }); card.addEventListener('touchcancel', function() { clearTimeout(pressTimer); this.style.transition = 'all 0.5s cubic-bezier(0.34,1.56,0.64,1)'; this.style.transform = ''; }); }); // 键盘导航 let focusedCardIndex = -1; document.addEventListener('keydown', function(e) { if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') { e.preventDefault(); allCards.forEach(c => c.classList.remove('selected')); if (e.key === 'ArrowRight') { focusedCardIndex = Math.min(focusedCardIndex + 1, allCards.length - 1); } else { focusedCardIndex = Math.max(focusedCardIndex - 1, 0); } if (focusedCardIndex >= 0 && focusedCardIndex < allCards.length) { allCards[focusedCardIndex].classList.add('selected'); allCards[focusedCardIndex].scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); } } if (e.key === 'Escape') { allCards.forEach(c => c.classList.remove('selected')); focusedCardIndex = -1; } }); // ============ 性能监控 & 页面可见性 ============ document.addEventListener('visibilitychange', () => { if (document.hidden) { // 暂停所有动画 Object.values(animationStates).forEach(s => { if (s && s._animId) { cancelAnimationFrame(s._animId); s._animId = null; s._paused = true; } }); } else { // 恢复动画 Object.entries(animationStates).forEach(([key, s]) => { if (s && s._paused) { s._paused = false; if (key === 'sunny') s._animId = requestAnimationFrame(function loop( t) { createSunnyAnimation('canvasSunny'); }); // 简化恢复:重新初始化 } }); if (Object.values(animationStates).some(s => s && s._paused)) { initAllAnimations(); } } }); console.log('%c🌤️ iOS 26 Weather Cards %c已就绪 %c| %c晴天 · 大风 · 暴雨 · 暴雪', 'font-size:1.1em;font-weight:700;', 'color:#888;', '', 'color:#aaa;'); console.log('%c✨ 点击卡片切换选中 · 悬停查看动效 · 方向键导航', 'color:#b0b0b8;font-style:italic;'); })(); </script> </body> </html>


--【肆】--:

你这一说我想起来了 gpt 给我做的挂灯也用的这个黄色圆球


--【伍】--:

赞同老哥的想法,逻辑确实好的话能解决一些问题了就


--【陆】--:

可以把视频压缩转换成avif动图,非常省空间还能上传。


--【柒】--:


压缩的太狠了,旋转效果都压缩没有了,不过我这个不是v4写的,这是上一个他出思考模式后写的


--【捌】--:

效果还不如前员工萝莉的小米v2.5做的呀:

image1333×853 127 KB


--【玖】--:

有点拉看着,感觉前端不是很行,而且佬是咋确定是v4 pro的?


--【拾】--:

佬:做一个3D效果的,6阶魔方的打乱和还原动态演示html 试试效果


--【拾壹】--:

宣传说的比claude opus 4.6还强,看起来还不如claude 4.5,用的不对吗


--【拾贰】--:

image1614×901 167 KB
GPT5.5


--【拾叁】--:

感觉依然是很普通,没什么大的进步啊这和3.2没区别


--【拾肆】--:

关键看逻辑能力,前端能力没那么重要,openai系的模型前端也不行但不影响SOTA


--【拾伍】--:

开深度思考,四个天气都有动效,有鼠标悬停动画

SnowShot2026-04-2411-20-071920×1036 150 KB

关闭深度思考,第一个太阳无动效,鼠标也是太阳没有悬停动画

SnowShot2026-04-2411-22-381920×1026 139 KB

标签:人工智能