opus-4-8-fast 天气卡片测试

以 iOS 18 的设计风格做一个带有动画效果的天气卡片,要求是使用 HTML、CSS 和基础 JavaScript,使用横板天气页面(拥有 4 个天气卡片 (晴天,大风,暴雨,暴雪))。应足够美观,实现一定的交互效果。 HTML代码: <html lang="zh-CN"> <head> <met...
opus-4-8-fast 天气卡片测试
opus-4-8-fast 天气卡片测试

image
QQ20260529-021807

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

HTML代码:

<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>天气 · Weather</title>
<style>
  *{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
  :root{
    --ease:cubic-bezier(.34,1.56,.64,1);
    --glass:rgba(255,255,255,.18);
    --glass-brd:rgba(255,255,255,.35);
  }
  html,body{height:100%}
  body{
    font-family:-apple-system,BlinkMacSystemFont,"SF Pro Display","Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
    background:#0a0a14;
    color:#fff;
    min-height:100vh;
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    padding:40px 20px;
    overflow-x:hidden;
    transition:background 1.2s ease;
  }

  /* 顶部标题 */
  .head{
    text-align:center;
    margin-bottom:32px;
    user-select:none;
  }
  .head h1{
    font-size:30px;font-weight:700;letter-spacing:.5px;
    text-shadow:0 2px 20px rgba(0,0,0,.4);
  }
  .head p{
    margin-top:6px;font-size:14px;font-weight:500;opacity:.6;
  }

  /* 横向滚动容器 */
  .scroller{
    display:flex;
    gap:26px;
    padding:30px 10px 40px;
    max-width:100%;
    overflow-x:auto;
    scroll-snap-type:x mandatory;
    scrollbar-width:none;
  }
  .scroller::-webkit-scrollbar{display:none}

  /* 卡片 */
  .card{
    position:relative;
    flex:0 0 280px;
    height:420px;
    border-radius:34px;
    padding:26px;
    overflow:hidden;
    cursor:pointer;
    scroll-snap-align:center;
    display:flex;
    flex-direction:column;
    justify-content:space-between;
    isolation:isolate;
    box-shadow:0 24px 50px -12px rgba(0,0,0,.55), inset 0 1px 0 rgba(255,255,255,.25);
    transition:transform .55s var(--ease), box-shadow .55s var(--ease);
    transform:perspective(900px);
  }
  .card:hover{
    transform:perspective(900px) translateY(-14px) scale(1.03);
    box-shadow:0 40px 70px -10px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.35);
  }
  .card:active{transform:perspective(900px) translateY(-6px) scale(.99)}

  /* 各卡片背景渐变 */
  .sunny{background:linear-gradient(160deg,#48a9ff 0%,#7ec8ff 45%,#ffd86b 120%)}
  .windy{background:linear-gradient(160deg,#5a7d8c 0%,#9fb8c4 55%,#cfe0e6 120%)}
  .rainy{background:linear-gradient(160deg,#243b55 0%,#3a5a78 55%,#1c2b3a 120%)}
  .snowy{background:linear-gradient(160deg,#5d6d8c 0%,#8fa3c4 50%,#dfe8f5 120%)}

  /* 玻璃高光层 */
  .card::before{
    content:"";position:absolute;inset:0;z-index:6;pointer-events:none;
    background:radial-gradient(120% 80% at 20% 0%,rgba(255,255,255,.35),transparent 50%);
    mix-blend-mode:overlay;
  }

  .card-top{position:relative;z-index:5}
  .city{font-size:13px;font-weight:600;letter-spacing:2px;opacity:.85;text-transform:uppercase}
  .cond{font-size:23px;font-weight:700;margin-top:2px;text-shadow:0 2px 10px rgba(0,0,0,.25)}

  .temp{
    position:relative;z-index:5;
    font-size:74px;font-weight:300;line-height:1;
    letter-spacing:-2px;
    text-shadow:0 4px 24px rgba(0,0,0,.3);
  }
  .temp sup{font-size:28px;font-weight:400;top:-1.3em}

  .meta{
    position:relative;z-index:5;
    display:flex;gap:18px;font-size:12.5px;font-weight:600;opacity:.9;
  }
  .meta div{display:flex;flex-direction:column;gap:3px}
  .meta span{opacity:.6;font-weight:500;font-size:10.5px;letter-spacing:.5px}

  /* 动画画布层 */
  .fx{position:absolute;inset:0;z-index:2;pointer-events:none;overflow:hidden}

  /* ---------- 晴天:太阳 ---------- */
  .sun{
    position:absolute;top:50px;right:46px;width:84px;height:84px;border-radius:50%;
    background:radial-gradient(circle at 40% 40%,#fff6c2,#ffd23f 60%,#ff9d2f);
    box-shadow:0 0 50px 14px rgba(255,210,63,.7);
    animation:sun-pulse 4s ease-in-out infinite;
  }
  .sun::after{
    content:"";position:absolute;inset:-26px;border-radius:50%;
    background:conic-gradient(from 0deg,transparent 0 12%,rgba(255,225,120,.55) 12% 14%,transparent 14% 25%,rgba(255,225,120,.55) 25% 27%,transparent 27% 37%,rgba(255,225,120,.55) 37% 39%,transparent 39% 50%,rgba(255,225,120,.55) 50% 52%,transparent 52% 62%,rgba(255,225,120,.55) 62% 64%,transparent 64% 75%,rgba(255,225,120,.55) 75% 77%,transparent 77% 87%,rgba(255,225,120,.55) 87% 89%,transparent 89%);
    animation:spin 18s linear infinite;
    -webkit-mask:radial-gradient(circle,transparent 42px,#000 43px);
            mask:radial-gradient(circle,transparent 42px,#000 43px);
  }
  @keyframes sun-pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.07)}}
  @keyframes spin{to{transform:rotate(360deg)}}
  .heat{position:absolute;bottom:0;left:0;right:0;height:60%;
    background:linear-gradient(transparent,rgba(255,221,128,.25));
    animation:heat-wave 5s ease-in-out infinite;}
  @keyframes heat-wave{0%,100%{opacity:.4}50%{opacity:.8}}

  /* ---------- 大风:流线 ---------- */
  .wind-line{
    position:absolute;height:3px;border-radius:3px;
    background:linear-gradient(90deg,transparent,rgba(255,255,255,.9),transparent);
    left:-50%;opacity:0;
    animation:gust 2.6s linear infinite;
  }
  @keyframes gust{
    0%{transform:translateX(0);opacity:0}
    15%{opacity:.9}
    85%{opacity:.9}
    100%{transform:translateX(360px);opacity:0}
  }
  .leaf{
    position:absolute;font-size:20px;left:-30px;opacity:0;
    animation:leaf-fly 3.4s linear infinite;
  }
  @keyframes leaf-fly{
    0%{transform:translate(0,0) rotate(0);opacity:0}
    10%{opacity:1}
    90%{opacity:1}
    100%{transform:translate(340px,40px) rotate(540deg);opacity:0}
  }

  /* ---------- 暴雨:雨滴 + 闪电 ---------- */
  .drop{
    position:absolute;top:-20px;width:2px;height:18px;border-radius:2px;
    background:linear-gradient(rgba(255,255,255,.05),rgba(190,220,255,.9));
    animation:fall linear infinite;
  }
  @keyframes fall{
    0%{transform:translateY(-20px);opacity:0}
    10%{opacity:1}
    100%{transform:translateY(440px);opacity:.3}
  }
  .cloud{
    position:absolute;top:42px;right:40px;width:96px;height:34px;border-radius:30px;
    background:rgba(255,255,255,.55);filter:blur(1px);
    box-shadow:-30px 6px 0 -6px rgba(255,255,255,.45),26px 8px 0 -8px rgba(255,255,255,.4);
    animation:cloud-drift 6s ease-in-out infinite;
  }
  @keyframes cloud-drift{0%,100%{transform:translateX(0)}50%{transform:translateX(-12px)}}
  .flash{position:absolute;inset:0;background:rgba(255,255,255,.85);opacity:0;z-index:4;mix-blend-mode:screen}
  .flash.bolt{animation:bolt 6s ease-out infinite}
  @keyframes bolt{
    0%,100%{opacity:0}
    1%{opacity:.9}2%{opacity:.1}3%{opacity:.8}5%{opacity:0}
  }

  /* ---------- 暴雪:雪花 ---------- */
  .flake{
    position:absolute;top:-12px;border-radius:50%;
    background:radial-gradient(circle,#fff,rgba(255,255,255,.6));
    box-shadow:0 0 6px rgba(255,255,255,.8);
    animation:snow linear infinite;
  }
  @keyframes snow{
    0%{transform:translate(0,-12px);opacity:0}
    10%{opacity:1}
    100%{transform:translate(var(--sx,20px),440px);opacity:.4}
  }
  .blizzard{position:absolute;inset:0;
    background:linear-gradient(120deg,transparent,rgba(255,255,255,.12),transparent);
    background-size:200% 100%;animation:bliz 3s linear infinite;}
  @keyframes bliz{to{background-position:200% 0}}

  /* 进场动画 */
  .card{opacity:0;transform:perspective(900px) translateY(40px) scale(.94);animation:enter .8s var(--ease) forwards}
  .card:nth-child(1){animation-delay:.05s}
  .card:nth-child(2){animation-delay:.16s}
  .card:nth-child(3){animation-delay:.27s}
  .card:nth-child(4){animation-delay:.38s}
  @keyframes enter{to{opacity:1;transform:perspective(900px) translateY(0) scale(1)}}

  .hint{margin-top:6px;font-size:12px;opacity:.45;letter-spacing:1px;user-select:none}

  @media(max-width:640px){
    .scroller{gap:18px}
    .card{flex-basis:240px;height:380px}
    .temp{font-size:62px}
  }
</style>
</head>
<body>
  <div class="head">
    <h1>天气预报</h1>
    <p id="clock">--:--</p>
  </div>

  <div class="scroller" id="scroller"></div>
  <div class="hint">← 左右滑动 · 点击卡片切换实况 →</div>

<script>
const data = [
  {key:"sunny", cls:"sunny", city:"Sanya · 三亚", cond:"晴", temp:32, hi:34, lo:27, hum:"58%", wind:"2级", bg:"linear-gradient(160deg,#48a9ff,#7ec8ff 45%,#ffd86b 120%)"},
  {key:"windy", cls:"windy", city:"Dalian · 大连", cond:"大风", temp:18, hi:21, lo:12, hum:"40%", wind:"8级", bg:"linear-gradient(160deg,#5a7d8c,#9fb8c4 55%,#cfe0e6 120%)"},
  {key:"rainy", cls:"rainy", city:"Guangzhou · 广州", cond:"暴雨", temp:24, hi:26, lo:22, hum:"95%", wind:"6级", bg:"linear-gradient(160deg,#243b55,#3a5a78 55%,#1c2b3a 120%)"},
  {key:"snowy", cls:"snowy", city:"Harbin · 哈尔滨", cond:"暴雪", temp:-12, hi:-8, lo:-19, hum:"82%", wind:"7级", bg:"linear-gradient(160deg,#5d6d8c,#8fa3c4 50%,#dfe8f5 120%)"},
];

const bodyBg = {
  sunny:"radial-gradient(circle at 30% 20%,#1a3a5c,#0a0a14 70%)",
  windy:"radial-gradient(circle at 30% 20%,#2a3a42,#0a0a14 70%)",
  rainy:"radial-gradient(circle at 30% 20%,#0f1f33,#06060c 70%)",
  snowy:"radial-gradient(circle at 30% 20%,#28324a,#0a0a14 70%)",
};

function fxFor(key){
  const fx = document.createElement('div');
  fx.className = 'fx';
  if(key==='sunny'){
    fx.innerHTML = `<div class="heat"></div><div class="sun"></div>`;
  }
  if(key==='windy'){
    let h='';
    for(let i=0;i<7;i++){
      const top=20+i*52+Math.random()*20, w=80+Math.random()*120, d=(Math.random()*2).toFixed(2), dur=(2+Math.random()*1.5).toFixed(2);
      h+=`<div class="wind-line" style="top:${top}px;width:${w}px;animation-delay:${d}s;animation-duration:${dur}s"></div>`;
    }
    h+=`<div class="leaf" style="top:90px;animation-delay:.4s">🍃</div><div class="leaf" style="top:240px;animation-delay:1.8s">🍂</div>`;
    fx.innerHTML=h;
  }
  if(key==='rainy'){
    let h=`<div class="cloud"></div><div class="flash bolt"></div>`;
    for(let i=0;i<60;i++){
      const left=Math.random()*100, d=(Math.random()*1.2).toFixed(2), dur=(0.45+Math.random()*0.35).toFixed(2);
      h+=`<div class="drop" style="left:${left}%;animation-delay:${d}s;animation-duration:${dur}s"></div>`;
    }
    fx.innerHTML=h;
  }
  if(key==='snowy'){
    let h=`<div class="blizzard"></div>`;
    for(let i=0;i<55;i++){
      const left=Math.random()*100, sz=(2+Math.random()*5).toFixed(1), d=(Math.random()*4).toFixed(2),
            dur=(3+Math.random()*4).toFixed(2), sx=(Math.random()*120-60).toFixed(0);
      h+=`<div class="flake" style="left:${left}%;width:${sz}px;height:${sz}px;--sx:${sx}px;animation-delay:${d}s;animation-duration:${dur}s"></div>`;
    }
    fx.innerHTML=h;
  }
  return fx;
}

const scroller = document.getElementById('scroller');
data.forEach(d=>{
  const card=document.createElement('div');
  card.className='card '+d.cls;
  card.dataset.key=d.key;
  card.innerHTML=`
    <div class="card-top">
      <div class="city">${d.city}</div>
      <div class="cond">${d.cond}</div>
    </div>
    <div class="temp">${d.temp}<sup>°</sup></div>
    <div class="meta">
      <div><span>最高</span>${d.hi}°</div>
      <div><span>最低</span>${d.lo}°</div>
      <div><span>湿度</span>${d.hum}</div>
      <div><span>风力</span>${d.wind}</div>
    </div>`;
  card.appendChild(fxFor(d.key));

  // 点击:切换实时温度 + 主题背景 + 轻微抖动
  card.addEventListener('click',()=>{
    document.body.style.background = bodyBg[d.key];
    const t=card.querySelector('.temp');
    const cur=parseInt(t.firstChild.textContent);
    const next=cur + (Math.random()>.5?1:-1);
    t.firstChild.textContent=next;
    card.animate([
      {transform:'perspective(900px) translateY(-14px) scale(1.03)'},
      {transform:'perspective(900px) translateY(-14px) scale(1.06) rotate(.6deg)'},
      {transform:'perspective(900px) translateY(-14px) scale(1.03)'}
    ],{duration:380,easing:'cubic-bezier(.34,1.56,.64,1)'});
  });

  // 鼠标视差:跟随指针轻微倾斜
  card.addEventListener('mousemove',e=>{
    const r=card.getBoundingClientRect();
    const px=(e.clientX-r.left)/r.width-0.5, py=(e.clientY-r.top)/r.height-0.5;
    card.style.transform=`perspective(900px) translateY(-14px) scale(1.03) rotateY(${px*10}deg) rotateX(${-py*10}deg)`;
  });
  card.addEventListener('mouseleave',()=>{card.style.transform=''});

  scroller.appendChild(card);
});

// 默认应用首张卡片主题
document.body.style.background = bodyBg[data[0].key];

// 时钟
function tick(){
  const n=new Date();
  document.getElementById('clock').textContent =
    n.toLocaleDateString('zh-CN',{month:'long',day:'numeric',weekday:'long'})+'  '+
    n.toTimeString().slice(0,5);
}
tick();setInterval(tick,1000*30);
</script>
</body>
</html>

4 个帖子 - 4 位参与者

阅读完整话题

来源: LinuxDo 最新话题查看原文