生成的html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>鹈鹕骑车 · Pelican Cyclist — SVG 2D 动画</title>
<style>
html, body { margin: 0; height: 100%; }
body {
display: flex; align-items: center; justify-content: center;
background: #0b1220;
}
svg {
width: min(94vw, calc(94vh * 4 / 3));
height: auto;
display: block;
border-radius: 16px;
box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
}
</style>
</head>
<body>
<svg viewBox="0 0 800 600" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="鹈鹕骑自行车的 2D 动画">
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#a9ddfb"/>
<stop offset="1" stop-color="#e9f8ff"/>
</linearGradient>
<linearGradient id="pouchGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#fbb257"/>
<stop offset="1" stop-color="#ee8d2c"/>
</linearGradient>
<g id="clouds">
<g fill="#ffffff" opacity="0.95">
<ellipse cx="120" cy="100" rx="40" ry="19"/>
<ellipse cx="158" cy="88" rx="30" ry="15"/>
<ellipse cx="186" cy="100" rx="26" ry="13"/>
</g>
<g fill="#ffffff" opacity="0.9">
<ellipse cx="500" cy="150" rx="34" ry="16"/>
<ellipse cx="538" cy="140" rx="26" ry="14"/>
<ellipse cx="563" cy="151" rx="24" ry="12"/>
</g>
<g fill="#ffffff" opacity="0.85">
<ellipse cx="315" cy="58" rx="26" ry="13"/>
<ellipse cx="344" cy="52" rx="20" ry="11"/>
<ellipse cx="360" cy="60" rx="17" ry="9"/>
</g>
</g>
<g id="scenery">
<path d="M -20 462 C 90 368 170 378 262 462 Z" fill="#c3e2a8"/>
<path d="M 178 462 C 300 336 430 346 558 462 Z" fill="#addc8e"/>
<path d="M 462 462 C 590 380 700 390 822 462 Z" fill="#c3e2a8"/>
<g>
<rect x="115" y="356" width="10" height="106" fill="#8a5a34"/>
<circle cx="120" cy="330" r="36" fill="#56a552"/>
<circle cx="98" cy="344" r="24" fill="#56a552"/>
<circle cx="144" cy="344" r="24" fill="#56a552"/>
</g>
<g>
<rect x="425" y="344" width="10" height="118" fill="#8a5a34"/>
<circle cx="430" cy="316" r="38" fill="#4c9c49"/>
<circle cx="406" cy="332" r="26" fill="#4c9c49"/>
<circle cx="456" cy="332" r="26" fill="#4c9c49"/>
</g>
<g>
<rect x="685" y="360" width="10" height="102" fill="#8a5a34"/>
<circle cx="690" cy="338" r="30" fill="#56a552"/>
<circle cx="670" cy="352" r="22" fill="#56a552"/>
<circle cx="711" cy="352" r="22" fill="#56a552"/>
</g>
<g fill="#6fbe6a">
<circle cx="36" cy="448" r="14"/>
<circle cx="54" cy="452" r="11"/>
<circle cx="20" cy="452" r="10"/>
<circle cx="250" cy="450" r="13"/>
<circle cx="266" cy="454" r="10"/>
<circle cx="520" cy="448" r="15"/>
<circle cx="540" cy="453" r="12"/>
<circle cx="502" cy="452" r="10"/>
<circle cx="762" cy="452" r="13"/>
<circle cx="778" cy="456" r="9"/>
</g>
<g>
<circle cx="92" cy="458" r="3.4" fill="#ffffff"/>
<circle cx="92" cy="458" r="1.6" fill="#ffd75e"/>
<circle cx="332" cy="459" r="3.4" fill="#ffffff"/>
<circle cx="332" cy="459" r="1.6" fill="#ffd75e"/>
<circle cx="602" cy="457" r="3.4" fill="#ffffff"/>
<circle cx="602" cy="457" r="1.6" fill="#ffd75e"/>
</g>
<g stroke="#4f9e4f" stroke-width="2.5" fill="none" stroke-linecap="round">
<path d="M 140 462 q 4 -16 8 0 M 144 462 q 4 -12 8 0"/>
<path d="M 470 462 q 4 -16 8 0 M 474 462 q 4 -12 8 0"/>
<path d="M 660 462 q 4 -16 8 0 M 664 462 q 4 -12 8 0"/>
</g>
</g>
<line id="dashSeg" x1="0" y1="535" x2="800" y2="535" stroke="#e8edf2" stroke-width="5" stroke-dasharray="44 36"/>
</defs>
<!-- 天空与太阳 -->
<rect x="0" y="0" width="800" height="600" fill="url(#sky)"/>
<g>
<circle cx="95" cy="92" r="44" fill="#ffd75e"/>
<g stroke="#ffd75e" stroke-width="6" stroke-linecap="round">
<line x1="95" y1="38" x2="95" y2="22"/>
<line x1="95" y1="146" x2="95" y2="160"/>
<line x1="41" y1="92" x2="25" y2="92"/>
<line x1="149" y1="92" x2="165" y2="92"/>
<line x1="133" y1="130" x2="146" y2="143"/>
<line x1="57" y1="130" x2="44" y2="143"/>
<line x1="133" y1="54" x2="146" y2="41"/>
<line x1="57" y1="54" x2="44" y2="41"/>
</g>
</g>
<!-- 缓慢漂移的云(视差) -->
<g id="cloudsWrap">
<use href="#clouds"/>
<use href="#clouds" x="800"/>
</g>
<!-- 滚动的远景:丘陵、树木、灌木、花草 -->
<g id="sceneryWrap">
<use href="#scenery"/>
<use href="#scenery" x="800"/>
</g>
<!-- 路面 -->
<rect x="0" y="462" width="800" height="8" fill="#7cc06f"/>
<rect x="0" y="470" width="800" height="130" fill="#7b8794"/>
<rect x="0" y="470" width="800" height="5" fill="#b9c2cc"/>
<!-- 路面虚线 -->
<g id="roadDashes">
<use href="#dashSeg"/>
<use href="#dashSeg" x="800"/>
</g>
<!-- 自行车投影 -->
<ellipse cx="405" cy="479" rx="170" ry="11" fill="#000000" opacity="0.12"/>
<!-- ===== 自行车 ===== -->
<g id="bike">
<!-- 后轮 -->
<circle cx="250" cy="400" r="70" stroke="#39404a" stroke-width="9" fill="none"/>
<circle cx="250" cy="400" r="62" stroke="#cfd5dc" stroke-width="3" fill="none"/>
<g id="spokesRear">
<g stroke="#a7adb6" stroke-width="2">
<line x1="250" y1="400" x2="311" y2="400"/>
<line x1="250" y1="400" x2="281" y2="453"/>
<line x1="250" y1="400" x2="220" y2="453"/>
<line x1="250" y1="400" x2="189" y2="400"/>
<line x1="250" y1="400" x2="220" y2="347"/>
<line x1="250" y1="400" x2="281" y2="347"/>
</g>
<circle cx="314" cy="400" r="3" fill="#ff7a7a"/>
</g>
<circle cx="250" cy="400" r="7" fill="#565b63"/>
<!-- 前轮 -->
<circle cx="560" cy="400" r="70" stroke="#39404a" stroke-width="9" fill="none"/>
<circle cx="560" cy="400" r="62" stroke="#cfd5dc" stroke-width="3" fill="none"/>
<g id="spokesFront">
<g stroke="#a7adb6" stroke-width="2">
<line x1="560" y1="400" x2="621" y2="400"/>
<line x1="560" y1="400" x2="591" y2="453"/>
<line x1="560" y1="400" x2="530" y2="453"/>
<line x1="560" y1="400" x2="499" y2="400"/>
<line x1="560" y1="400" x2="530" y2="347"/>
<line x1="560" y1="400" x2="591" y2="347"/>
</g>
<circle cx="624" cy="400" r="3" fill="#ff7a7a"/>
</g>
<circle cx="560" cy="400" r="7" fill="#565b63"/>
<!-- 链条与飞轮 -->
<line x1="395" y1="380" x2="250" y2="386" stroke="#4a5057" stroke-width="4"/>
<line x1="395" y1="420" x2="250" y2="414" stroke="#4a5057" stroke-width="4"/>
<circle cx="250" cy="400" r="13" fill="#666d75"/>
<!-- 车架 -->
<g stroke="#e05252" stroke-width="7" stroke-linecap="round" stroke-linejoin="round" fill="none">
<line x1="250" y1="400" x2="395" y2="400"/>
<line x1="395" y1="400" x2="352" y2="298"/>
<line x1="352" y1="298" x2="505" y2="290"/>
<line x1="395" y1="400" x2="505" y2="290"/>
<line x1="505" y1="290" x2="560" y2="400"/>
</g>
<!-- 车座 -->
<line x1="352" y1="298" x2="346" y2="291" stroke="#3d434c" stroke-width="6" stroke-linecap="round"/>
<path d="M 320 292 Q 345 282 370 292 Q 345 301 320 292 Z" fill="#3d434c"/>
<!-- 车把与车铃 -->
<line x1="505" y1="290" x2="499" y2="282" stroke="#33384a" stroke-width="9" stroke-linecap="round"/>
<path d="M 499 284 C 514 272 530 270 541 276" fill="none" stroke="#33384a" stroke-width="7" stroke-linecap="round"/>
<circle cx="501" cy="278" r="5" fill="#ffd75e" stroke="#d8a92e" stroke-width="1.5"/>
<!-- 牙盘与曲柄 -->
<circle cx="395" cy="400" r="20" fill="#52575f"/>
<circle cx="395" cy="400" r="11" fill="#9aa0a8"/>
<g id="crankGroup">
<line x1="395" y1="400" x2="417" y2="400" stroke="#6b7079" stroke-width="6" stroke-linecap="round"/>
<line x1="395" y1="400" x2="373" y2="400" stroke="#6b7079" stroke-width="6" stroke-linecap="round"/>
</g>
<rect id="pedNear" x="406" y="396" width="22" height="8" rx="3" fill="#3d434c" stroke="#2e333a" stroke-width="1.5"/>
<rect id="pedFar" x="362" y="396" width="22" height="8" rx="3" fill="#3d434c" stroke="#2e333a" stroke-width="1.5"/>
</g>
<!-- ===== 鹈鹕 ===== -->
<g id="rider">
<!-- 远侧腿与脚 -->
<path id="legFar" d="M 330 295 L 330 295 L 373 400" fill="none" stroke="#d98a2a" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path id="footFar" d="M -11 1 Q -2 -8 11 -1 Q 13 5 1 6 Q -13 6 -11 1 Z" fill="#d98a2a" stroke="#b96f1c" stroke-width="1.5" transform="translate(373 400)"/>
<!-- 脖子 -->
<path d="M 384 216 C 412 196 406 156 380 148" fill="none" stroke="#faf6ea" stroke-width="26" stroke-linecap="round"/>
<!-- 尾羽 -->
<path d="M 288 240 L 252 266 L 284 279 Z" fill="#faf6ea"/>
<!-- 身体 -->
<ellipse cx="342" cy="255" rx="58" ry="46" fill="#faf6ea"/>
<ellipse cx="330" cy="267" rx="38" ry="26" fill="#efe6c9"/>
<!-- 翅膀(拍动) -->
<g id="wingG">
<path d="M 334 238 C 302 246 284 266 284 290 C 312 287 330 268 344 250 Z" fill="#eee4c8" stroke="#d8cfa8" stroke-width="2"/>
</g>
<!-- 头部:喙、喉囊、眼睛、棒球帽 -->
<g id="headG">
<circle cx="372" cy="136" r="22" fill="#faf6ea"/>
<g id="pouchG">
<path d="M 388 134 C 420 132 466 133 508 136 C 514 148 502 166 480 174 C 452 184 424 184 406 174 C 393 165 384 150 388 134 Z" fill="url(#pouchGrad)" stroke="#e08a24" stroke-width="1.5"/>
</g>
<path d="M 388 124 C 452 116 502 124 508 136 C 462 130 414 130 388 134 Z" fill="#f6a623" stroke="#d8871c" stroke-width="1.5"/>
<circle cx="381" cy="129" r="3.4" fill="#2d2a26"/>
<circle cx="382.3" cy="127.7" r="1.2" fill="#ffffff"/>
<path d="M 352 130 C 350 108 374 102 388 116 C 381 126 366 130 352 130 Z" fill="#d94f4f"/>
<path d="M 352 128 C 336 124 320 126 312 133 C 326 137 340 136 352 132 Z" fill="#b23a3a"/>
</g>
<!-- 近侧腿与脚 -->
<path id="legNear" d="M 350 298 L 350 298 L 417 400" fill="none" stroke="#f2a13c" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
<path id="footNear" d="M -11 1 Q -2 -8 11 -1 Q 13 5 1 6 Q -13 6 -11 1 Z" fill="#f2a13c" stroke="#c47a20" stroke-width="1.5" transform="translate(417 400)"/>
</g>
<text x="16" y="588" font-size="18" fill="#f8fafc" opacity="0.8" font-family="'Segoe UI', system-ui, sans-serif">鹈鹕骑车 · Pelican Cyclist · SVG 2D 动画</text>
</svg>
<noscript><p style="color:#e2e8f0;font-family:system-ui">需要启用 JavaScript 才能看到动画。</p></noscript>
<script>
(function () {
'use strict';
var W = 800, BX = 395, BY = 400, CRANK = 22, RW = 70, OMEGA = 3.6;
var H1 = { x: 350, y: 298 }; // 近侧腿髋关节
var H2 = { x: 330, y: 295 }; // 远侧腿髋关节
var L1 = 62, L2 = 86; // 大腿 / 小腿长度
var $ = function (id) { return document.getElementById(id); };
var spokeR = $('spokesRear'), spokeF = $('spokesFront'), crankG = $('crankGroup');
var legFar = $('legFar'), legNear = $('legNear'), footFar = $('footFar'), footNear = $('footNear');
var pedFar = $('pedFar'), pedNear = $('pedNear');
var rider = $('rider'), wingG = $('wingG'), headG = $('headG'), pouchG = $('pouchG');
var sceneryWrap = $('sceneryWrap'), cloudsWrap = $('cloudsWrap'), roadDashes = $('roadDashes');
// 两骨 IK:由髋 H 与脚 F 求膝盖,膝盖向后弯(鸟类姿态)
function ik(H, F) {
var dx = F.x - H.x, dy = F.y - H.y;
var d = Math.hypot(dx, dy);
var maxd = L1 + L2 - 0.5;
if (d > maxd) { var s = maxd / d; dx *= s; dy *= s; d = maxd; }
var a = (L1 * L1 - L2 * L2 + d * d) / (2 * d);
var h = Math.sqrt(Math.max(0.01, L1 * L1 - a * a));
var ux = dx / d, uy = dy / d;
var px = -uy, py = ux;
if (px > 0) { px = -px; py = -py; }
return { x: H.x + ux * a + px * h, y: H.y + uy * a + py * h };
}
function setLeg(el, H, F) {
var k = ik(H, F);
el.setAttribute('d', 'M ' + H.x + ' ' + H.y + ' L ' + k.x.toFixed(1) + ' ' + k.y.toFixed(1) + ' L ' + F.x.toFixed(1) + ' ' + F.y.toFixed(1));
}
var start = null;
function frame(t) {
if (start === null) start = t;
var dt = (t - start) / 1000;
var th = OMEGA * dt;
var deg = th * 180 / Math.PI;
var dist = OMEGA * RW * dt;
var off = dist % W;
var bobY = 2.6 * Math.sin(2 * th); // 车身/身体起伏
// 车轮、曲柄旋转
spokeR.setAttribute('transform', 'rotate(' + deg + ' 250 400)');
spokeF.setAttribute('transform', 'rotate(' + deg + ' 560 400)');
crankG.setAttribute('transform', 'rotate(' + deg + ' ' + BX + ' ' + BY + ')');
// 踏板位置(左右 180° 相位差)
var p1 = { x: BX + CRANK * Math.cos(th), y: BY + CRANK * Math.sin(th) };
var p2 = { x: BX - CRANK * Math.cos(th), y: BY - CRANK * Math.sin(th) };
var f1 = { x: p1.x, y: p1.y - bobY };
var f2 = { x: p2.x, y: p2.y - bobY };
// 腿跟随踏板(IK)
setLeg(legNear, H1, f1);
setLeg(legFar, H2, f2);
// 蹼足:基本保持水平,轻微随踩踏摆动
var fAng = 12 * Math.sin(th) + 6;
footNear.setAttribute('transform', 'translate(' + f1.x.toFixed(1) + ' ' + f1.y.toFixed(1) + ') rotate(' + fAng.toFixed(1) + ')');
footFar.setAttribute('transform', 'translate(' + f2.x.toFixed(1) + ' ' + f2.y.toFixed(1) + ') rotate(' + fAng.toFixed(1) + ')');
// 水平踏板
pedNear.setAttribute('x', (p1.x - 11).toFixed(1));
pedNear.setAttribute('y', (p1.y - 4).toFixed(1));
pedFar.setAttribute('x', (p2.x - 11).toFixed(1));
pedFar.setAttribute('y', (p2.y - 4).toFixed(1));
// 鹈鹕整体起伏、翅膀拍动、头部点头、喉囊摆动
rider.setAttribute('transform', 'translate(0 ' + bobY.toFixed(2) + ')');
wingG.setAttribute('transform', 'rotate(' + (9 * Math.sin(1.6 * th + 0.6)).toFixed(1) + ' 330 245)');
headG.setAttribute('transform', 'rotate(' + (2.5 * Math.sin(2 * th + 1)).toFixed(1) + ' 376 155)');
pouchG.setAttribute('transform', 'rotate(' + (2.2 * Math.sin(2 * th + 0.3)).toFixed(1) + ' 390 136)');
// 背景滚动(远景、云、路面虚线)
sceneryWrap.setAttribute('transform', 'translate(' + (-off).toFixed(1) + ' 0)');
cloudsWrap.setAttribute('transform', 'translate(' + (-((dist * 0.35) % W)).toFixed(1) + ' 0)');
roadDashes.setAttribute('transform', 'translate(' + (-off).toFixed(1) + ' 0)');
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
})();
</script>
</body>
</html>