v4p max+dsh 鹈鹕骑自行车

OctoberSama 2026-08-13 22:13 1



HTML:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>鹈鹕骑自行车 — SVG 动画</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🚴</text></svg>">
<style>
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 26px 16px 40px;
font-family: system-ui, "PingFang SC", "Microsoft YaHei", "Segoe UI", sans-serif;
color: #1e293b;
background: linear-gradient(180deg, #7cc4ee 0%, #b8e2f7 55%, #eef9ff 100%);
}
header { text-align: center; margin-bottom: 16px; }
h1 {
margin: 0;
font-size: clamp(26px, 4.5vw, 42px);
letter-spacing: 2px;
text-shadow: 0 2px 0 rgba(255,255,255,.55);
}
.sub { margin: 8px 0 0; color: #35566e; font-size: 14px; }
.card {
width: 100%;
max-width: 1080px;
background: #fff;
border-radius: 22px;
box-shadow: 0 24px 70px rgba(23, 58, 94, .28);
padding: 14px;
}
svg { display: block; width: 100%; height: auto; border-radius: 13px; }
.controls {
display: flex;
gap: 18px;
align-items: center;
justify-content: center;
flex-wrap: wrap;
margin-top: 16px;
padding: 4px 0 8px;
}
.btn {
border: none;
cursor: pointer;
font-size: 15px;
font-weight: 600;
padding: 9px 20px;
border-radius: 999px;
color: #0f3a5c;
background: #e3f0fa;
box-shadow: 0 2px 8px rgba(30,80,120,.18);
transition: transform .12s ease, box-shadow .12s ease, background .12s ease;
}
.btn:hover { transform: translateY(-1px); box-shadow: 0 5px 14px rgba(30,80,120,.25); }
.btn:active { transform: translateY(0); }
.btn.accent { color: #7c2d12; background: #ffe3c2; }
.speed { display: flex; align-items: center; gap: 10px; font-size: 14px; color: #475569; }
.speed .val { min-width: 42px; font-variant-numeric: tabular-nums; font-weight: 700; color: #0f3a5c; }
input[type="range"] { width: 190px; accent-color: #2563eb; }
.hint { margin: 10px 0 0; color: #5b7690; font-size: 12.5px; text-align: center; }
</style>
</head>
<body>
<header>
<h1>鹈鹕骑自行车</h1>
<p class="sub">Pelican on a Bicycle · 纯 SVG + JS 动画 · 轮子、踏板、腿、翅膀全联动</p>
</header>

<div class="card">
<svg id="scene" viewBox="0 0 1200 620" role="img" aria-label="一只鹈鹕骑着自行车在路上前行">
<defs>
<linearGradient id="skyGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#6ec3f5"/>
<stop offset="1" stop-color="#e8f7ff"/>
</linearGradient>
<radialGradient id="sunGrad">
<stop offset="0" stop-color="#ffe28a"/>
<stop offset="1" stop-color="#ffb347"/>
</radialGradient>
<line id="spoke" x1="0" y1="-6" x2="0" y2="-48" stroke="#9aa5b1" stroke-width="1.8"/>
<line id="ray" x1="0" y1="-72" x2="0" y2="-102" stroke="#ffd86b" stroke-width="5" stroke-linecap="round" opacity=".85"/>
<clipPath id="bodyClip">
<ellipse cx="505" cy="300" rx="76" ry="44" transform="rotate(-12 505 300)"/>
</clipPath>
</defs>

<!-- 天空 -->
<rect x="0" y="0" width="1200" height="620" fill="url(#skyGrad)"/>

<!-- 太阳 -->
<g>
<circle cx="1050" cy="110" r="62" fill="#ffd86b" opacity=".25"/>
<g id="sunRays">
<use href="#ray" transform="rotate(0 1050 110)"/>
<use href="#ray" transform="rotate(36 1050 110)"/>
<use href="#ray" transform="rotate(72 1050 110)"/>
<use href="#ray" transform="rotate(108 1050 110)"/>
<use href="#ray" transform="rotate(144 1050 110)"/>
<use href="#ray" transform="rotate(180 1050 110)"/>
<use href="#ray" transform="rotate(216 1050 110)"/>
<use href="#ray" transform="rotate(252 1050 110)"/>
<use href="#ray" transform="rotate(288 1050 110)"/>
<use href="#ray" transform="rotate(324 1050 110)"/>
<animateTransform attributeName="transform" type="rotate" from="0 1050 110" to="360 1050 110" dur="50s" repeatCount="indefinite"/>
</g>
<circle cx="1050" cy="110" r="46" fill="url(#sunGrad)"/>
</g>

<!-- 云(JS 控制飘动) -->
<g class="cloud" data-x="180" data-y="100" data-f="0.10" data-s="1.1" fill="#fff" opacity=".96">
<ellipse cx="0" cy="0" rx="40" ry="22"/><ellipse cx="30" cy="6" rx="30" ry="18"/>
<ellipse cx="-30" cy="8" rx="26" ry="16"/><ellipse cx="2" cy="-11" rx="24" ry="15"/>
</g>
<g class="cloud" data-x="560" data-y="150" data-f="0.07" data-s="0.75" fill="#fff" opacity=".94">
<ellipse cx="0" cy="0" rx="40" ry="22"/><ellipse cx="30" cy="6" rx="30" ry="18"/>
<ellipse cx="-30" cy="8" rx="26" ry="16"/><ellipse cx="2" cy="-11" rx="24" ry="15"/>
</g>
<g class="cloud" data-x="980" data-y="70" data-f="0.12" data-s="1.25" fill="#fff" opacity=".95">
<ellipse cx="0" cy="0" rx="40" ry="22"/><ellipse cx="30" cy="6" rx="30" ry="18"/>
<ellipse cx="-30" cy="8" rx="26" ry="16"/><ellipse cx="2" cy="-11" rx="24" ry="15"/>
</g>
<g class="cloud" data-x="380" data-y="215" data-f="0.05" data-s="0.55" fill="#fff" opacity=".9">
<ellipse cx="0" cy="0" rx="40" ry="22"/><ellipse cx="30" cy="6" rx="30" ry="18"/>
<ellipse cx="-30" cy="8" rx="26" ry="16"/><ellipse cx="2" cy="-11" rx="24" ry="15"/>
</g>

<!-- 远山(视差滚动) -->
<g id="hillBack">
<path d="M 0 470 Q 150 370 300 420 T 600 410 L 600 470 Z" fill="#a9d9a4" transform="translate(0 0)"/>
<path d="M 0 470 Q 150 370 300 420 T 600 410 L 600 470 Z" fill="#a9d9a4" transform="translate(600 0)"/>
</g>
<g id="hillFront">
<path d="M 0 470 Q 120 410 260 452 T 600 448 L 600 470 Z" fill="#86c67f" transform="translate(0 0)"/>
<path d="M 0 470 Q 120 410 260 452 T 600 448 L 600 470 Z" fill="#86c67f" transform="translate(600 0)"/>
</g>

<!-- 草地 -->
<rect x="0" y="470" width="1200" height="150" fill="#79c15e"/>

<!-- 栅栏(横杆静止,立柱滚动) -->
<line x1="0" y1="480" x2="1200" y2="480" stroke="#9c6b3f" stroke-width="3.5"/>
<line x1="0" y1="502" x2="1200" y2="502" stroke="#9c6b3f" stroke-width="3.5"/>
<g id="posts">
<g transform="translate(0 470)"><rect x="-2.5" y="0" width="5" height="46" fill="#8d5a33" stroke="#6f4423" stroke-width="1"/><rect x="-5" y="-4" width="10" height="6" rx="2" fill="#7a4c2a"/></g>
<g transform="translate(160 470)"><rect x="-2.5" y="0" width="5" height="46" fill="#8d5a33" stroke="#6f4423" stroke-width="1"/><rect x="-5" y="-4" width="10" height="6" rx="2" fill="#7a4c2a"/></g>
<g transform="translate(320 470)"><rect x="-2.5" y="0" width="5" height="46" fill="#8d5a33" stroke="#6f4423" stroke-width="1"/><rect x="-5" y="-4" width="10" height="6" rx="2" fill="#7a4c2a"/></g>
<g transform="translate(480 470)"><rect x="-2.5" y="0" width="5" height="46" fill="#8d5a33" stroke="#6f4423" stroke-width="1"/><rect x="-5" y="-4" width="10" height="6" rx="2" fill="#7a4c2a"/></g>
<g transform="translate(640 470)"><rect x="-2.5" y="0" width="5" height="46" fill="#8d5a33" stroke="#6f4423" stroke-width="1"/><rect x="-5" y="-4" width="10" height="6" rx="2" fill="#7a4c2a"/></g>
<g transform="translate(800 470)"><rect x="-2.5" y="0" width="5" height="46" fill="#8d5a33" stroke="#6f4423" stroke-width="1"/><rect x="-5" y="-4" width="10" height="6" rx="2" fill="#7a4c2a"/></g>
<g transform="translate(960 470)"><rect x="-2.5" y="0" width="5" height="46" fill="#8d5a33" stroke="#6f4423" stroke-width="1"/><rect x="-5" y="-4" width="10" height="6" rx="2" fill="#7a4c2a"/></g>
<g transform="translate(1120 470)"><rect x="-2.5" y="0" width="5" height="46" fill="#8d5a33" stroke="#6f4423" stroke-width="1"/><rect x="-5" y="-4" width="10" height="6" rx="2" fill="#7a4c2a"/></g>
<g transform="translate(1280 470)"><rect x="-2.5" y="0" width="5" height="46" fill="#8d5a33" stroke="#6f4423" stroke-width="1"/><rect x="-5" y="-4" width="10" height="6" rx="2" fill="#7a4c2a"/></g>
</g>

<!-- 路旁草丛(与道路同速滚动) -->
<g id="tufts" transform="translate(0 513)">
<g transform="translate(0 0)"><path d="M 0 0 C -4 -10 -2 -16 2 -22 C 4 -14 3 -6 5 0 Z" fill="#3f8f35"/><path d="M 8 0 C 5 -8 8 -14 12 -18 C 12 -10 11 -4 14 0 Z" fill="#4c9e3e"/></g>
<g transform="translate(250 0)"><path d="M 0 0 C -4 -10 -2 -16 2 -22 C 4 -14 3 -6 5 0 Z" fill="#3f8f35"/><path d="M 8 0 C 5 -8 8 -14 12 -18 C 12 -10 11 -4 14 0 Z" fill="#4c9e3e"/></g>
<g transform="translate(500 0)"><path d="M 0 0 C -4 -10 -2 -16 2 -22 C 4 -14 3 -6 5 0 Z" fill="#3f8f35"/><path d="M 8 0 C 5 -8 8 -14 12 -18 C 12 -10 11 -4 14 0 Z" fill="#4c9e3e"/></g>
<g transform="translate(750 0)"><path d="M 0 0 C -4 -10 -2 -16 2 -22 C 4 -14 3 -6 5 0 Z" fill="#3f8f35"/><path d="M 8 0 C 5 -8 8 -14 12 -18 C 12 -10 11 -4 14 0 Z" fill="#4c9e3e"/></g>
<g transform="translate(1000 0)"><path d="M 0 0 C -4 -10 -2 -16 2 -22 C 4 -14 3 -6 5 0 Z" fill="#3f8f35"/><path d="M 8 0 C 5 -8 8 -14 12 -18 C 12 -10 11 -4 14 0 Z" fill="#4c9e3e"/></g>
<g transform="translate(1250 0)"><path d="M 0 0 C -4 -10 -2 -16 2 -22 C 4 -14 3 -6 5 0 Z" fill="#3f8f35"/><path d="M 8 0 C 5 -8 8 -14 12 -18 C 12 -10 11 -4 14 0 Z" fill="#4c9e3e"/></g>
</g>

<!-- 马路 -->
<rect x="0" y="515" width="1200" height="105" fill="#5a6372"/>
<line x1="0" y1="516" x2="1200" y2="516" stroke="#ffffff" stroke-width="2" opacity=".35"/>
<line x1="0" y1="614" x2="1200" y2="614" stroke="#ffffff" stroke-width="2" opacity=".25"/>
<line id="dashLine" x1="0" y1="567" x2="1200" y2="567" stroke="#f1f3f5" stroke-width="5" stroke-dasharray="42 48"/>

<!-- 后轮扬起的尘土 -->
<g class="puff" opacity="0"><ellipse rx="15" ry="9" fill="#e5e7eb"/></g>
<g class="puff" opacity="0"><ellipse rx="15" ry="9" fill="#e5e7eb"/></g>
<g class="puff" opacity="0"><ellipse rx="15" ry="9" fill="#e5e7eb"/></g>
<g class="puff" opacity="0"><ellipse rx="15" ry="9" fill="#e5e7eb"/></g>
<g class="puff" opacity="0"><ellipse rx="15" ry="9" fill="#e5e7eb"/></g>
<g class="puff" opacity="0"><ellipse rx="15" ry="9" fill="#e5e7eb"/></g>
<g class="puff" opacity="0"><ellipse rx="15" ry="9" fill="#e5e7eb"/></g>
<g class="puff" opacity="0"><ellipse rx="15" ry="9" fill="#e5e7eb"/></g>

<!-- 影子 -->
<ellipse cx="575" cy="514" rx="180" ry="12" fill="#26323d" opacity=".2"/>

<!-- 远侧腿(在车身后) -->
<g id="farLegG">
<polyline id="farLeg" fill="none" stroke="#e8621c" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
<g id="farFoot">
<ellipse rx="9" ry="5" fill="#e8621c" stroke="#c74f10" stroke-width="2"/>
<path d="M 2 -1 L 12 -6 M 3 0 L 13 0 M 2 1 L 12 6" stroke="#c74f10" stroke-width="2" fill="none" stroke-linecap="round"/>
</g>
</g>

<!-- 骑行者整体(随蹬踏轻微摇摆) -->
<g id="riderG">
<!-- ================= 自行车 ================= -->
<!-- 后轮 -->
<g id="rearWheel">
<circle r="60" fill="#f4f7f9" stroke="#313a47" stroke-width="9"/>
<circle r="50" fill="none" stroke="#b6c0cc" stroke-width="3"/>
<use href="#spoke"/><use href="#spoke" transform="rotate(45)"/>
<use href="#spoke" transform="rotate(90)"/><use href="#spoke" transform="rotate(135)"/>
<use href="#spoke" transform="rotate(180)"/><use href="#spoke" transform="rotate(225)"/>
<use href="#spoke" transform="rotate(270)"/><use href="#spoke" transform="rotate(315)"/>
<circle r="9" fill="#313a47"/>
<circle cy="-50" r="2.5" fill="#e11d48"/>
</g>
<!-- 前轮 -->
<g id="frontWheel">
<circle r="60" fill="#f4f7f9" stroke="#313a47" stroke-width="9"/>
<circle r="50" fill="none" stroke="#b6c0cc" stroke-width="3"/>
<use href="#spoke"/><use href="#spoke" transform="rotate(45)"/>
<use href="#spoke" transform="rotate(90)"/><use href="#spoke" transform="rotate(135)"/>
<use href="#spoke" transform="rotate(180)"/><use href="#spoke" transform="rotate(225)"/>
<use href="#spoke" transform="rotate(270)"/><use href="#spoke" transform="rotate(315)"/>
<circle r="6" fill="#313a47"/>
<circle cy="-50" r="2.5" fill="#e11d48"/>
</g>
<!-- 车架 -->
<g stroke="#37474f" stroke-linecap="round">
<line x1="548" y1="316" x2="560" y2="440" stroke-width="7"/> <!-- 座管 -->
<line x1="558" y1="440" x2="688" y2="330" stroke-width="7"/> <!-- 下管 -->
<line x1="548" y1="314" x2="692" y2="330" stroke-width="7"/> <!-- 上管 -->
<line x1="544" y1="318" x2="460" y2="455" stroke-width="6"/> <!-- 后上叉 -->
<line x1="556" y1="440" x2="460" y2="455" stroke-width="6"/> <!-- 后下叉 -->
<line x1="692" y1="330" x2="700" y2="362" stroke-width="7"/> <!-- 头管 -->
<path d="M 698 360 C 703 396 702 420 700 455" stroke-width="6" fill="none"/> <!-- 前叉 -->
</g>
<!-- 链条 -->
<line x1="557.6" y1="424.2" x2="458.7" y2="446.1" stroke="#2b323c" stroke-width="2.5"/>
<line x1="562.4" y1="455.8" x2="461.3" y2="463.9" stroke="#2b323c" stroke-width="2.5"/>
<!-- 曲柄与脚踏 -->
<g id="crankG" transform="translate(560 440)">
<circle r="16" fill="#2f3b46" stroke="#232c34" stroke-width="2"/>
<line x1="0" y1="0" x2="28" y2="0" stroke="#2f3b46" stroke-width="6" stroke-linecap="round"/>
<line x1="0" y1="0" x2="-28" y2="0" stroke="#2f3b46" stroke-width="6" stroke-linecap="round"/>
<g transform="translate(28 0)"><g id="pedalN"><rect x="-13" y="-7" width="26" height="14" rx="4" fill="#2f3b46" stroke="#232c34" stroke-width="1.5"/></g></g>
<g transform="translate(-28 0)"><g id="pedalF"><rect x="-13" y="-7" width="26" height="14" rx="4" fill="#2f3b46" stroke="#232c34" stroke-width="1.5"/></g></g>
</g>
<!-- 车把 -->
<g stroke="#37474f" stroke-linecap="round">
<line x1="695" y1="332" x2="712" y2="297" stroke-width="6"/>
<line x1="704" y1="300" x2="716" y2="297" stroke-width="7"/>
</g>
<line x1="716" y1="297" x2="728" y2="297" stroke="#2f3b46" stroke-width="8" stroke-linecap="round"/>
<!-- 车座 -->
<g transform="rotate(-8 549 310)">
<rect x="528" y="304" width="42" height="13" rx="6" fill="#7c4a2d" stroke="#5f3820" stroke-width="2"/>
</g>
<!-- 车篮 + 鱼 -->
<g id="basket">
<line x1="705" y1="298" x2="704" y2="306" stroke="#8d5a33" stroke-width="2"/>
<line x1="716" y1="296" x2="717" y2="306" stroke="#8d5a33" stroke-width="2"/>
<rect x="698" y="306" width="32" height="25" rx="4" fill="#c98b52" stroke="#8d5a33" stroke-width="2"/>
<line x1="698" y1="314" x2="730" y2="314" stroke="#8d5a33" stroke-width="1.5"/>
<line x1="698" y1="322" x2="730" y2="322" stroke="#8d5a33" stroke-width="1.5"/>
<ellipse cx="710" cy="307" rx="7" ry="4.5" fill="#7fa6c4" stroke="#5d84a3" stroke-width="1.5"/>
<g id="fishTail">
<path d="M 711 306 C 714 298 717 292 724 285 C 723 293 721 300 714 306 Z" fill="#6d93b8" stroke="#4c6e8e" stroke-width="1.5"/>
</g>
</g>

<!-- ================= 鹈鹕 ================= -->
<g id="bodyG">
<!-- 远侧翅膀(伸向车把) -->
<g id="wingFar">
<path d="M 500 302 C 585 262 655 276 700 305" fill="none" stroke="#d7e1e8" stroke-width="24" stroke-linecap="round"/>
</g>
<!-- 尾巴 -->
<g id="tailG">
<path d="M 452 297 Q 420 288 388 301 Q 408 294 424 297 Q 428 306 420 314 Q 440 314 452 307 Z"
fill="#ffffff" stroke="#c9d7de" stroke-width="2.5"/>
</g>
<!-- 身体 -->
<g transform="rotate(-12 505 300)">
<ellipse cx="505" cy="300" rx="78" ry="46" fill="#ffffff" stroke="#c9d7de" stroke-width="2.5"/>
<ellipse cx="512" cy="318" rx="62" ry="32" fill="#edf3f6" clip-path="url(#bodyClip)"/>
</g>
<!-- 脖子 -->
<path d="M 542 274 C 576 252 600 218 636 206" fill="none" stroke="#c9d7de" stroke-width="38" stroke-linecap="round"/>
<path d="M 542 274 C 576 252 600 218 636 206" fill="none" stroke="#ffffff" stroke-width="30" stroke-linecap="round"/>
<!-- 头 -->
<g id="headG">
<circle cx="646" cy="199" r="27" fill="#ffffff" stroke="#c9d7de" stroke-width="2.5"/>
<g id="eyeG">
<circle cx="658" cy="192" r="6.5" fill="#ffffff" stroke="#94a3b8" stroke-width="1"/>
<circle cx="660" cy="192" r="3" fill="#1f2937"/>
<circle cx="661" cy="191" r="1.2" fill="#ffffff"/>
</g>
<path d="M 651 184 Q 658 181 666 185" stroke="#475569" stroke-width="2.5" fill="none" stroke-linecap="round"/>
<!-- 头盔 -->
<path d="M 622 186 A 26 26 0 0 1 670 186 Z" fill="#2563eb" stroke="#1d4ed8" stroke-width="2"/>
<rect x="618" y="184" width="56" height="4.5" rx="2.5" fill="#1e40af"/>
<circle cx="646" cy="175" r="3.5" fill="#fde047"/>
<path d="M 632 196 L 638 215" stroke="#1e40af" stroke-width="2"/>
<!-- 上喙 -->
<path d="M 650 193 C 700 186 748 198 772 211 C 745 203 705 205 658 214 Z"
fill="#f6a21d" stroke="#d97706" stroke-width="2.5" stroke-linejoin="round"/>
<!-- 下喙 + 喉囊(张嘴动画) -->
<g id="beakLower">
<path d="M 658 213 L 745 210 Q 720 246 658 213 Z"
fill="#ffc14d" stroke="#d97706" stroke-width="2.5" stroke-linejoin="round"/>
</g>
</g>
<!-- 红围巾(随风飘动) -->
<g id="scarfG">
<path id="scarfPath" d="M 588 232 L 568 236 L 548 236 L 528 234 L 508 234 L 488 238 L 468 242 L 448 242 L 428 240 L 428 243 L 448 245 L 468 247 L 488 246 L 508 243 L 528 242 L 548 245 L 568 249 L 588 248 Z"
fill="#ef4444" stroke="#dc2626" stroke-width="1.5" stroke-linejoin="round"/>
<circle cx="588" cy="234" r="8" fill="#dc2626"/>
</g>
<!-- 近侧翅膀(扶着车把,随节奏起伏) -->
<g id="wingNear">
<path d="M 498 288 C 600 256 660 268 712 296" fill="none" stroke="#c9d7de" stroke-width="29" stroke-linecap="round"/>
<path d="M 498 288 C 600 256 660 268 712 296" fill="none" stroke="#ffffff" stroke-width="24" stroke-linecap="round"/>
<ellipse cx="710" cy="297" rx="11" ry="4.5" transform="rotate(-6 710 297)" fill="#ffffff" stroke="#c9d7de" stroke-width="1.5"/>
<ellipse cx="719" cy="298" rx="9" ry="4" transform="rotate(-3 719 298)" fill="#ffffff" stroke="#c9d7de" stroke-width="1.5"/>
<ellipse cx="704" cy="293" rx="8" ry="3.5" transform="rotate(-10 704 293)" fill="#ffffff" stroke="#c9d7de" stroke-width="1.5"/>
</g>
</g>
</g>

<!-- 近侧腿(在车身上方) -->
<g id="nearLegG">
<polyline id="nearLeg" fill="none" stroke="#fb8a3c" stroke-width="11" stroke-linecap="round" stroke-linejoin="round"/>
<g id="nearFoot">
<ellipse rx="9" ry="5" fill="#fb8a3c" stroke="#e8621c" stroke-width="2"/>
<path d="M 2 -1 L 12 -6 M 3 0 L 13 0 M 2 1 L 12 6" stroke="#e8621c" stroke-width="2" fill="none" stroke-linecap="round"/>
</g>
</g>
</svg>

<div class="controls">
<button id="btnPause" class="btn" aria-label="暂停或播放">⏸ 暂停</button>
<label class="speed">
<span>速度</span>
<input id="speed" type="range" min="0.2" max="3" step="0.1" value="1">
<span id="speedVal" class="val">1.0×</span>
</label>
<button id="btnSquawk" class="btn accent" aria-label="让鹈鹕叫一声">🐟 呱!</button>
</div>
<p class="hint">空格键:暂停 / 播放 &nbsp;·&nbsp; ← → 键:调整速度 &nbsp;·&nbsp; 鹈鹕的后轮会扬起尘土,围巾随风飘扬</p>
</div>

<script>
(() => {
"use strict";
const $ = id => document.getElementById(id);
const RAD = a => a * Math.PI / 180;
const CRANK = { x: 560, y: 440 }, R = 28; // 曲柄中心 / 曲柄长度
const HIP_N = { x: 516, y: 332 }, HIP_F = { x: 503, y: 330 };
const L1 = 70, L2 = 66; // 大腿 / 小腿长度
const LEAN_C = { x: 580, y: 505 }; // 车身摇摆支点(地面接触点)

const riderG = $("riderG"), bodyG = $("bodyG");
const rearWheel = $("rearWheel"), frontWheel = $("frontWheel");
const crankG = $("crankG"), pedalN = $("pedalN"), pedalF = $("pedalF");
const farLeg = $("farLeg"), nearLeg = $("nearLeg");
const farFoot = $("farFoot"), nearFoot = $("nearFoot");
const headG = $("headG"), eyeG = $("eyeG"), beakLower = $("beakLower");
const wingNear = $("wingNear"), wingFar = $("wingFar"), tailG = $("tailG");
const fishTail = $("fishTail"), scarfPath = $("scarfPath");
const dashLine = $("dashLine");
const hillBack = $("hillBack"), hillFront = $("hillFront");
const tufts = $("tufts"), posts = $("posts");
const btnPause = $("btnPause"), btnSquawk = $("btnSquawk");
const speedInput = $("speed"), speedVal = $("speedVal");

const clouds = [...document.querySelectorAll(".cloud")].map(g => ({
g, x: +g.dataset.x, y: +g.dataset.y, f: +g.dataset.f, s: +g.dataset.s
}));
const puffs = [...document.querySelectorAll(".puff")].map(g => ({ g, life: 0 }));

const S = {
t: 0, theta: 0, wheel: 0, v: 0, speedMul: 1, paused: false,
open: 0, openState: "closed", openT: 0, closedDur: 2.2, openDur: 0.6,
forcedOpen: 0, blinkT: 0, blinkTimer: 2.8,
dash: 0, offBack: 0, offFront: 0, offTuft: 0, offPost: 0, dustAcc: 0
};

/* 绕 LEAN_C 旋转一点,用于把车架上的点换算成屏幕坐标 */
function rot(px, py, deg) {
const a = RAD(deg), dx = px - LEAN_C.x, dy = py - LEAN_C.y;
return {
x: LEAN_C.x + dx * Math.cos(a) - dy * Math.sin(a),
y: LEAN_C.y + dx * Math.sin(a) + dy * Math.cos(a)
};
}

/* 两段式腿的 IK:已知髋 H、脚 F,求膝盖 K(取较高的那个解) */
function ik(H, F) {
const dx = F.x - H.x, dy = F.y - H.y;
let d = Math.hypot(dx, dy) || 0.0001;
const maxL = L1 + L2;
if (d > maxL * 0.999) d = maxL * 0.999;
const a = (L1 * L1 - L2 * L2 + d * d) / (2 * d);
const h = Math.sqrt(Math.max(L1 * L1 - a * a, 0));
const ux = dx / d, uy = dy / d;
const mx = H.x + a * ux, my = H.y + a * uy;
const k1 = { x: mx - uy * h, y: my + ux * h };
const k2 = { x: mx + uy * h, y: my - ux * h };
return k1.y <= k2.y ? k1 : k2;
}

function setPoly(el, A, B, C) {
el.setAttribute("points", `${A.x.toFixed(1)},${A.y.toFixed(1)} ${B.x.toFixed(1)},${B.y.toFixed(1)} ${C.x.toFixed(1)},${C.y.toFixed(1)}`);
}

/* 围巾:正弦波浪,幅度随风速变化 */
function scarfD(t, v) {
const ax = 588, ay = 232, n = 8, seg = 20;
const amp = 3 + Math.min(v, 900) * 0.009;
const pts = [];
for (let i = 0; i <= n; i++) {
pts.push([ax - i * seg, ay + Math.sin(t * 9 + i * 1.25) * amp + i * 1.2]);
}
let d = `M ${ax} ${ay}`;
for (let i = 1; i <= n; i++) d += ` L ${pts[i][0].toFixed(1)} ${pts[i][1].toFixed(1)}`;
for (let i = n; i >= 0; i--) d += ` L ${pts[i][0].toFixed(1)} ${(pts[i][1] + 16 - i * 1.6).toFixed(1)}`;
return d + " Z";
}

function spawnPuff(lean) {
const p = puffs.find(q => q.life <= 0);
if (!p) return;
const o = rot(448, 514, lean);
p.x = o.x + (Math.random() * 12 - 6);
p.y = 514 + (Math.random() * 6 - 3);
p.life = p.max = 0.85;
}

function togglePause() {
S.paused = !S.paused;
btnPause.textContent = S.paused ? "▶ 播放" : "⏸ 暂停";
}

let ac = null;
function squawk() {
try {
ac = ac || new (window.AudioContext || window.webkitAudioContext)();
const t0 = ac.currentTime;
const o = ac.createOscillator(), g = ac.createGain();
o.type = "sawtooth";
o.frequency.setValueAtTime(660, t0);
o.frequency.exponentialRampToValueAtTime(200, t0 + 0.22);
g.gain.setValueAtTime(0.0001, t0);
g.gain.exponentialRampToValueAtTime(0.22, t0 + 0.03);
g.gain.exponentialRampToValueAtTime(0.001, t0 + 0.26);
o.connect(g); g.connect(ac.destination);
o.start(t0); o.stop(t0 + 0.3);
} catch (e) { /* 无音频环境时静默失败 */ }
S.forcedOpen = 0.55;
if (S.paused) togglePause();
}

function setSpeed(mul) {
S.speedMul = Math.min(3, Math.max(0.2, Math.round(mul * 10) / 10));
speedInput.value = S.speedMul;
speedVal.textContent = S.speedMul.toFixed(1) + "×";
}

btnPause.addEventListener("click", togglePause);
btnSquawk.addEventListener("click", squawk);
speedInput.addEventListener("input", () => {
S.speedMul = +speedInput.value;
speedVal.textContent = S.speedMul.toFixed(1) + "×";
});
window.addEventListener("keydown", e => {
if (e.code === "Space") { e.preventDefault(); togglePause(); }
else if (e.key === "ArrowRight" || e.key === "ArrowUp") { setSpeed(S.speedMul + 0.2); }
else if (e.key === "ArrowLeft" || e.key === "ArrowDown") { setSpeed(S.speedMul - 0.2); }
});
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
S.paused = true; btnPause.textContent = "▶ 播放";
}

let last = performance.now();
function frame(now) {
requestAnimationFrame(frame);
const dt = Math.min((now - last) / 1000, 0.05);
last = now;
if (S.paused) return;

/* 速度平滑过渡,起步/变速不突兀 */
const target = 360 * S.speedMul;
S.v += (target - S.v) * Math.min(1, dt * 4);
S.t += dt;
const w = S.v / 60; // 车轮 / 曲柄角速度
S.theta += w * dt;
S.wheel += w * dt;

/* 车身随蹬踏轻微摇摆 */
const lean = Math.sin(S.theta) * 1.2 + Math.sin(S.t * 0.8) * 0.5;
riderG.setAttribute("transform", `rotate(${lean.toFixed(2)} ${LEAN_C.x} ${LEAN_C.y})`);

/* 车轮滚动 */
const wheelDeg = (S.wheel * 180 / Math.PI) % 360;
rearWheel.setAttribute("transform", `translate(460 455) rotate(${wheelDeg.toFixed(2)})`);
frontWheel.setAttribute("transform", `translate(700 455) rotate(${wheelDeg.toFixed(2)})`);

/* 曲柄转动,脚踏反向保持水平 */
const crankDeg = (S.theta * 180 / Math.PI) % 360;
crankG.setAttribute("transform", `translate(560 440) rotate(${crankDeg.toFixed(2)})`);
pedalN.setAttribute("transform", `rotate(${-crankDeg.toFixed(2)})`);
pedalF.setAttribute("transform", `rotate(${-crankDeg.toFixed(2)})`);

/* 身体起伏 */
const bobX = Math.sin(2 * S.theta) * 1.4;
const bobY = -Math.abs(Math.sin(2 * S.theta)) * 2.2;
bodyG.setAttribute("transform", `translate(${bobX.toFixed(2)} ${bobY.toFixed(2)})`);

/* 腿:髋部在车架坐标系里,换算到屏幕坐标后求 IK */
const p1 = rot(CRANK.x + R * Math.cos(S.theta), CRANK.y + R * Math.sin(S.theta), lean);
const p2 = rot(CRANK.x - R * Math.cos(S.theta), CRANK.y - R * Math.sin(S.theta), lean);
const hN = rot(HIP_N.x + bobX, HIP_N.y + bobY, lean);
const hF = rot(HIP_F.x + bobX, HIP_F.y + bobY, lean);
setPoly(nearLeg, hN, ik(hN, p1), p1);
setPoly(farLeg, hF, ik(hF, p2), p2);
nearFoot.setAttribute("transform", `translate(${p1.x.toFixed(1)} ${p1.y.toFixed(1)})`);
farFoot.setAttribute("transform", `translate(${p2.x.toFixed(1)} ${p2.y.toFixed(1)})`);

/* 翅膀扇动、尾巴、头、鱼尾 */
const flap = Math.sin(2 * S.theta + 0.7) * 4 + Math.sin(S.t * 2.3) * 1.5;
wingNear.setAttribute("transform", `rotate(${flap.toFixed(2)} 498 288)`);
wingFar.setAttribute("transform", `rotate(${(-flap * 0.7).toFixed(2)} 500 302)`);
tailG.setAttribute("transform", `rotate(${(4 + Math.sin(S.t * 1.6 + 1) * 3 + Math.min(S.v / 900, 1) * 12).toFixed(2)} 445 306)`);
fishTail.setAttribute("transform", `rotate(${(4 + Math.sin(S.t * 6) * 13).toFixed(2)} 714 306)`);

/* 张嘴周期 */
S.openT += dt;
const openTarget = S.openState === "open" ? 1 : 0;
if (S.openT > (S.openState === "closed" ? S.closedDur : S.openDur)) {
S.openState = S.openState === "closed" ? "open" : "closed";
S.openT = 0;
if (S.openState === "closed") S.closedDur = 1.8 + Math.random() * 1.6;
}
if (openTarget > S.open) S.open = Math.min(1, S.open + dt * 7);
else S.open = Math.max(0, S.open - dt * 5);
if (S.forcedOpen > 0) {
S.forcedOpen -= dt;
S.open = Math.max(S.open, Math.min(1, S.forcedOpen / 0.2));
}
const mouth = S.open;
beakLower.setAttribute("transform",
`translate(658 213) scale(1 ${(1 + mouth * 0.12).toFixed(3)}) rotate(${(mouth * 15).toFixed(2)}) translate(-658 -213)`);
headG.setAttribute("transform", `rotate(${(-8 + Math.sin(S.t * 2.1) * 2.5 + mouth * 5).toFixed(2)} 646 199)`);

/* 眨眼 */
S.blinkTimer -= dt;
if (S.blinkTimer <= 0) { S.blinkT = 0.13; S.blinkTimer = 2.2 + Math.random() * 2.2; }
let blink = 0;
if (S.blinkT > 0) {
S.blinkT -= dt;
blink = Math.sin(Math.PI * Math.min(1, Math.max(0, S.blinkT / 0.13)));
}
eyeG.setAttribute("transform", `translate(658 192) scale(1 ${(1 - blink * 0.85).toFixed(3)}) translate(-658 -192)`);

/* 围巾 */
scarfPath.setAttribute("d", scarfD(S.t, S.v));

/* 路面虚线、远山、草丛、栅栏(视差滚动) */
S.dash = (S.dash + S.v * dt) % 90;
dashLine.setAttribute("stroke-dashoffset", (-S.dash).toFixed(1));
S.offBack = (S.offBack + S.v * 0.10 * dt) % 600;
hillBack.setAttribute("transform", `translate(${-S.offBack.toFixed(1)} 0)`);
S.offFront = (S.offFront + S.v * 0.22 * dt) % 600;
hillFront.setAttribute("transform", `translate(${-S.offFront.toFixed(1)} 0)`);
S.offTuft = (S.offTuft + S.v * dt) % 250;
tufts.setAttribute("transform", `translate(${-S.offTuft.toFixed(1)} 513)`);
S.offPost = (S.offPost + S.v * dt) % 160;
posts.setAttribute("transform", `translate(${-S.offPost.toFixed(1)} 0)`);

/* 云 */
for (const c of clouds) {
c.x -= S.v * c.f * dt;
if (c.x < -260 * c.s) c.x = 1200 + 40 + Math.random() * 160;
c.g.setAttribute("transform", `translate(${c.x.toFixed(1)} ${c.y}) scale(${c.s})`);
}

/* 尘土 */
if (S.v > 40) {
S.dustAcc += dt * (0.6 + S.v / 240);
while (S.dustAcc > 0.25) { S.dustAcc -= 0.25; spawnPuff(lean); }
}
for (const p of puffs) {
if (p.life <= 0) { p.g.setAttribute("opacity", "0"); continue; }
p.life -= dt;
const k = p.life / p.max;
p.x -= S.v * 0.45 * dt;
p.y -= 16 * dt;
p.g.setAttribute("opacity", (0.4 * (1 - (1 - k) * (1 - k))).toFixed(3));
p.g.setAttribute("transform", `translate(${p.x.toFixed(1)} ${p.y.toFixed(1)}) scale(${(0.5 + (1 - k) * 1.3).toFixed(2)})`);
}
}
requestAnimationFrame(frame);
})();
</script>
</body>
</html>



提示词:

  1. 做一个html网页,内容是鹈鹕骑自行车的SVG动画。

  2. 拿这个改个纯svg的版本 保存为一个.svg文件


效果还可以 不过也就内样 两个任务各跑二十多分钟 烧掉token7.3M/166k 花费1.41余额

最新回复 (2)
  • OctoberSama 楼主 08-13 23:08
    1

    @ksw_one 腿其实是正常的 不过不知道为什么svg没动起来 大概这一帧重叠了 或者是渲染问题?

    可以打开html看下实际效果

  • Zany 08-13 23:12
    2



    这是我用dsh接v4pro生成的max强度用时8分14秒

* 帖子来源Linux.do
返回