我感觉是提示词问题
1 个赞
2 个赞
感觉目前确实在总体审美上,国外的模型强一些,毕竟有全球资源,设计方面积累的资料都是好看的。但是现在国产的模型在功能上已经在狠狠追赶了。
2 个赞
这么看感觉还是审美上差点意思,就是不知道后端的需求解决能力怎么样
怎么感觉效果有点倒沫子啊,这效果感觉不太行啊 ![]()
以下是 ClaudeOpus4.6 生成的:
下边是 GPT5.5 生成的:
Gemini 3.1pro 生成的:
最后是 DeepSeek V4pro 生成的(哎,超级慢呀,得十来分钟感觉。御三家的早就生成好了):
你确定用的是 v4 吗?我的 web 端,还是 v3,v4 完全体还没上线。
我还以为那是窗户就是那么设计的,原来是定位错了吗?难绷
同样的提示词
<!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>
不懂就问,为啥佬们都用 天气卡片的测试 来对比呢?
一个是确实可以考察模型的前端能力。另外一点,大家都通常用这个来比较,已经对生成结果的好坏有所评判。算是一个比较直观简单的评测手段吧。
2 个赞
















