• OctoberSama

    图片

    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&#039; 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>
    ```提示词:

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

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

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

    主楼
  • OctoberSama

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

    可以打开html看下实际效果

    #1
  • Zany

    图片

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

    #2

发表回复

登录后回复