各家agent + 模型 鹈鹕骑车测试(长期)
泼痞
2026-08-13 19:28
1
【测试时间】:2026-08-13 19:40:00
【测试agent + 模型】:Cursor pro + grok4.6 high fast
提示词:【生成 html,内容是 svg 绘制鹈鹕骑自行车 2D 动画,不用进行测试】
耗时 3m13s

【测试时间】:2026-08-14 10:20:00
【测试agent + 模型】:DSH(DeepSeek web) + Deepseek-V4-Pro high
提示词:【生成 html,内容是 svg 绘制鹈鹕骑自行车 2D 动画,不用进行测试】
耗时 2m33s

【测试时间】:2026-08-23 8:30:00
【测试agent + 模型】:Claude Code + Fable5
提示词:【生成 html,内容是 svg 绘制鹈鹕骑自行车 2D 动画,不用进行测试】
耗时 5m30s

代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>鹈鹕骑自行车</title>
<style>
html, body {
margin: 0;
min-height: 100vh;
background: linear-gradient(160deg, #0d1b2a, #1b263b);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 14px;
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
}
svg {
width: min(96vw, 1100px);
height: auto;
border-radius: 16px;
box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
}
.caption {
color: #9fb3c8;
font-size: 14px;
letter-spacing: 2px;
}
</style>
</head>
<body>
<svg viewBox="0 0 900 500" role="img" aria-label="鹈鹕骑自行车的2D动画">
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#8ecdf2"/>
<stop offset="0.7" stop-color="#cbe9f7"/>
<stop offset="1" stop-color="#eaf6e8"/>
</linearGradient>
<radialGradient id="sunGlow" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="#ffe9a8" stop-opacity="0.9"/>
<stop offset="1" stop-color="#ffe9a8" stop-opacity="0"/>
</radialGradient>
</defs>
<!-- 天空 -->
<rect x="0" y="0" width="900" height="500" fill="url(#sky)"/>
<!-- 太阳 -->
<g transform="translate(790 80)">
<circle r="70" fill="url(#sunGlow)"/>
<g stroke="#ffd66b" stroke-width="4" stroke-linecap="round">
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="40s" repeatCount="indefinite"/>
<line y1="-42" y2="-56"/>
<line y1="-42" y2="-56" transform="rotate(45)"/>
<line y1="-42" y2="-56" transform="rotate(90)"/>
<line y1="-42" y2="-56" transform="rotate(135)"/>
<line y1="-42" y2="-56" transform="rotate(180)"/>
<line y1="-42" y2="-56" transform="rotate(225)"/>
<line y1="-42" y2="-56" transform="rotate(270)"/>
<line y1="-42" y2="-56" transform="rotate(315)"/>
</g>
<circle r="34" fill="#ffd66b"/>
</g>
<!-- 云 -->
<g transform="translate(0 95)">
<g fill="#ffffff" opacity="0.92">
<animateTransform attributeName="transform" type="translate" from="980 0" to="-300 0" dur="34s" repeatCount="indefinite"/>
<ellipse cx="0" cy="0" rx="38" ry="16"/>
<ellipse cx="28" cy="-8" rx="30" ry="14"/>
<ellipse cx="-30" cy="-5" rx="26" ry="12"/>
</g>
</g>
<g transform="translate(0 155)">
<g fill="#ffffff" opacity="0.8">
<animateTransform attributeName="transform" type="translate" from="1000 0" to="-260 0" dur="24s" repeatCount="indefinite"/>
<ellipse cx="0" cy="0" rx="28" ry="12"/>
<ellipse cx="22" cy="-6" rx="22" ry="10"/>
<ellipse cx="-22" cy="-4" rx="19" ry="9"/>
</g>
</g>
<!-- 远处飞鸟 -->
<g transform="translate(600 85)" stroke="#4a5b70" stroke-width="2.5" fill="none" stroke-linecap="round">
<g>
<animateTransform attributeName="transform" type="translate" values="0 0; -34 6; 0 0" dur="9s" repeatCount="indefinite"/>
<path d="M 0 0 q 7 -7 14 0 q 7 -7 14 0"/>
<path d="M 44 22 q 6 -6 12 0 q 6 -6 12 0"/>
</g>
</g>
<!-- 远山 -->
<path d="M 380 424 Q 640 348 940 424 Z" fill="#b7dba1"/>
<path d="M -20 424 Q 180 312 400 424 Z" fill="#9fcd84"/>
<path d="M 300 424 Q 560 330 830 424 Z" fill="#8bc172"/>
<!-- 草地 -->
<rect x="0" y="418" width="900" height="26" fill="#79b95e"/>
<!-- 滚动的近景植物 -->
<g>
<animateTransform attributeName="transform" type="translate" from="0 0" to="-900 0" dur="3.6s" repeatCount="indefinite"/>
<g id="scenery">
<g transform="translate(90 430)">
<ellipse cx="0" cy="0" rx="26" ry="14" fill="#4f8f3e"/>
<ellipse cx="20" cy="-3" rx="18" ry="11" fill="#5da04a"/>
</g>
<g transform="translate(300 428)">
<rect x="-4" y="-24" width="8" height="28" fill="#7a5230"/>
<circle cx="0" cy="-34" r="19" fill="#4f8f3e"/>
<circle cx="-13" cy="-26" r="13" fill="#5da04a"/>
<circle cx="13" cy="-26" r="13" fill="#5da04a"/>
</g>
<g transform="translate(480 432)">
<ellipse cx="0" cy="0" rx="20" ry="11" fill="#5da04a"/>
<circle cx="-6" cy="-8" r="4" fill="#f7d24b"/>
<circle cx="8" cy="-5" r="4" fill="#ef8bb1"/>
</g>
<g transform="translate(660 428)">
<rect x="-4" y="-22" width="8" height="26" fill="#7a5230"/>
<circle cx="0" cy="-31" r="17" fill="#569645"/>
<circle cx="-12" cy="-23" r="11" fill="#63a851"/>
<circle cx="12" cy="-23" r="11" fill="#63a851"/>
</g>
<g transform="translate(800 431)">
<ellipse cx="0" cy="0" rx="24" ry="13" fill="#4f8f3e"/>
<ellipse cx="-18" cy="-2" rx="15" ry="9" fill="#5da04a"/>
</g>
</g>
<use href="#scenery" x="900"/>
</g>
<!-- 公路 -->
<rect x="0" y="440" width="900" height="60" fill="#4f4f5a"/>
<line x1="0" y1="443" x2="900" y2="443" stroke="#8b8b97" stroke-width="3"/>
<line x1="0" y1="472" x2="900" y2="472" stroke="#f2f0e6" stroke-width="5" stroke-dasharray="34 26">
<animate attributeName="stroke-dashoffset" from="0" to="60" dur="0.24s" repeatCount="indefinite"/>
</line>
<!-- 车影 -->
<ellipse cx="430" cy="452" rx="195" ry="11" fill="#000" opacity="0.16"/>
<!-- 速度线 -->
<g stroke="#ffffff" stroke-width="4" stroke-linecap="round">
<line x1="150" y1="250" x2="205" y2="250">
<animate attributeName="opacity" values="0;0.55;0" dur="0.7s" repeatCount="indefinite"/>
</line>
<line x1="120" y1="310" x2="185" y2="310">
<animate attributeName="opacity" values="0;0.5;0" dur="0.7s" begin="0.25s" repeatCount="indefinite"/>
</line>
<line x1="160" y1="370" x2="215" y2="370">
<animate attributeName="opacity" values="0;0.45;0" dur="0.7s" begin="0.45s" repeatCount="indefinite"/>
</line>
</g>
<!-- 远侧腿 + 远侧曲柄/踏板(画在车架后面) -->
<path id="legFar" d="M 396 262 L 453.5 305.2 L 396.1 345.3" fill="none" stroke="#c47f16" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
<line id="crankFar" x1="415" y1="370" x2="395.1" y2="353.3" stroke="#5a5a63" stroke-width="5" stroke-linecap="round"/>
<g id="pedalFar" transform="translate(395.1 353.3)">
<rect x="-13" y="-3" width="26" height="6" rx="2.5" fill="#3a3a42"/>
</g>
<g id="footFar" transform="translate(395.1 353.3)">
<path d="M -10 -2 L 16 -2 L 9 -12 L -4 -10 Z" fill="#d98f22" stroke="#a86a10" stroke-width="1.5" stroke-linejoin="round"/>
</g>
<!-- 自行车 -->
<!-- 后轮 -->
<g transform="translate(300 380)">
<g>
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="1.5s" repeatCount="indefinite"/>
<circle r="60" fill="none" stroke="#22252b" stroke-width="9"/>
<circle r="52" fill="none" stroke="#9aa1ab" stroke-width="2.5"/>
<g stroke="#9aa1ab" stroke-width="2">
<line x1="-52" y1="0" x2="52" y2="0"/>
<line x1="0" y1="-52" x2="0" y2="52"/>
<line x1="-37" y1="-37" x2="37" y2="37"/>
<line x1="-37" y1="37" x2="37" y2="-37"/>
</g>
<circle r="6" fill="#33363d"/>
</g>
</g>
<!-- 前轮 -->
<g transform="translate(560 380)">
<g>
<animateTransform attributeName="transform" type="rotate" from="0" to="360" dur="1.5s" repeatCount="indefinite"/>
<circle r="60" fill="none" stroke="#22252b" stroke-width="9"/>
<circle r="52" fill="none" stroke="#9aa1ab" stroke-width="2.5"/>
<g stroke="#9aa1ab" stroke-width="2">
<line x1="-52" y1="0" x2="52" y2="0"/>
<line x1="0" y1="-52" x2="0" y2="52"/>
<line x1="-37" y1="-37" x2="37" y2="37"/>
<line x1="-37" y1="37" x2="37" y2="-37"/>
</g>
<circle r="6" fill="#33363d"/>
</g>
</g>
<!-- 链条 -->
<g stroke="#3a3a3a" stroke-width="2.5" stroke-dasharray="5 3">
<line x1="302" y1="373" x2="413" y2="359"/>
<line x1="302" y1="387" x2="413" y2="381"/>
</g>
<circle cx="300" cy="380" r="8" fill="none" stroke="#4a4a52" stroke-width="3"/>
<!-- 车架 -->
<g stroke="#d64545" stroke-width="7" stroke-linecap="round" fill="none">
<line x1="300" y1="380" x2="415" y2="370"/>
<line x1="300" y1="380" x2="383" y2="278"/>
<line x1="415" y1="370" x2="380" y2="272"/>
<line x1="415" y1="370" x2="528" y2="288"/>
<line x1="381" y1="276" x2="524" y2="284"/>
<line x1="533" y1="268" x2="560" y2="380"/>
</g>
<!-- 座杆与车座 -->
<line x1="380" y1="272" x2="377" y2="262" stroke="#33363d" stroke-width="5"/>
<line x1="358" y1="259" x2="397" y2="257" stroke="#22252b" stroke-width="9" stroke-linecap="round"/>
<!-- 车把 -->
<line x1="533" y1="268" x2="536" y2="252" stroke="#33363d" stroke-width="5" stroke-linecap="round"/>
<path d="M 536 252 C 547 248, 554 253, 551 264" fill="none" stroke="#22252b" stroke-width="6" stroke-linecap="round"/>
<!-- 牙盘 -->
<circle cx="415" cy="370" r="13" fill="#44464e"/>
<circle cx="415" cy="370" r="4.5" fill="#6b6e78"/>
<!-- 鹈鹕 -->
<g>
<!-- 围巾 -->
<g>
<path d="M 412 180 L 350 188 L 359 195 L 354 202 L 414 194 Z" fill="#e05252">
<animateTransform attributeName="transform" type="rotate" values="-6 414 186; 9 414 186; -6 414 186" dur="0.8s" repeatCount="indefinite"/>
</path>
</g>
<!-- 尾羽 -->
<path d="M 340 230 L 302 202 L 322 238 L 296 230 L 330 252 Z" fill="#e9e4d8" stroke="#d9d2c2" stroke-width="2" stroke-linejoin="round"/>
<!-- 身体 -->
<ellipse cx="385" cy="215" rx="58" ry="42" fill="#f6f3ec" stroke="#d9d2c2" stroke-width="2.5" transform="rotate(-12 385 215)"/>
<!-- 颈 + 头 + 喙(轻微点头) -->
<g>
<animateTransform attributeName="transform" type="translate" values="0 0; 0 3.5; 0 0" dur="1.4s" repeatCount="indefinite"/>
<path d="M 412 195 C 448 188, 438 134, 462 113" fill="none" stroke="#f6f3ec" stroke-width="16" stroke-linecap="round"/>
<circle cx="468" cy="106" r="20" fill="#f6f3ec" stroke="#d9d2c2" stroke-width="2"/>
<path d="M 456 90 q 4 -9 12 -7" fill="none" stroke="#d9d2c2" stroke-width="3" stroke-linecap="round"/>
<!-- 上喙 -->
<path d="M 470 99 L 592 115 L 470 111 Z" fill="#f2a03d" stroke="#d97f1e" stroke-width="1.5" stroke-linejoin="round"/>
<!-- 喉囊 -->
<path d="M 473 111 C 496 150, 556 144, 590 116 C 558 127, 500 131, 473 117 Z" fill="#e8893a" stroke="#d97f1e" stroke-width="1.5" stroke-linejoin="round"/>
<!-- 眼睛 -->
<circle cx="474" cy="98" r="6" fill="#ffffff" stroke="#c9c2b2" stroke-width="1"/>
<circle cx="475.5" cy="98" r="2.8" fill="#22252b"/>
</g>
<!-- 扶车把的翅膀 -->
<path d="M 402 196 C 452 200, 502 226, 545 254 C 549 260, 543 265, 536 262 C 494 243, 447 225, 397 220 Z"
fill="#efe9db" stroke="#d9d2c2" stroke-width="2.5" stroke-linejoin="round"/>
<path d="M 420 208 C 458 216, 492 232, 520 248" fill="none" stroke="#d9d2c2" stroke-width="2"/>
</g>
<!-- 近侧曲柄/踏板/腿(画在最上层) -->
<line id="crankNear" x1="415" y1="370" x2="434.9" y2="386.7" stroke="#2f2f35" stroke-width="5.5" stroke-linecap="round"/>
<g id="pedalNear" transform="translate(434.9 386.7)">
<rect x="-13" y="-3" width="26" height="6" rx="2.5" fill="#22252b"/>
</g>
<path id="legNear" d="M 396 262 L 449.6 310.1 L 436 378.7" fill="none" stroke="#f0a12f" stroke-width="11" stroke-linecap="round" stroke-linejoin="round"/>
<g id="footNear" transform="translate(434.9 386.7)">
<path d="M -10 -2 L 16 -2 L 9 -12 L -4 -10 Z" fill="#f0a12f" stroke="#c97e14" stroke-width="1.5" stroke-linejoin="round"/>
</g>
</svg>
<div class="caption">🚲 鹈鹕骑行 · SVG 2D 动画</div>
<script>
(function () {
// 曲柄与骑行腿部(两段式反向运动学),与车轮同步:1 圈 / 1.5s
var C = { x: 415, y: 370 }; // 中轴
var R = 26; // 曲柄半径
var HIP = { x: 396, y: 262 }; // 髋关节
var L1 = 72, L2 = 70; // 大腿 / 小腿长度
var PERIOD = 1500;
var el = {
crankNear: document.getElementById('crankNear'),
crankFar: document.getElementById('crankFar'),
pedalNear: document.getElementById('pedalNear'),
pedalFar: document.getElementById('pedalFar'),
footNear: document.getElementById('footNear'),
footFar: document.getElementById('footFar'),
legNear: document.getElementById('legNear'),
legFar: document.getElementById('legFar')
};
function pedalPos(a) {
return { x: C.x + R * Math.cos(a), y: C.y + R * Math.sin(a) };
}
function legD(p) {
var ankle = { x: p.x + 1, y: p.y - 8 };
var dx = ankle.x - HIP.x, dy = ankle.y - HIP.y;
var d = Math.max(Math.abs(L1 - L2) + 0.5, Math.min(L1 + L2 - 0.5, Math.hypot(dx, dy)));
var a = Math.atan2(dy, dx);
var alpha = Math.acos((L1 * L1 + d * d - L2 * L2) / (2 * L1 * d));
// a - alpha:膝盖朝前(+x 方向)
var k = { x: HIP.x + L1 * Math.cos(a - alpha), y: HIP.y + L1 * Math.sin(a - alpha) };
return 'M ' + HIP.x + ' ' + HIP.y +
' L ' + k.x.toFixed(1) + ' ' + k.y.toFixed(1) +
' L ' + ankle.x.toFixed(1) + ' ' + ankle.y.toFixed(1);
}
function place(g, p) {
g.setAttribute('transform', 'translate(' + p.x.toFixed(1) + ' ' + p.y.toFixed(1) + ')');
}
function frame(now) {
var a = (now % PERIOD) / PERIOD * Math.PI * 2;
var near = pedalPos(a);
var far = pedalPos(a + Math.PI);
el.crankNear.setAttribute('x2', near.x.toFixed(1));
el.crankNear.setAttribute('y2', near.y.toFixed(1));
el.crankFar.setAttribute('x2', far.x.toFixed(1));
el.crankFar.setAttribute('y2', far.y.toFixed(1));
place(el.pedalNear, near);
place(el.footNear, near);
place(el.pedalFar, far);
place(el.footFar, far);
el.legNear.setAttribute('d', legD(near));
el.legFar.setAttribute('d', legD(far));
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
})();
</script>
</body>
</html>
【测试时间】:2026-08-23 19:40:00
【测试agent + 模型】:Claude 网页端 + opus5 high
提示词:【生成 html,内容是 svg 绘制鹈鹕骑自行车 2D 动画,不用进行测试】
耗时 3m30s

代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>鹈鹕骑自行车 · SVG 动画</title>
<style>
:root{
--ink:#22303a;
--paper:#f6efe2;
--sea:#2f7f8c;
--coral:#e4593f;
--mustard:#f0b429;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;
background:
radial-gradient(120% 90% at 50% 0%, #14343d 0%, #0d2028 60%, #08151b 100%);
display:flex;align-items:center;justify-content:center;
padding:24px;
font-family:"Avenir Next","Helvetica Neue","PingFang SC","Microsoft YaHei",sans-serif;
color:var(--paper);
}
.frame{
width:min(100%,980px);
background:var(--paper);
border-radius:6px;
padding:14px 14px 0;
box-shadow:0 40px 80px -20px rgba(0,0,0,.7), 0 0 0 1px rgba(255,255,255,.06);
}
.screen{
border-radius:3px;
overflow:hidden;
background:#bfe3ef;
}
svg{display:block;width:100%;height:auto}
.plate{
display:flex;align-items:baseline;gap:14px;
padding:12px 4px 14px;
color:var(--ink);
border-top:1px solid rgba(34,48,58,.18);
margin-top:12px;
}
.plate b{
font-size:15px;letter-spacing:.34em;text-transform:uppercase;
font-weight:700;
}
.plate span{
font-size:12px;letter-spacing:.18em;opacity:.55;margin-left:auto;
font-variant-numeric:tabular-nums;
}
/* ---------- 环境动画(CSS) ---------- */
@keyframes drift{from{transform:translateX(980px)}to{transform:translateX(-420px)}}
@keyframes roll{to{transform:translateX(-180px)}}
@keyframes rollFar{to{transform:translateX(-240px)}}
@keyframes zip{
0%{transform:translateX(60px);opacity:0}
25%{opacity:.85}
100%{transform:translateX(-320px);opacity:0}
}
@keyframes glow{0%,100%{opacity:.55}50%{opacity:.9}}
#cloudA{animation:drift 26s linear infinite}
#cloudB{animation:drift 34s linear infinite;animation-delay:-11s}
#cloudC{animation:drift 20s linear infinite;animation-delay:-6s}
#dashes{animation:roll .82s linear infinite}
#pebbles{animation:roll 1.1s linear infinite}
#bushes{animation:rollFar 3.4s linear infinite}
#sunRing{animation:glow 5s ease-in-out infinite}
.zip1{animation:zip 1.1s linear infinite}
.zip2{animation:zip 1.1s linear infinite;animation-delay:-.37s}
.zip3{animation:zip 1.1s linear infinite;animation-delay:-.74s}
@media (prefers-reduced-motion: reduce){
*{animation:none !important}
}
</style>
</head>
<body>
<div class="frame">
<div class="screen">
<svg id="scene" viewBox="0 0 900 500" xmlns="http://www.w3.org/2000/svg" role="img"
aria-label="一只鹈鹕骑着自行车沿海边公路前行的循环动画">
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#4fb3c9"/>
<stop offset="55%" stop-color="#a9e0e9"/>
<stop offset="100%" stop-color="#f7dcb4"/>
</linearGradient>
<linearGradient id="plume" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#fffdf7"/>
<stop offset="100%" stop-color="#e6dcc8"/>
</linearGradient>
<linearGradient id="bill" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#f7b32b"/>
<stop offset="100%" stop-color="#e07c1c"/>
</linearGradient>
<linearGradient id="pouch" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#ffcf6b"/>
<stop offset="100%" stop-color="#f09a22"/>
</linearGradient>
<linearGradient id="road" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#4d5560"/>
<stop offset="100%" stop-color="#333a44"/>
</linearGradient>
<radialGradient id="shade" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#1d2830" stop-opacity=".38"/>
<stop offset="100%" stop-color="#1d2830" stop-opacity="0"/>
</radialGradient>
<clipPath id="box"><rect x="0" y="0" width="900" height="500"/></clipPath>
</defs>
<g clip-path="url(#box)">
<!-- 天空 -->
<rect x="0" y="0" width="900" height="420" fill="url(#sky)"/>
<!-- 太阳 -->
<g>
<circle id="sunRing" cx="742" cy="112" r="58" fill="#ffe9b0" opacity=".6"/>
<circle cx="742" cy="112" r="36" fill="#ffd166"/>
</g>
<!-- 云 -->
<g fill="#ffffff" opacity=".9">
<g id="cloudA" transform="translate(0,0)">
<g transform="translate(-300,64)">
<ellipse cx="60" cy="20" rx="46" ry="18"/>
<ellipse cx="96" cy="12" rx="30" ry="20"/>
<ellipse cx="26" cy="24" rx="26" ry="14"/>
</g>
</g>
<g id="cloudB" opacity=".75">
<g transform="translate(-360,150)">
<ellipse cx="50" cy="16" rx="38" ry="13"/>
<ellipse cx="82" cy="10" rx="24" ry="16"/>
</g>
</g>
<g id="cloudC" opacity=".55">
<g transform="translate(-320,32)">
<ellipse cx="40" cy="14" rx="30" ry="11"/>
<ellipse cx="66" cy="9" rx="20" ry="13"/>
</g>
</g>
</g>
<!-- 远海 + 远山 -->
<path d="M0 336 C120 320 210 344 320 332 C440 318 520 342 640 330 C760 318 830 338 900 328 L900 420 L0 420 Z"
fill="#2f7f8c" opacity=".55"/>
<path d="M0 358 C90 342 170 362 268 352 C380 340 470 366 580 356 C700 344 800 364 900 354 L900 420 L0 420 Z"
fill="#1f6470" opacity=".55"/>
<!-- 草地 -->
<rect x="0" y="392" width="900" height="30" fill="#8ac59b"/>
<rect x="0" y="392" width="900" height="7" fill="#a7d9b3"/>
<!-- 灌木(快速掠过) -->
<g id="bushes" fill="#5aa678">
<g><ellipse cx="60" cy="392" rx="26" ry="15"/><ellipse cx="82" cy="394" rx="16" ry="10"/></g>
<g transform="translate(240,0)"><ellipse cx="60" cy="393" rx="20" ry="12"/></g>
<g transform="translate(470,0)"><ellipse cx="60" cy="392" rx="30" ry="16"/><ellipse cx="34" cy="395" rx="15" ry="9"/></g>
<g transform="translate(700,0)"><ellipse cx="60" cy="393" rx="22" ry="13"/></g>
<g transform="translate(940,0)"><ellipse cx="60" cy="392" rx="26" ry="15"/><ellipse cx="82" cy="394" rx="16" ry="10"/></g>
<g transform="translate(1180,0)"><ellipse cx="60" cy="393" rx="20" ry="12"/></g>
</g>
<!-- 公路 -->
<rect x="0" y="418" width="900" height="82" fill="url(#road)"/>
<rect x="0" y="418" width="900" height="4" fill="#6b7480" opacity=".7"/>
<!-- 中线 -->
<g id="dashes" fill="#f4e3b8" opacity=".92">
<rect x="-180" y="462" width="92" height="9" rx="4.5"/>
<rect x="0" y="462" width="92" height="9" rx="4.5"/>
<rect x="180" y="462" width="92" height="9" rx="4.5"/>
<rect x="360" y="462" width="92" height="9" rx="4.5"/>
<rect x="540" y="462" width="92" height="9" rx="4.5"/>
<rect x="720" y="462" width="92" height="9" rx="4.5"/>
<rect x="900" y="462" width="92" height="9" rx="4.5"/>
<rect x="1080" y="462" width="92" height="9" rx="4.5"/>
</g>
<!-- 路面碎石 -->
<g id="pebbles" fill="#8b95a1" opacity=".45">
<circle cx="-140" cy="436" r="3"/><circle cx="-40" cy="486" r="2.4"/>
<circle cx="46" cy="444" r="2.6"/><circle cx="140" cy="492" r="3"/>
<circle cx="226" cy="432" r="2.2"/><circle cx="322" cy="480" r="2.8"/>
<circle cx="400" cy="440" r="2.4"/><circle cx="500" cy="488" r="3"/>
<circle cx="586" cy="434" r="2.6"/><circle cx="676" cy="482" r="2.2"/>
<circle cx="760" cy="446" r="2.8"/><circle cx="860" cy="490" r="2.4"/>
<circle cx="940" cy="436" r="3"/><circle cx="1040" cy="486" r="2.4"/>
</g>
<!-- 速度线 -->
<g stroke="#ffffff" stroke-linecap="round" opacity=".8">
<line class="zip1" x1="300" y1="252" x2="376" y2="252" stroke-width="5"/>
<line class="zip2" x1="286" y1="318" x2="344" y2="318" stroke-width="4"/>
<line class="zip3" x1="312" y1="212" x2="360" y2="212" stroke-width="3"/>
</g>
<!-- ================= 骑行主体 ================= -->
<g id="rig">
<!-- 地面投影 -->
<ellipse cx="470" cy="424" rx="185" ry="18" fill="url(#shade)"/>
<!-- 远侧脚踏 -->
<g id="pedalFar">
<rect x="-16" y="-4" width="32" height="8" rx="3" fill="#2c3540"/>
<rect x="-16" y="-4" width="32" height="3" rx="1.5" fill="#5a6472"/>
</g>
<!-- 远侧腿 -->
<g id="legFar" opacity=".72">
<line id="thighFar" stroke="#d9cdb6" stroke-width="24" stroke-linecap="round"/>
<line id="shinFar" stroke="#d98b1f" stroke-width="11" stroke-linecap="round"/>
<circle id="kneeFar" r="11" fill="#d9cdb6"/>
<g id="footFar">
<path d="M-13 -3 L13 -3 L20 3 L-13 5 Z" fill="#d98b1f"/>
</g>
</g>
<!-- 远侧翅膀 -->
<path d="M446 224 C492 214 540 230 584 250 C560 260 518 260 486 251 C464 245 448 237 446 224 Z"
fill="#ddd2bc"/>
<!-- ============ 自行车 ============ -->
<g id="bike">
<!-- 后轮 -->
<g id="wheelRear">
<circle cx="330" cy="362" r="56" fill="none" stroke="#232b34" stroke-width="9"/>
<circle cx="330" cy="362" r="48" fill="none" stroke="#c9ccd2" stroke-width="4"/>
<circle cx="330" cy="362" r="8" fill="#3b444f"/>
</g>
<!-- 前轮 -->
<g id="wheelFront">
<circle cx="612" cy="362" r="56" fill="none" stroke="#232b34" stroke-width="9"/>
<circle cx="612" cy="362" r="48" fill="none" stroke="#c9ccd2" stroke-width="4"/>
<circle cx="612" cy="362" r="8" fill="#3b444f"/>
</g>
<!-- 链条 -->
<g stroke="#4a5360" stroke-width="4" stroke-dasharray="5 4">
<line x1="452" y1="357" x2="330" y2="353"/>
<line x1="452" y1="387" x2="330" y2="371"/>
</g>
<circle cx="330" cy="362" r="11" fill="none" stroke="#7c8794" stroke-width="4"/>
<!-- 车架 -->
<g stroke="#e4593f" stroke-width="10" stroke-linecap="round" fill="none">
<line x1="455" y1="372" x2="398" y2="284"/> <!-- 立管 -->
<line x1="455" y1="372" x2="566" y2="300"/> <!-- 下管 -->
<line x1="402" y1="290" x2="574" y2="272"/> <!-- 上管 -->
<line x1="455" y1="372" x2="330" y2="362"/> <!-- 后下叉 -->
<line x1="330" y1="362" x2="400" y2="290"/> <!-- 后上叉 -->
<line x1="566" y1="300" x2="580" y2="266"/> <!-- 头管 -->
</g>
<line x1="570" y1="296" x2="612" y2="362" stroke="#2c3540" stroke-width="8" stroke-linecap="round"/>
<!-- 车把 -->
<g fill="none" stroke="#2c3540" stroke-width="8" stroke-linecap="round">
<path d="M579 264 L585 248 C589 240 599 238 605 244"/>
</g>
<circle cx="604" cy="246" r="7" fill="#1f262e"/>
<!-- 座垫 -->
<path d="M372 278 C384 270 404 270 414 276 C404 286 384 288 372 284 Z" fill="#2c3540"/>
<line x1="398" y1="284" x2="396" y2="292" stroke="#8f98a4" stroke-width="6"/>
<!-- 车铃 -->
<circle cx="572" cy="256" r="7" fill="#f0b429" stroke="#c98d12" stroke-width="2"/>
</g>
<!-- 曲柄 + 牙盘 -->
<g id="crank">
<circle cx="455" cy="372" r="20" fill="none" stroke="#b7bec9" stroke-width="4"/>
<g stroke="#b7bec9" stroke-width="3">
<line x1="443" y1="360" x2="467" y2="384"/>
<line x1="467" y1="360" x2="443" y2="384"/>
</g>
<line x1="455" y1="372" x2="482" y2="372" stroke="#2c3540" stroke-width="7" stroke-linecap="round"/>
<line x1="455" y1="372" x2="428" y2="372" stroke="#3d4854" stroke-width="7" stroke-linecap="round"/>
<circle cx="455" cy="372" r="6" fill="#2c3540"/>
</g>
<!-- ============ 鹈鹕 ============ -->
<!-- 尾羽 -->
<g id="tail">
<path d="M368 238 L318 218 L346 246 L308 252 L352 268 Z" fill="#e3d8c2"/>
</g>
<!-- 身体 -->
<path d="M358 262 C352 222 380 192 424 190 C470 188 498 212 500 244
C502 278 470 300 428 300 C388 300 362 286 358 262 Z"
fill="url(#plume)" stroke="#cfc3ac" stroke-width="2"/>
<path d="M370 282 C392 296 434 300 466 286 C444 300 396 302 370 282 Z" fill="#e8dfcb"/>
<!-- 颈 + 头 + 喙 -->
<g id="head">
<path d="M478 216 C500 208 522 198 536 186" fill="none" stroke="url(#plume)"
stroke-width="36" stroke-linecap="round"/>
<path d="M478 216 C500 208 522 198 536 186" fill="none" stroke="#e8dfcb"
stroke-width="10" stroke-linecap="round" opacity=".6"/>
<circle cx="546" cy="176" r="25" fill="url(#plume)"/>
<path d="M534 156 C544 148 556 150 560 158 C550 154 540 154 534 156 Z" fill="#e0d4bc"/>
<!-- 喉囊 -->
<g id="pouch">
<path d="M562 176 C600 181 644 190 682 193 C674 221 634 234 596 226
C572 220 560 199 562 176 Z" fill="url(#pouch)" stroke="#d98b1f" stroke-width="2"/>
<path d="M572 196 C598 206 632 214 660 210" fill="none" stroke="#e0a23a" stroke-width="2.5" opacity=".8"/>
</g>
<!-- 上喙 -->
<path d="M561 163 C600 159 646 171 686 185 L681 196 C641 183 596 176 561 178 Z"
fill="url(#bill)" stroke="#c9761a" stroke-width="2"/>
<path d="M686 185 C692 186 694 191 690 195 L681 196 Z" fill="#c9761a"/>
<!-- 眼睛 -->
<circle cx="553" cy="167" r="6" fill="#2b2118"/>
<circle cx="555" cy="165" r="2" fill="#ffffff"/>
</g>
<!-- 围巾 -->
<g id="scarf" fill="#e4593f">
<path d="M508 197 C497 214 476 221 452 214 C470 227 494 228 513 212 Z"/>
<path d="M502 202 C472 208 440 198 412 180 C436 206 470 218 504 214 Z" opacity=".92"/>
</g>
<!-- 近侧翅膀(搭在车把上) -->
<g id="wing">
<path d="M452 214 C500 203 549 219 598 240 C578 255 540 259 500 251
C470 245 452 232 452 214 Z"
fill="url(#plume)" stroke="#cfc3ac" stroke-width="2"/>
<g stroke="#dcd1ba" stroke-width="2.5" fill="none">
<path d="M486 224 C512 226 546 234 572 243"/>
<path d="M480 234 C506 238 536 246 560 250"/>
</g>
</g>
<!-- 近侧脚踏 -->
<g id="pedalNear">
<rect x="-17" y="-4.5" width="34" height="9" rx="3" fill="#1f262e"/>
<rect x="-17" y="-4.5" width="34" height="3" rx="1.5" fill="#6d7684"/>
</g>
<!-- 近侧腿 -->
<g id="legNear">
<line id="thighNear" stroke="#f3ecdd" stroke-width="26" stroke-linecap="round"/>
<line id="shinNear" stroke="#f0932b" stroke-width="12" stroke-linecap="round"/>
<circle id="kneeNear" r="12" fill="#f3ecdd"/>
<g id="footNear">
<path d="M-14 -4 L14 -4 L22 3 L-14 6 Z" fill="#f0932b"/>
<g stroke="#d97d16" stroke-width="1.5">
<line x1="2" y1="-3" x2="6" y2="4"/>
<line x1="10" y1="-3" x2="13" y2="4"/>
</g>
</g>
</g>
</g>
</g>
</svg>
</div>
<div class="plate">
<b>Pelican on Tour</b>
<span>SVG · 纯前端循环动画</span>
</div>
</div>
<script>
(function(){
const NS = "http://www.w3.org/2000/svg";
const $ = id => document.getElementById(id);
// 几何参数
const BB = {x:455, y:372}; // 五通中心
const CR = 27; // 曲柄长度
const HIP = {x:400, y:286}; // 髋关节
const L1 = 66; // 大腿
const L2 = 68; // 小腿
const CAD = 2.5; // 踏频 (rad/s)
// 生成车轮辐条
function buildSpokes(gid, cx, cy, r){
const g = $(gid);
if(!g) return;
for(let i=0;i<12;i++){
const a = i*Math.PI/6;
const l = document.createElementNS(NS,"line");
l.setAttribute("x1", cx + Math.cos(a)*7);
l.setAttribute("y1", cy + Math.sin(a)*7);
l.setAttribute("x2", cx + Math.cos(a)*(r-6));
l.setAttribute("y2", cy + Math.sin(a)*(r-6));
l.setAttribute("stroke", "#e9ecf0");
l.setAttribute("stroke-width", "2.2");
l.setAttribute("opacity", ".85");
g.appendChild(l);
}
}
buildSpokes("wheelRear", 330, 362, 56);
buildSpokes("wheelFront", 612, 362, 56);
// 两连杆逆解:由脚踏位置求膝关节
function solveLeg(px, py, thigh, shin, knee, foot){
let dx = px - HIP.x, dy = py - HIP.y;
let d = Math.hypot(dx, dy) || 0.001;
const lo = Math.abs(L1 - L2) + 1, hi = L1 + L2 - 1;
const dc = Math.max(lo, Math.min(hi, d));
const ux = dx / d, uy = dy / d;
const a = (dc*dc + L1*L1 - L2*L2) / (2*dc);
const h = Math.sqrt(Math.max(0, L1*L1 - a*a));
const bx = HIP.x + ux*a, by = HIP.y + uy*a;
// 两个解中取更靠前(x 更大)的,让膝盖朝行进方向弯曲
const k1 = {x: bx - uy*h, y: by + ux*h};
const k2 = {x: bx + uy*h, y: by - ux*h};
const k = (k1.x > k2.x) ? k1 : k2;
thigh.setAttribute("x1", HIP.x); thigh.setAttribute("y1", HIP.y);
thigh.setAttribute("x2", k.x); thigh.setAttribute("y2", k.y);
shin.setAttribute("x1", k.x); shin.setAttribute("y1", k.y);
shin.setAttribute("x2", px); shin.setAttribute("y2", py - 6);
knee.setAttribute("cx", k.x); knee.setAttribute("cy", k.y);
foot.setAttribute("transform", `translate(${px.toFixed(2)},${(py-6).toFixed(2)})`);
}
const el = {
rig: $("rig"),
crank: $("crank"),
wheelR: $("wheelRear"),
wheelF: $("wheelFront"),
pedalNear: $("pedalNear"),
pedalFar: $("pedalFar"),
thighNear: $("thighNear"), shinNear: $("shinNear"), kneeNear: $("kneeNear"), footNear: $("footNear"),
thighFar: $("thighFar"), shinFar: $("shinFar"), kneeFar: $("kneeFar"), footFar: $("footFar"),
head: $("head"),
wing: $("wing"),
scarf: $("scarf"),
tail: $("tail"),
pouch: $("pouch")
};
const DEG = 180 / Math.PI;
let ang = 0, last = performance.now();
function tick(now){
const dt = Math.min(0.05, (now - last) / 1000);
last = now;
ang += dt * CAD;
// 曲柄与脚踏
const nx = BB.x + Math.cos(ang) * CR, ny = BB.y + Math.sin(ang) * CR;
const fx = BB.x - Math.cos(ang) * CR, fy = BB.y - Math.sin(ang) * CR;
el.crank.setAttribute("transform", `rotate(${(ang*DEG).toFixed(2)} ${BB.x} ${BB.y})`);
el.pedalNear.setAttribute("transform", `translate(${nx.toFixed(2)},${ny.toFixed(2)})`);
el.pedalFar.setAttribute("transform", `translate(${fx.toFixed(2)},${fy.toFixed(2)})`);
// 车轮(比曲柄快约 3 倍)
const w = ang * 3 * DEG;
el.wheelR.setAttribute("transform", `rotate(${w.toFixed(2)} 330 362)`);
el.wheelF.setAttribute("transform", `rotate(${w.toFixed(2)} 612 362)`);
// 双腿
solveLeg(nx, ny, el.thighNear, el.shinNear, el.kneeNear, el.footNear);
solveLeg(fx, fy, el.thighFar, el.shinFar, el.kneeFar, el.footFar);
// 身体上下起伏(每圈两次)
const bob = Math.sin(ang * 2) * 2.6;
el.rig.setAttribute("transform", `translate(0,${bob.toFixed(2)})`);
// 头颈点动、翅膀微振、尾羽与围巾随风
el.head.setAttribute("transform", `rotate(${(Math.sin(ang*2 + .6)*1.8).toFixed(2)} 520 200)`);
el.wing.setAttribute("transform", `rotate(${(Math.sin(ang*2)*2.6).toFixed(2)} 452 216)`);
el.tail.setAttribute("transform", `rotate(${(Math.sin(ang*2 + 1.2)*3.4).toFixed(2)} 368 244)`);
el.scarf.setAttribute("transform", `rotate(${(Math.sin(ang*3)*7).toFixed(2)} 510 200)`);
// 喉囊随颠簸轻晃
const s = 1 + 0.045 * Math.sin(ang * 2 + 1.1);
el.pouch.setAttribute("transform",
`translate(562,176) scale(1,${s.toFixed(3)}) translate(-562,-176)`);
requestAnimationFrame(tick);
}
if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches){
// 尊重系统设置:只摆一个静态姿态
const nx = BB.x + CR*Math.cos(-0.6), ny = BB.y + CR*Math.sin(-0.6);
const fx = BB.x - CR*Math.cos(-0.6), fy = BB.y - CR*Math.sin(-0.6);
el.pedalNear.setAttribute("transform", `translate(${nx},${ny})`);
el.pedalFar.setAttribute("transform", `translate(${fx},${fy})`);
el.crank.setAttribute("transform", `rotate(${-0.6*DEG} ${BB.x} ${BB.y})`);
solveLeg(nx, ny, el.thighNear, el.shinNear, el.kneeNear, el.footNear);
solveLeg(fx, fy, el.thighFar, el.shinFar, el.kneeFar, el.footFar);
} else {
requestAnimationFrame(tick);
}
})();
</script>
</body>
</html>
最新回复 (3)
-
bamboo2026 08-13 19:391楼现在的测试结果应该不存在版本错误的问题了
-
blade 08-18 15:242楼佬真棒,在佬的科普下学会了很多!
-
shenlvcheng 08-26 09:093楼佬真棒,在佬的科普下学会了很多!
* 帖子来源Linux.do
附近帖子