DeepSeek v4 pro天气卡片测试来了,结果有点意外(新增GPT5.5对比)

我感觉是提示词问题

1 个赞

我的感觉还不错,同样的提示词


https://chat.deepseek.com/share/43xdyid62x5foh0jiv

2 个赞

先放gemini 3.1 pro,审美在线


deepseek expret think,整体不错,动画丰富略显杂乱

1 个赞


用CC跑的没开Max

我感觉其实还可以啊

感觉目前确实在总体审美上,国外的模型强一些,毕竟有全球资源,设计方面积累的资料都是好看的。但是现在国产的模型在功能上已经在狠狠追赶了。

2 个赞

这么看感觉还是审美上差点意思,就是不知道后端的需求解决能力怎么样

怎么感觉效果有点倒沫子啊,这效果感觉不太行啊 :lark_156:

以下是 ClaudeOpus4.6 生成的:


下边是 GPT5.5 生成的:

Gemini 3.1pro 生成的:

最后是 DeepSeek V4pro 生成的(哎,超级慢呀,得十来分钟感觉。御三家的早就生成好了):

你确定用的是 v4 吗?我的 web 端,还是 v3,v4 完全体还没上线。

我还以为那是窗户就是那么设计的,原来是定位错了吗?难绷

我觉得还行啊,用的api V4Pro

CC上使用,效果不错的,网页版是不是没有开max,毕竟max才是它的真正实力


同样的提示词

<!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 {
    --card-width: 260px;
    --card-height: 420px;
    --gap: 20px;
  }

  * { margin: 0; padding: 0; box-sizing: border-box; }

  body {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "PingFang SC", "Helvetica Neue", sans-serif;
    background: #0a0a0f;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-font-smoothing: antialiased;
    user-select: none;
    -webkit-user-select: none;
  }

  /* 动态星空背景 */
  .bg-stars {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 0;
  }

  .star {
    position: absolute;
    background: white;
    border-radius: 50%;
    animation: twinkle var(--dur) ease-in-out infinite;
    animation-delay: var(--delay);
    opacity: 0;
  }

  @keyframes twinkle {
    0%, 100% { opacity: 0.15; transform: scale(1); }
    50% { opacity: 0.9; transform: scale(1.8); }
  }

  /* 动态渐变光晕背景 */
  .bg-aurora {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 0;
    opacity: 0.6;
  }

  .aurora-blob {
    position: absolute;
    border-radius: 50%;
    filter: blur(120px);
    animation: float var(--dur) ease-in-out infinite;
    opacity: 0.5;
  }

  .aurora-blob:nth-child(1) {
    width: 500px; height: 500px;
    background: radial-gradient(circle, #3b82f6 0%, transparent 70%);
    top: -15%; left: 5%;
    animation-delay: 0s;
  }
  .aurora-blob:nth-child(2) {
    width: 400px; height: 400px;
    background: radial-gradient(circle, #8b5cf6 0%, transparent 70%);
    top: 40%; right: -5%;
    animation-delay: -3s;
  }
  .aurora-blob:nth-child(3) {
    width: 450px; height: 450px;
    background: radial-gradient(circle, #06b6d4 0%, transparent 70%);
    bottom: -10%; left: 35%;
    animation-delay: -6s;
  }

  @keyframes float {
    0%, 100% { transform: translate(0, 0) scale(1); }
    25% { transform: translate(40px, -30px) scale(1.1); }
    50% { transform: translate(-20px, 20px) scale(0.95); }
    75% { transform: translate(-30px, -15px) scale(1.05); }
  }

  /* 主布局 */
  .app {
    position: relative;
    z-index: 1;
    display: flex;
    gap: var(--gap);
    padding: 40px 30px;
    align-items: center;
  }

  /* 卡片:iOS 18 毛玻璃 */
  .card {
    position: relative;
    width: var(--card-width);
    height: var(--card-height);
    border-radius: 32px;
    overflow: hidden;
    cursor: pointer;
    transition: all 0.5s cubic-bezier(0.25, 0.8, 0.25, 1.2);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    background: rgba(255,255,255,0.06);
    border: 1px solid rgba(255,255,255,0.12);
    box-shadow:
      0 8px 32px rgba(0,0,0,0.3),
      inset 0 1px 0 rgba(255,255,255,0.08),
      inset 0 -1px 0 rgba(255,255,255,0.03);
    flex-shrink: 0;
  }

  .card:hover {
    transform: translateY(-8px);
    box-shadow:
      0 20px 48px rgba(0,0,0,0.4),
      0 0 0 1px rgba(255,255,255,0.1),
      inset 0 1px 0 rgba(255,255,255,0.1);
  }

  .card:active {
    transform: scale(0.97);
    transition: transform 0.15s ease;
  }

  /* 卡片选中态 */
  .card.selected {
    border-color: rgba(255,255,255,0.35);
    box-shadow:
      0 16px 48px rgba(99,102,241,0.25),
      0 0 0 2px rgba(129,140,248,0.3),
      inset 0 1px 0 rgba(255,255,255,0.15);
    transform: translateY(-12px);
  }

  /* 卡片内光效层 */
  .card-shine {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 10;
    border-radius: 32px;
    background: linear-gradient(
      135deg,
      rgba(255,255,255,0.15) 0%,
      transparent 40%,
      transparent 60%,
      rgba(255,255,255,0.03) 100%
    );
    transition: opacity 0.5s;
  }

  .card:hover .card-shine {
    opacity: 0.5;
  }

  /* 画布容器 */
  .canvas-wrap {
    position: absolute;
    inset: 0;
    z-index: 1;
    border-radius: 32px;
    overflow: hidden;
  }

  .canvas-wrap canvas {
    display: block;
    width: 100%;
    height: 100%;
  }

  /* 卡片内容层 */
  .card-content {
    position: absolute;
    bottom: 0; left: 0; right: 0;
    z-index: 5;
    padding: 20px 22px 22px;
    display: flex;
    flex-direction: column;
    gap: 4px;
  }

  .card-time {
    font-size: 13px;
    font-weight: 500;
    color: rgba(255,255,255,0.55);
    letter-spacing: 0.5px;
  }

  .card-temp {
    font-size: 52px;
    font-weight: 200;
    color: white;
    line-height: 1;
    letter-spacing: -2px;
  }

  .card-degree {
    font-size: 22px;
    font-weight: 300;
    vertical-align: super;
    margin-left: 2px;
    letter-spacing: 0;
  }

  .card-label {
    font-size: 17px;
    font-weight: 500;
    color: rgba(255,255,255,0.85);
    letter-spacing: 0.5px;
  }

  .card-desc {
    font-size: 12px;
    color: rgba(255,255,255,0.45);
    font-weight: 400;
  }

  /* 控制栏 */
  .controls {
    position: fixed;
    bottom: 40px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 20;
    display: flex;
    gap: 10px;
    backdrop-filter: blur(30px);
    -webkit-backdrop-filter: blur(30px);
    background: rgba(255,255,255,0.06);
    border: 1px solid rgba(255,255,255,0.1);
    border-radius: 28px;
    padding: 6px;
    box-shadow: 0 8px 32px rgba(0,0,0,0.4);
  }

  .ctrl-btn {
    padding: 10px 22px;
    border: none;
    border-radius: 24px;
    cursor: pointer;
    font-size: 14px;
    font-weight: 500;
    font-family: inherit;
    background: transparent;
    color: rgba(255,255,255,0.6);
    transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
    white-space: nowrap;
    letter-spacing: 0.3px;
  }

  .ctrl-btn:hover {
    color: rgba(255,255,255,0.9);
    background: rgba(255,255,255,0.08);
  }

  .ctrl-btn.active {
    background: rgba(255,255,255,0.15);
    color: white;
    box-shadow: 0 2px 12px rgba(0,0,0,0.2);
  }

  /* 选中卡片的脉冲光环 */
  @keyframes pulse-ring {
    0% { box-shadow: 0 16px 48px rgba(99,102,241,0.25), 0 0 0 0px rgba(129,140,248,0.4); }
    100% { box-shadow: 0 16px 48px rgba(99,102,241,0.25), 0 0 0 18px rgba(129,140,248,0); }
  }

  /* 响应式 */
  @media (max-width: 1200px) {
    .app {
      gap: 14px;
      padding: 40px 16px;
    }
    :root {
      --card-width: 220px;
      --card-height: 370px;
    }
    .card-temp { font-size: 44px; }
  }

  @media (max-width: 960px) {
    body { overflow-x: auto; overflow-y: hidden; }
    .app {
      padding: 30px 16px;
      gap: 12px;
    }
    :root {
      --card-width: 200px;
      --card-height: 340px;
    }
    .card-temp { font-size: 38px; }
    .card-content { padding: 14px 16px 16px; }
  }
</style>
</head>
<body>

  <!-- 星空背景 -->
  <div class="bg-stars" id="stars"></div>

  <!-- 极光光晕 -->
  <div class="bg-aurora">
    <div class="aurora-blob"></div>
    <div class="aurora-blob"></div>
    <div class="aurora-blob"></div>
  </div>

  <!-- 卡片区域 -->
  <div class="app" id="app">
    <!-- 动态生成 -->
  </div>

  <!-- 底部控制 -->
  <div class="controls" id="controls">
    <button class="ctrl-btn active" data-mode="all">全部展示</button>
    <button class="ctrl-btn" data-mode="sunny">☀️ 晴天</button>
    <button class="ctrl-btn" data-mode="windy">💨 大风</button>
    <button class="ctrl-btn" data-mode="storm">⛈️ 暴雨</button>
    <button class="ctrl-btn" data-mode="snow">❄️ 暴雪</button>
  </div>

<script>
// ==================== 主程序 ====================

const weatherTypes = [
  {
    id: 'sunny',
    label: '晴天',
    desc: '晴朗无云',
    temp: 28,
    time: 'SAN FRANCISCO',
    icon: '☀️',
  },
  {
    id: 'windy',
    label: '大风',
    desc: '阵风 45km/h',
    temp: 19,
    time: 'CHICAGO',
    icon: '💨',
  },
  {
    id: 'storm',
    label: '暴雨',
    desc: '降水量 42mm',
    temp: 22,
    time: 'MIAMI',
    icon: '⛈️',
  },
  {
    id: 'snow',
    label: '暴雪',
    desc: '积雪 30cm',
    temp: -8,
    time: 'HARBIN',
    icon: '❄️',
  },
];

const app = document.getElementById('app');
const controls = document.getElementById('controls');

let cards = [];
let animators = [];
let selectedCardId = null;

// ---------- 创建 DOM ----------
function buildCards() {
  weatherTypes.forEach((w, i) => {
    const card = document.createElement('div');
    card.className = 'card';
    card.dataset.id = w.id;

    card.innerHTML = `
      <div class="canvas-wrap"><canvas></canvas></div>
      <div class="card-shine"></div>
      <div class="card-content">
        <div class="card-time">${w.time}</div>
        <div class="card-temp">${w.temp}<span class="card-degree">°</span></div>
        <div class="card-label">${w.label}</div>
        <div class="card-desc">${w.desc}</div>
      </div>
    `;

    card.addEventListener('click', () => selectCard(w.id));
    app.appendChild(card);
    cards.push(card);
  });
}

// ---------- 选中卡片 ----------
function selectCard(id) {
  selectedCardId = (selectedCardId === id) ? null : id;
  cards.forEach(c => {
    c.classList.toggle('selected', c.dataset.id === selectedCardId);
  });
}

// ---------- 模式切换 ----------
function setMode(mode) {
  if (mode === 'all') {
    cards.forEach(c => c.style.display = '');
    selectedCardId = null;
    cards.forEach(c => c.classList.remove('selected'));
  } else {
    cards.forEach(c => {
      if (c.dataset.id === mode) {
        c.style.display = '';
      } else {
        c.style.display = 'none';
        c.classList.remove('selected');
      }
    });
    selectedCardId = null;
  }

  document.querySelectorAll('.ctrl-btn').forEach(b => {
    b.classList.toggle('active', b.dataset.mode === mode);
  });

  // 重新触发所有动画器调整尺寸
  animators.forEach(a => a.resize());
}

controls.addEventListener('click', e => {
  const btn = e.target.closest('.ctrl-btn');
  if (!btn) return;
  setMode(btn.dataset.mode);
});

// ==================== 动画引擎 ====================

class WeatherAnimator {
  constructor(canvas, type) {
    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
    this.type = type;
    this.w = 0; this.h = 0;
    this.animId = null;
    this.particles = [];
    this.frame = 0;
    this.bolts = [];
    this.snowAccum = [];
    this.waterPuddles = [];
    this.clouds = [];
    this.trees = [];
    this.rays = [];
  }

  init() {
    this.resize();
    this.createParticles();
    this.animate();
  }

  resize() {
    const rect = this.canvas.parentElement.getBoundingClientRect();
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    const w = rect.width;
    const h = rect.height;
    if (w === 0 || h === 0) return;
    this.w = w;
    this.h = h;
    this.canvas.width = w * dpr;
    this.canvas.height = h * dpr;
    this.canvas.style.width = w + 'px';
    this.canvas.style.height = h + 'px';
    this.ctx.setTransform(1, 0, 0, 1, 0, 0);
    this.ctx.scale(dpr, dpr);
  }

  animate() {
    this.frame++;
    this.ctx.clearRect(0, 0, this.w, this.h);
    this.draw();
    this.animId = requestAnimationFrame(() => this.animate());
  }

  destroy() {
    cancelAnimationFrame(this.animId);
  }

  createParticles() {
    switch (this.type) {
      case 'sunny': this.initSunny(); break;
      case 'windy': this.initWindy(); break;
      case 'storm': this.initStorm(); break;
      case 'snow': this.initSnow(); break;
    }
  }

  draw() {
    switch (this.type) {
      case 'sunny': this.drawSunny(); break;
      case 'windy': this.drawWindy(); break;
      case 'storm': this.drawStorm(); break;
      case 'snow': this.drawSnow(); break;
    }
  }

  // ========== 晴天 ==========
  initSunny() {
    this.sun = { x: this.w * 0.5, y: this.h * 0.38, r: Math.min(this.w, this.h) * 0.22 };
    this.rays = [];
    for (let i = 0; i < 24; i++) {
      this.rays.push({
        angle: (Math.PI * 2 / 24) * i,
        length: this.sun.r * (1.3 + Math.random() * 0.8),
        width: 1.5 + Math.random() * 2.5,
        speed: 0.003 + Math.random() * 0.005,
        offset: Math.random() * Math.PI * 2,
        opacity: 0.3 + Math.random() * 0.5,
      });
    }
    // 小型高光粒子
    for (let i = 0; i < 30; i++) {
      this.particles.push({
        x: this.w * 0.3 + Math.random() * this.w * 0.4,
        y: this.h * 0.2 + Math.random() * this.h * 0.45,
        r: 0.6 + Math.random() * 1.8,
        speed: 0.15 + Math.random() * 0.4,
        opacity: 0.2 + Math.random() * 0.7,
        phase: Math.random() * Math.PI * 2,
        amplitude: 3 + Math.random() * 12,
      });
    }
  }

  drawSunny() {
    const ctx = this.ctx;
    const { x, y, r } = this.sun;
    const f = this.frame;

    // 外层大光晕
    const g1 = ctx.createRadialGradient(x, y, r * 0.6, x, y, r * 2.8);
    g1.addColorStop(0, 'rgba(255,200,80,0.5)');
    g1.addColorStop(0.35, 'rgba(255,160,40,0.2)');
    g1.addColorStop(0.7, 'rgba(255,100,20,0.05)');
    g1.addColorStop(1, 'rgba(255,80,0,0)');
    ctx.fillStyle = g1;
    ctx.beginPath(); ctx.arc(x, y, r * 2.8, 0, Math.PI * 2); ctx.fill();

    // 光线
    this.rays.forEach(ray => {
      const a = ray.angle + Math.sin(f * ray.speed + ray.offset) * 0.15;
      const len = ray.length + Math.sin(f * 0.02 + ray.offset) * 8;
      const sx = x + Math.cos(a) * r;
      const sy = y + Math.sin(a) * r;
      const ex = x + Math.cos(a) * (r + len);
      const ey = y + Math.sin(a) * (r + len);

      ctx.save();
      ctx.globalAlpha = ray.opacity * (0.5 + 0.5 * Math.sin(f * 0.03 + ray.offset));
      const grad = ctx.createLinearGradient(sx, sy, ex, ey);
      grad.addColorStop(0, 'rgba(255,220,120,1)');
      grad.addColorStop(1, 'rgba(255,200,80,0)');
      ctx.strokeStyle = grad;
      ctx.lineWidth = ray.width;
      ctx.lineCap = 'round';
      ctx.beginPath(); ctx.moveTo(sx, sy); ctx.lineTo(ex, ey); ctx.stroke();
      ctx.restore();
    });

    // 太阳本体
    const sg = ctx.createRadialGradient(x, y, r * 0.05, x, y, r);
    sg.addColorStop(0, 'rgba(255,255,240,1)');
    sg.addColorStop(0.2, 'rgba(255,250,220,1)');
    sg.addColorStop(0.5, 'rgba(255,210,80,0.9)');
    sg.addColorStop(0.8, 'rgba(255,150,30,0.4)');
    sg.addColorStop(1, 'rgba(255,100,10,0)');
    ctx.fillStyle = sg;
    ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI * 2); ctx.fill();

    // 核心高光
    const cg = ctx.createRadialGradient(x, y, 0, x, y, r * 0.45);
    cg.addColorStop(0, 'rgba(255,255,255,0.9)');
    cg.addColorStop(1, 'rgba(255,255,200,0)');
    ctx.fillStyle = cg;
    ctx.beginPath(); ctx.arc(x, y, r * 0.45, 0, Math.PI * 2); ctx.fill();

    // 浮动光点
    this.particles.forEach(p => {
      const px = p.x + Math.cos(f * 0.02 + p.phase) * p.amplitude;
      const py = p.y + Math.sin(f * 0.025 + p.phase) * p.amplitude * 0.7;
      const alpha = p.opacity * (0.5 + 0.5 * Math.sin(f * 0.04 + p.phase));
      ctx.fillStyle = `rgba(255,240,200,${alpha})`;
      ctx.beginPath(); ctx.arc(px, py, p.r, 0, Math.PI * 2); ctx.fill();
    });

    // 镜头炫光
    this.drawLensFlare(x - r * 1.4, y - r * 1.1);
  }

  drawLensFlare(x, y) {
    const ctx = this.ctx;
    const f = this.frame;
    for (let i = 0; i < 5; i++) {
      const ox = x + i * 14;
      const oy = y + i * 8;
      const alpha = 0.12 - i * 0.022;
      const rad = 6 + i * 2.5 + Math.sin(f * 0.05 + i) * 2;
      ctx.fillStyle = `rgba(200,220,255,${alpha})`;
      ctx.beginPath(); ctx.arc(ox, oy, rad, 0, Math.PI * 2); ctx.fill();
    }
  }

  // ========== 大风 ==========
  initWindy() {
    // 云
    this.clouds = [];
    for (let i = 0; i < 5; i++) {
      this.clouds.push({
        x: -80 + Math.random() * this.w * 0.6,
        y: 10 + Math.random() * this.h * 0.35,
        w: 40 + Math.random() * 60,
        h: 18 + Math.random() * 22,
        speed: 0.4 + Math.random() * 0.7,
        opacity: 0.5 + Math.random() * 0.4,
        bubbles: Array.from({ length: 3 + Math.floor(Math.random() * 3) }, () => ({
          rx: -15 + Math.random() * 30,
          ry: -8 + Math.random() * 10,
          r: 10 + Math.random() * 16,
        })),
      });
    }
    // 树
    this.trees = [];
    for (let i = 0; i < 5; i++) {
      const tx = 40 + i * (this.w / 5.5) + Math.random() * 20;
      this.trees.push({
        x: tx,
        baseY: this.h * 0.7,
        height: 50 + Math.random() * 55,
        swayAmp: 2 + Math.random() * 5,
        swaySpeed: 0.025 + Math.random() * 0.03,
        swayPhase: Math.random() * Math.PI * 2,
        branches: 3 + Math.floor(Math.random() * 3),
      });
    }
    // 风线
    this.particles = [];
    for (let i = 0; i < 60; i++) {
      this.particles.push({
        x: Math.random() * this.w,
        y: Math.random() * this.h,
        len: 15 + Math.random() * 55,
        speed: 1.2 + Math.random() * 3.5,
        opacity: 0.1 + Math.random() * 0.35,
        thickness: 0.5 + Math.random() * 1.8,
      });
    }
    // 落叶
    this.leafParticles = [];
    for (let i = 0; i < 15; i++) {
      this.leafParticles.push({
        x: Math.random() * this.w,
        y: Math.random() * this.h,
        r: 2 + Math.random() * 4,
        vx: 0.8 + Math.random() * 1.8,
        vy: -0.2 + Math.random() * 0.6,
        rot: Math.random() * Math.PI * 2,
        rotV: -0.03 + Math.random() * 0.06,
        color: `hsl(${25 + Math.random() * 30}, ${60 + Math.random() * 30}%, ${40 + Math.random() * 25}%)`,
      });
    }
  }

  drawWindy() {
    const ctx = this.ctx;
    const f = this.frame;

    // 底部渐变 - 地面
    const gg = ctx.createLinearGradient(0, this.h * 0.68, 0, this.h);
    gg.addColorStop(0, 'rgba(40,50,40,0)');
    gg.addColorStop(1, 'rgba(30,40,30,0.5)');
    ctx.fillStyle = gg;
    ctx.fillRect(0, this.h * 0.68, this.w, this.h * 0.32);

    // 云
    this.clouds.forEach(c => {
      c.x += c.speed;
      if (c.x > this.w + 100) { c.x = -120; c.y = 10 + Math.random() * this.h * 0.3; }

      ctx.save();
      ctx.globalAlpha = c.opacity;
      const cx = c.x + c.w / 2;
      const cy = c.y + c.h / 2;

      // 云主体
      ctx.fillStyle = 'rgba(220,225,235,0.85)';
      ctx.beginPath();
      ctx.ellipse(cx, cy, c.w / 2, c.h / 2, 0, 0, Math.PI * 2);
      ctx.fill();

      // 气泡凸起
      ctx.fillStyle = 'rgba(230,235,245,0.9)';
      c.bubbles.forEach(b => {
        ctx.beginPath();
        ctx.arc(cx + b.rx, cy + b.ry, b.r, 0, Math.PI * 2);
        ctx.fill();
      });

      // 云底部阴影
      const cg = ctx.createRadialGradient(cx, cy + c.h * 0.2, c.w * 0.1, cx, cy, c.w * 0.5);
      cg.addColorStop(0, 'rgba(180,185,195,0.3)');
      cg.addColorStop(1, 'rgba(220,225,235,0)');
      ctx.fillStyle = cg;
      ctx.beginPath();
      ctx.ellipse(cx, cy, c.w / 2, c.h / 2, 0, 0, Math.PI * 2);
      ctx.fill();

      ctx.restore();
    });

    // 树
    this.trees.forEach(tree => {
      const sway = Math.sin(f * tree.swaySpeed + tree.swayPhase) * tree.swayAmp;
      const tx = tree.x;
      const ty = tree.baseY;

      // 树干
      ctx.strokeStyle = 'rgba(80,65,45,0.75)';
      ctx.lineWidth = 4;
      ctx.lineCap = 'round';
      ctx.beginPath();
      ctx.moveTo(tx, ty);
      ctx.quadraticCurveTo(tx + sway * 0.3, ty - tree.height * 0.5, tx + sway, ty - tree.height);
      ctx.stroke();

      // 树枝
      for (let b = 0; b < tree.branches; b++) {
        const by = ty - tree.height * (0.3 + b * 0.2);
        const swayB = sway * (1 + b * 0.6);
        ctx.strokeStyle = 'rgba(80,65,45,0.5)';
        ctx.lineWidth = 2 - b * 0.3;
        ctx.beginPath();
        ctx.moveTo(tx + swayB * 0.25, by);
        const bx = tx + swayB + (b % 2 === 0 ? 1 : -1) * (12 + b * 4);
        ctx.quadraticCurveTo(tx + swayB * 0.5 + (b % 2 === 0 ? 5 : -5), by - 8, bx, by - 12);
        ctx.stroke();

        // 叶片簇
        ctx.fillStyle = `rgba(${50 + b * 20}, ${100 + b * 25}, ${35 + b * 15}, 0.6)`;
        ctx.beginPath();
        ctx.arc(bx, by - 14, 9 + b * 2, 0, Math.PI * 2);
        ctx.fill();

        // 次级叶簇
        ctx.fillStyle = `rgba(${70 + b * 15}, ${110 + b * 20}, ${45 + b * 10}, 0.4)`;
        ctx.beginPath();
        ctx.arc(bx + 3, by - 16, 7 + b * 1.5, 0, Math.PI * 2);
        ctx.fill();
      }

      // 树冠顶
      ctx.fillStyle = 'rgba(65,125,45,0.55)';
      ctx.beginPath();
      ctx.arc(tx + sway, ty - tree.height, 14, 0, Math.PI * 2);
      ctx.fill();
      ctx.fillStyle = 'rgba(80,140,55,0.4)';
      ctx.beginPath();
      ctx.arc(tx + sway + 4, ty - tree.height - 3, 10, 0, Math.PI * 2);
      ctx.fill();
    });

    // 风线
    ctx.lineCap = 'round';
    this.particles.forEach(p => {
      p.x += p.speed;
      if (p.x > this.w + 60) { p.x = -60; p.y = Math.random() * this.h; }
      const alpha = p.opacity * (0.6 + 0.4 * Math.sin(f * 0.05 + p.y * 0.1));
      ctx.strokeStyle = `rgba(200,210,230,${alpha})`;
      ctx.lineWidth = p.thickness;
      ctx.beginPath();
      ctx.moveTo(p.x, p.y);
      ctx.quadraticCurveTo(p.x + p.len * 0.5, p.y + Math.sin(f * 0.04 + p.y * 0.05) * 3, p.x + p.len, p.y + Math.sin(f * 0.03 + p.y * 0.04) * 5);
      ctx.stroke();
    });

    // 落叶
    this.leafParticles.forEach(l => {
      l.x += l.vx + Math.sin(f * 0.03 + l.y * 0.1) * 0.5;
      l.y += l.vy + Math.abs(Math.cos(f * 0.04 + l.x * 0.1)) * 0.3;
      l.rot += l.rotV;
      if (l.x > this.w + 20) { l.x = -20; l.y = Math.random() * this.h * 0.6; }
      if (l.y > this.h + 20) { l.y = -20; l.x = Math.random() * this.w; }

      ctx.save();
      ctx.translate(l.x, l.y);
      ctx.rotate(l.rot);
      ctx.fillStyle = l.color;
      ctx.beginPath();
      ctx.ellipse(0, 0, l.r, l.r * 0.45, 0, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();
    });
  }

  // ========== 暴雨 ==========
  initStorm() {
    // 雨滴
    this.particles = [];
    for (let i = 0; i < 200; i++) {
      this.particles.push({
        x: Math.random() * this.w,
        y: Math.random() * this.h,
        len: 12 + Math.random() * 22,
        speed: 6 + Math.random() * 12,
        opacity: 0.25 + Math.random() * 0.55,
        thickness: 0.6 + Math.random() * 1.2,
      });
    }
    // 水洼
    this.waterPuddles = [];
    for (let i = 0; i < 8; i++) {
      this.waterPuddles.push({
        x: Math.random() * this.w,
        y: this.h * 0.7 + Math.random() * this.h * 0.25,
        rx: 18 + Math.random() * 35,
        ry: 4 + Math.random() * 8,
        rippleCount: 1 + Math.floor(Math.random() * 3),
        ripplePhase: Math.random() * Math.PI * 2,
        rippleSpeed: 0.03 + Math.random() * 0.06,
      });
    }
    // 闪电
    this.bolts = [];
    this.nextBolt = 40 + Math.floor(Math.random() * 120);
    // 云层
    this.clouds = [];
    for (let i = 0; i < 6; i++) {
      this.clouds.push({
        x: -40 + i * (this.w / 5.5),
        y: 0 + Math.random() * this.h * 0.15,
        w: 70 + Math.random() * 50,
        h: 28 + Math.random() * 20,
        opacity: 0.45 + Math.random() * 0.35,
      });
    }
  }

  generateBolt(sx, sy) {
    const points = [{ x: sx, y: sy }];
    const ex = sx + (Math.random() - 0.5) * 80;
    const ey = this.h * 0.6 + Math.random() * this.h * 0.3;
    const segments = 6 + Math.floor(Math.random() * 8);
    const dx = (ex - sx) / segments;
    const dy = (ey - sy) / segments;
    for (let i = 1; i <= segments; i++) {
      const jitter = (i < segments) ? (Math.random() - 0.5) * 50 : 0;
      points.push({
        x: sx + dx * i + jitter,
        y: sy + dy * i,
      });
    }
    return {
      points,
      alpha: 0.7 + Math.random() * 0.3,
      flickerDur: 4 + Math.floor(Math.random() * 8),
      life: 0,
      maxLife: 6 + Math.floor(Math.random() * 10),
    };
  }

  drawStorm() {
    const ctx = this.ctx;
    const f = this.frame;

    // 暗色氛围渐变
    const ag = ctx.createLinearGradient(0, 0, 0, this.h);
    ag.addColorStop(0, 'rgba(15,20,35,0.5)');
    ag.addColorStop(0.5, 'rgba(20,25,40,0.3)');
    ag.addColorStop(1, 'rgba(10,15,30,0.6)');
    ctx.fillStyle = ag;
    ctx.fillRect(0, 0, this.w, this.h);

    // 云层
    this.clouds.forEach(c => {
      const cx = c.x + c.w / 2;
      const cy = c.y + c.h / 2;
      ctx.fillStyle = `rgba(30,35,55,${c.opacity})`;
      ctx.beginPath();
      ctx.ellipse(cx, cy, c.w / 2, c.h / 2, 0, 0, Math.PI * 2);
      ctx.fill();

      // 云底暗部
      const cg = ctx.createRadialGradient(cx, cy + c.h * 0.2, c.w * 0.1, cx, cy, c.w * 0.5);
      cg.addColorStop(0, 'rgba(15,18,30,0.5)');
      cg.addColorStop(1, 'rgba(30,35,55,0)');
      ctx.fillStyle = cg;
      ctx.beginPath();
      ctx.ellipse(cx, cy, c.w / 2, c.h / 2, 0, 0, Math.PI * 2);
      ctx.fill();
    });

    // 闪电
    if (f % this.nextBolt === 0) {
      const sx = Math.random() * this.w;
      this.bolts.push(this.generateBolt(sx, 0));
      this.nextBolt = 35 + Math.floor(Math.random() * 130);
    }

    this.bolts = this.bolts.filter(b => {
      b.life++;
      const flicker = b.life < b.flickerDur ? (Math.random() > 0.4 ? 1 : 0.3) : 1;
      const alpha = b.alpha * (1 - b.life / b.maxLife) * flicker;

      if (b.life >= b.maxLife) return false;

      // 发光外层
      ctx.save();
      ctx.globalAlpha = alpha * 0.4;
      ctx.strokeStyle = 'rgba(180,200,255,1)';
      ctx.lineWidth = 8;
      ctx.shadowColor = 'rgba(150,180,255,0.8)';
      ctx.shadowBlur = 20;
      ctx.beginPath();
      ctx.moveTo(b.points[0].x, b.points[0].y);
      for (let i = 1; i < b.points.length; i++) {
        ctx.lineTo(b.points[i].x, b.points[i].y);
      }
      ctx.stroke();
      ctx.restore();

      // 主闪电体
      ctx.save();
      ctx.globalAlpha = alpha;
      ctx.strokeStyle = 'rgba(220,235,255,1)';
      ctx.lineWidth = 2;
      ctx.shadowColor = 'rgba(200,220,255,0.9)';
      ctx.shadowBlur = 8;
      ctx.beginPath();
      ctx.moveTo(b.points[0].x, b.points[0].y);
      for (let i = 1; i < b.points.length; i++) {
        ctx.lineTo(b.points[i].x, b.points[i].y);
      }
      ctx.stroke();
      ctx.restore();

      return true;
    });

    // 雨滴
    ctx.lineCap = 'round';
    this.particles.forEach(p => {
      p.y += p.speed;
      if (p.y > this.h + 20) {
        p.y = -20;
        p.x = Math.random() * this.w;
      }

      const alpha = p.opacity;
      ctx.strokeStyle = `rgba(180,200,230,${alpha})`;
      ctx.lineWidth = p.thickness;
      ctx.beginPath();
      ctx.moveTo(p.x, p.y);
      ctx.lineTo(p.x - p.thickness * 2, p.y + p.len);
      ctx.stroke();

      // 雨滴尾迹
      ctx.strokeStyle = `rgba(180,200,230,${alpha * 0.3})`;
      ctx.lineWidth = p.thickness * 0.5;
      ctx.beginPath();
      ctx.moveTo(p.x, p.y + p.len);
      ctx.lineTo(p.x - p.thickness * 2, p.y + p.len + 6);
      ctx.stroke();
    });

    // 水洼
    this.waterPuddles.forEach(p => {
      // 水洼主体
      ctx.fillStyle = 'rgba(60,80,110,0.35)';
      ctx.beginPath();
      ctx.ellipse(p.x, p.y, p.rx, p.ry, 0, 0, Math.PI * 2);
      ctx.fill();

      // 水洼高光
      ctx.fillStyle = 'rgba(140,170,200,0.2)';
      ctx.beginPath();
      ctx.ellipse(p.x - p.rx * 0.15, p.y - p.ry * 0.2, p.rx * 0.5, p.ry * 0.5, 0, 0, Math.PI * 2);
      ctx.fill();

      // 涟漪
      for (let r = 0; r < p.rippleCount; r++) {
        const prog = (Math.sin(f * p.rippleSpeed + p.ripplePhase + r * 2.5) + 1) / 2;
        const rad = p.rx * (0.3 + prog * 0.7);
        ctx.strokeStyle = `rgba(180,200,220,${0.3 * (1 - prog)})`;
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.ellipse(p.x, p.y, rad, rad * (p.ry / p.rx), 0, 0, Math.PI * 2);
        ctx.stroke();
      }
    });
  }

  // ========== 暴雪 ==========
  initSnow() {
    // 雪花
    this.particles = [];
    for (let i = 0; i < 160; i++) {
      this.particles.push({
        x: Math.random() * this.w,
        y: Math.random() * this.h,
        r: 1 + Math.random() * 3.5,
        speed: 0.3 + Math.random() * 1.5,
        wind: (Math.random() - 0.5) * 0.6,
        wobbleAmp: 0.3 + Math.random() * 1.2,
        wobbleSpeed: 0.01 + Math.random() * 0.03,
        opacity: 0.5 + Math.random() * 0.5,
        phase: Math.random() * Math.PI * 2,
        type: Math.floor(Math.random() * 3), // 0=圆,1=星,2=椭圆
      });
    }
    // 积雪
    this.snowAccum = [];
    const segW = this.w / 20;
    for (let i = 0; i < 20; i++) {
      this.snowAccum.push({
        x: i * segW,
        w: segW + 2,
        h: 15 + Math.random() * 25,
        targetH: 15 + Math.random() * 25,
      });
    }
    // 增长积雪的计时
    this.accumTimer = 0;
    this.accumInterval = 40 + Math.floor(Math.random() * 40);
  }

  drawSnow() {
    const ctx = this.ctx;
    const f = this.frame;

    // 冷蓝背景渐变
    const ag = ctx.createLinearGradient(0, 0, 0, this.h);
    ag.addColorStop(0, 'rgba(20,25,55,0.4)');
    ag.addColorStop(0.6, 'rgba(25,35,65,0.3)');
    ag.addColorStop(1, 'rgba(200,210,225,0.25)');
    ctx.fillStyle = ag;
    ctx.fillRect(0, 0, this.w, this.h);

    // 冰冻地面
    const ig = ctx.createLinearGradient(0, this.h * 0.72, 0, this.h);
    ig.addColorStop(0, 'rgba(200,210,225,0)');
    ig.addColorStop(0.3, 'rgba(210,220,235,0.3)');
    ig.addColorStop(1, 'rgba(230,240,250,0.5)');
    ctx.fillStyle = ig;
    ctx.fillRect(0, this.h * 0.72, this.w, this.h * 0.28);

    // 积雪层
    this.accumTimer++;
    if (this.accumTimer >= this.accumInterval) {
      this.accumTimer = 0;
      this.accumInterval = 30 + Math.floor(Math.random() * 50);
      const idx = Math.floor(Math.random() * this.snowAccum.length);
      this.snowAccum[idx].targetH = Math.min(60, this.snowAccum[idx].targetH + 0.5 + Math.random() * 1.5);
    }

    this.snowAccum.forEach(s => {
      s.h += (s.targetH - s.h) * 0.02;
      const baseY = this.h * 0.72 + 8;
      const topY = baseY - s.h;

      // 积雪主体
      ctx.fillStyle = 'rgba(235,240,250,0.65)';
      ctx.beginPath();
      ctx.moveTo(s.x, baseY);
      ctx.quadraticCurveTo(s.x + s.w / 2, topY, s.x + s.w, baseY);
      ctx.fill();

      // 雪面微光
      ctx.fillStyle = 'rgba(250,252,255,0.25)';
      ctx.beginPath();
      ctx.moveTo(s.x + s.w * 0.2, baseY);
      ctx.quadraticCurveTo(s.x + s.w / 2, topY + 2, s.x + s.w * 0.8, baseY);
      ctx.fill();
    });

    // 雪花
    this.particles.forEach(p => {
      p.y += p.speed;
      p.x += p.wind + Math.sin(f * p.wobbleSpeed + p.phase) * p.wobbleAmp;
      if (p.y > this.h + 10) {
        p.y = -10;
        p.x = Math.random() * this.w;
      }
      if (p.x < -10) p.x = this.w + 10;
      if (p.x > this.w + 10) p.x = -10;

      ctx.save();
      ctx.translate(p.x, p.y);
      ctx.globalAlpha = p.opacity;
      ctx.fillStyle = 'rgba(245,248,255,1)';

      if (p.type === 0) {
        // 圆点雪
        ctx.beginPath();
        ctx.arc(0, 0, p.r, 0, Math.PI * 2);
        ctx.fill();
      } else if (p.type === 1) {
        // 星形雪
        const r = p.r;
        ctx.beginPath();
        for (let i = 0; i < 6; i++) {
          const a = (Math.PI * 2 / 6) * i - Math.PI / 2;
          const lx = Math.cos(a) * r;
          const ly = Math.sin(a) * r;
          if (i === 0) ctx.moveTo(lx, ly);
          else ctx.lineTo(lx, ly);
          const sa = a + Math.PI / 6;
          const mx = Math.cos(sa) * r * 0.4;
          const my = Math.sin(sa) * r * 0.4;
          ctx.lineTo(mx, my);
        }
        ctx.closePath();
        ctx.fill();

        // 星形微光晕
        ctx.fillStyle = 'rgba(200,220,255,0.3)';
        ctx.beginPath();
        ctx.arc(0, 0, p.r * 1.6, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = 'rgba(245,248,255,1)';
      } else {
        // 椭圆雪
        ctx.beginPath();
        ctx.ellipse(0, 0, p.r, p.r * 0.6, Math.sin(f * 0.02 + p.phase) * 0.5, 0, Math.PI * 2);
        ctx.fill();
      }

      ctx.restore();
    });
  }
}

// ==================== 初始化 ====================
function init() {
  buildStars();
  buildCards();

  // 启动所有动画器
  cards.forEach(card => {
    const canvas = card.querySelector('canvas');
    const type = card.dataset.id;
    const anim = new WeatherAnimator(canvas, type);
    anim.init();
    animators.push(anim);
  });

  // 默认选中晴天
  setTimeout(() => selectCard('sunny'), 600);

  // 窗口大小变化时重新调整
  let resizeTimer;
  window.addEventListener('resize', () => {
    clearTimeout(resizeTimer);
    resizeTimer = setTimeout(() => animators.forEach(a => a.resize()), 200);
  });
}

// 星空
function buildStars() {
  const stars = document.getElementById('stars');
  for (let i = 0; i < 80; i++) {
    const star = document.createElement('div');
    star.className = 'star';
    star.style.left = Math.random() * 100 + '%';
    star.style.top = Math.random() * 100 + '%';
    star.style.width = (0.5 + Math.random() * 2) + 'px';
    star.style.height = star.style.width;
    star.style.setProperty('--dur', (2 + Math.random() * 5) + 's');
    star.style.setProperty('--delay', Math.random() * 5 + 's');
    stars.appendChild(star);
  }
}

init();
</script>
</body>
</html>

deepseek-v4-flash,几分钟吧 生成的,感觉还可以吧,有点慢可能用的人比较多

这是生成的一坨吗?


opus 4.7 里面的动效效果也很无敌

不懂就问,为啥佬们都用 天气卡片的测试 来对比呢?

1 个赞

一个是确实可以考察模型的前端能力。另外一点,大家都通常用这个来比较,已经对生成结果的好坏有所评判。算是一个比较直观简单的评测手段吧。

2 个赞