glm5.3的雷霆大思考
合本丶
2026-09-17 09:14
1
创建一个HTML,内容是SVG绘制一个鹈鹕骑自行车的2D动画
思考了足足9分45秒,展开长度夸张


效果展示:

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>鹈鹕骑自行车</title>
<style>
/* ========== 设计令牌:日间(默认完整定义) ========== */
:root{
color-scheme: light;
--page-bg:#DFEEF5;
--chip-bg:rgba(255,255,255,.62); --chip-tx:#33526B;
--bar-bg:rgba(255,255,255,.72); --bar-bd:rgba(38,66,88,.16); --bar-tx:#33526B;
--accent:#2FA3A0;
--sky-0:#A9D6EE; --sky-1:#C9E8F4; --sky-2:#EAF6FA;
--sun:#FFD666; --sun-halo:#FFE49A;
--stars:0; --day-sun:1; --dusk-sun:0;
--cloud:#FFFFFF;
--bird:#5F7488;
--hills-far:#BCD9C2; --hills-near:#9CC79E;
--grass:#8FC47E; --trunk:#8A6A50; --crown-a:#79B565; --crown-b:#63A457;
--road:#7B8794; --road-edge:#E9EEF3; --road-dash:#F4C64D;
--tire:#39404A; --rim:#D7DEE6; --spoke:#B9C3CE; --hub:#556070;
--frame:#E05B4B; --chain:#57616C; --saddle:#333A44; --metal:#39404A; --bell:#E8C063;
--body:#FCF6E7; --wing:#EFE1C3; --shade:#E2CEA3; --line:#D9C69B;
--bill:#F5A83B; --pouch:#EF8A2B;
--leg:#F09A2E; --leg-far:#C77A1E;
--eye:#2A2E33;
--helmet:#2FA3A0; --helmet-dk:#20777A; --strap:#20777A;
--speed:#FFFFFF; --puff:#EAF2F7; --dust-op:.5;
--shadow:#1B2B3A; --shadow-op:.13;
--bubble:#FFFFFF; --bubble-tx:#33526B;
}
/* ========== 黄昏主题(系统暗色 且 未显式选亮色) ========== */
@media (prefers-color-scheme: dark){
:root:not([data-theme="light"]){
color-scheme: dark;
--page-bg:#131B2E;
--chip-bg:rgba(20,30,52,.55); --chip-tx:#C6D2E4;
--bar-bg:rgba(24,34,58,.72); --bar-bd:rgba(160,185,230,.18); --bar-tx:#C6D2E4;
--accent:#4FC3BF;
--sky-0:#26325C; --sky-1:#7A4E7E; --sky-2:#E8937C;
--sun:#FF9E64; --sun-halo:#FFC49B;
--stars:.95; --day-sun:0; --dusk-sun:1;
--cloud:#7E8CBD;
--bird:#AFBBD6;
--hills-far:#47527E; --hills-near:#3A4868;
--grass:#3E5B49; --trunk:#57453A; --crown-a:#3F5D46; --crown-b:#35503C;
--road:#565E6B; --road-edge:#B9C2D0; --road-dash:#E0B75C;
--tire:#262B33; --rim:#8E99A8; --spoke:#8A94A4; --hub:#46505E;
--frame:#C64B3E; --chain:#4A525D; --saddle:#2A3038; --metal:#2A3038; --bell:#D9B054;
--body:#EFE3CB; --wing:#DFCFAA; --shade:#CDB98E; --line:#C0AC82;
--bill:#E89A38; --pouch:#DD8226;
--leg:#DE8C31; --leg-far:#A9671C;
--eye:#22262B;
--helmet:#23777B; --helmet-dk:#1A5C60; --strap:#1A5C60;
--speed:#FFD9C2; --puff:#B9C6DE; --dust-op:.4;
--shadow:#0B1220; --shadow-op:.28;
--bubble:#FFD9C2; --bubble-tx:#4A3221;
}
}
/* 黄昏主题(显式选择暗色) */
:root[data-theme="dark"]{
color-scheme: dark;
--page-bg:#131B2E;
--chip-bg:rgba(20,30,52,.55); --chip-tx:#C6D2E4;
--bar-bg:rgba(24,34,58,.72); --bar-bd:rgba(160,185,230,.18); --bar-tx:#C6D2E4;
--accent:#4FC3BF;
--sky-0:#26325C; --sky-1:#7A4E7E; --sky-2:#E8937C;
--sun:#FF9E64; --sun-halo:#FFC49B;
--stars:.95; --day-sun:0; --dusk-sun:1;
--cloud:#7E8CBD;
--bird:#AFBBD6;
--hills-far:#47527E; --hills-near:#3A4868;
--grass:#3E5B49; --trunk:#57453A; --crown-a:#3F5D46; --crown-b:#35503C;
--road:#565E6B; --road-edge:#B9C2D0; --road-dash:#E0B75C;
--tire:#262B33; --rim:#8E99A8; --spoke:#8A94A4; --hub:#46505E;
--frame:#C64B3E; --chain:#4A525D; --saddle:#2A3038; --metal:#2A3038; --bell:#D9B054;
--body:#EFE3CB; --wing:#DFCFAA; --shade:#CDB98E; --line:#C0AC82;
--bill:#E89A38; --pouch:#DD8226;
--leg:#DE8C31; --leg-far:#A9671C;
--eye:#22262B;
--helmet:#23777B; --helmet-dk:#1A5C60; --strap:#1A5C60;
--speed:#FFD9C2; --puff:#B9C6DE; --dust-op:.4;
--shadow:#0B1220; --shadow-op:.28;
--bubble:#FFD9C2; --bubble-tx:#4A3221;
}
/* ========== 页面布局 ========== */
*{box-sizing:border-box}
html,body{margin:0}
body{
min-height:100vh; display:flex; align-items:center; justify-content:center;
background:var(--page-bg); color:var(--bar-tx);
font-family:system-ui,-apple-system,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;
padding:20px 16px;
}
[hidden]{display:none!important}
.wrap{width:min(960px,100%)}
.stage{position:relative; border-radius:20px; overflow:hidden;
box-shadow:0 22px 50px -24px rgba(10,30,55,.5)}
.stage svg{display:block; width:100%; height:auto}
.chip{
position:absolute; top:14px; right:14px;
padding:6px 14px; border-radius:999px;
background:var(--chip-bg); color:var(--chip-tx);
font-size:13px; letter-spacing:.5px;
backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
}
.bar{
display:flex; align-items:center; gap:12px; flex-wrap:wrap;
margin-top:14px; padding:10px 14px; border-radius:16px;
background:var(--bar-bg); border:1px solid var(--bar-bd);
backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
font-size:13px;
}
.ib{
width:36px; height:36px; border-radius:50%;
border:1px solid var(--bar-bd); background:transparent; color:var(--bar-tx);
display:grid; place-items:center; cursor:pointer; flex:none;
}
.ib:hover{background:rgba(127,140,160,.16)}
.ib:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
.speed{display:flex; align-items:center; gap:8px}
.speed input{width:130px; accent-color:var(--accent)}
#speedVal{min-width:44px; font-variant-numeric:tabular-nums}
.hint{margin-left:auto; opacity:.72}
@media (max-width:560px){ .hint{display:none} }
/* ========== SVG 场景着色(经类引用令牌,var() 不能用于表现属性) ========== */
#scene *{transition:fill .9s ease, stroke .9s ease}
.sky0{stop-color:var(--sky-0)} .sky1{stop-color:var(--sky-1)} .sky2{stop-color:var(--sky-2)}
#sunDay{opacity:var(--day-sun); transition:opacity .9s}
#sunDusk{opacity:var(--dusk-sun); transition:opacity .9s}
#stars{opacity:var(--stars); transition:opacity .9s}
.c-sun{fill:var(--sun)} .c-halo{fill:var(--sun-halo)}
.c-star{fill:#F3F6FF}
.c-cloud{fill:var(--cloud); opacity:.92}
.s-bird{stroke:var(--bird)}
.c-hfar{fill:var(--hills-far)} .c-hnear{fill:var(--hills-near)}
.c-grass{fill:var(--grass)}
.c-trunk{fill:var(--trunk)} .c-crownA{fill:var(--crown-a)} .c-crownB{fill:var(--crown-b)}
.c-road{fill:var(--road)}
.s-edge{stroke:var(--road-edge)} .s-dash{stroke:var(--road-dash)}
.s-tire{stroke:var(--tire)} .s-rim{stroke:var(--rim)} .s-spoke{stroke:var(--spoke)} .c-hub{fill:var(--hub)}
.s-frame{stroke:var(--frame)} .s-stay{stroke:var(--frame)}
.s-chain{stroke:var(--chain)} .s-ring{stroke:var(--chain)}
.c-saddle{fill:var(--saddle)} .s-metal{stroke:var(--metal)} .c-bell{fill:var(--bell)}
.c-body{fill:var(--body)} .s-body{stroke:var(--body)}
.c-wing{fill:var(--wing)} .s-line{stroke:var(--line)} .c-shade{fill:var(--shade); opacity:.55}
.c-bill{fill:var(--bill)} .c-pouch{fill:var(--pouch)}
.s-legF{stroke:var(--leg-far)} .c-legF{fill:var(--leg-far)}
.s-legN{stroke:var(--leg)} .c-legN{fill:var(--leg)}
.c-eye{fill:var(--eye)} .c-white{fill:#FFFFFF}
.c-helmet{fill:var(--helmet)} .s-helmetdk{stroke:var(--helmet-dk)} .s-strap{stroke:var(--strap)}
.s-speed{stroke:var(--speed)}
.c-puff{fill:var(--puff)}
.c-shadow{fill:var(--shadow); opacity:var(--shadow-op)}
.c-bubble{fill:var(--bubble)} .c-bubbletx{fill:var(--bubble-tx)}
#raysDay{animation:spin 90s linear infinite; transform-box:fill-box; transform-origin:center}
@keyframes spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion: reduce){ #raysDay{animation:none} }
#pokeHit{cursor:pointer}
</style>
<main class="wrap" id="app">
<div class="stage">
<div class="chip">鹈鹕骑行 · Pelican Ride</div>
<svg id="scene" viewBox="0 0 960 600" role="img" aria-label="一只戴头盔的鹈鹕骑着自行车的 2D 动画">
<defs>
<linearGradient id="skyG" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" class="sky0"/>
<stop offset=".55" class="sky1"/>
<stop offset="1" class="sky2"/>
</linearGradient>
<!-- 云朵 -->
<g id="cloud">
<ellipse cx="0" cy="0" rx="48" ry="20"/>
<circle cx="-24" cy="-10" r="17"/>
<circle cx="6" cy="-16" r="22"/>
<circle cx="28" cy="-6" r="15"/>
</g>
<!-- 行道树 -->
<g id="tree1">
<rect x="-4" y="-30" width="8" height="32" rx="3" class="c-trunk"/>
<circle cx="0" cy="-42" r="17" class="c-crownA"/>
<circle cx="-12" cy="-32" r="12" class="c-crownB"/>
<circle cx="12" cy="-32" r="12" class="c-crownA"/>
</g>
<g id="tree2">
<rect x="-4" y="-44" width="8" height="46" rx="3" class="c-trunk"/>
<circle cx="0" cy="-54" r="13" class="c-crownA"/>
<circle cx="-11" cy="-42" r="15" class="c-crownB"/>
<circle cx="11" cy="-42" r="14" class="c-crownA"/>
<circle cx="0" cy="-30" r="13" class="c-crownB"/>
</g>
<g id="bush">
<circle cx="-9" cy="-8" r="10" class="c-crownB"/>
<circle cx="0" cy="-13" r="12" class="c-crownA"/>
<circle cx="10" cy="-8" r="10" class="c-crownB"/>
</g>
</defs>
<!-- 天空 -->
<rect x="0" y="0" width="960" height="600" fill="url(#skyG)"/>
<!-- 日间太阳 -->
<g id="sunDay" transform="translate(150 112)">
<circle r="56" class="c-halo" opacity=".5"/>
<g id="raysDay">
<line y1="-46" y2="-62" class="s-line" style="stroke:var(--sun)" stroke-width="5" stroke-linecap="round"/>
<line y1="-46" y2="-62" transform="rotate(45)" style="stroke:var(--sun)" stroke-width="5" stroke-linecap="round"/>
<line y1="-46" y2="-62" transform="rotate(90)" style="stroke:var(--sun)" stroke-width="5" stroke-linecap="round"/>
<line y1="-46" y2="-62" transform="rotate(135)" style="stroke:var(--sun)" stroke-width="5" stroke-linecap="round"/>
<line y1="-46" y2="-62" transform="rotate(180)" style="stroke:var(--sun)" stroke-width="5" stroke-linecap="round"/>
<line y1="-46" y2="-62" transform="rotate(225)" style="stroke:var(--sun)" stroke-width="5" stroke-linecap="round"/>
<line y1="-46" y2="-62" transform="rotate(270)" style="stroke:var(--sun)" stroke-width="5" stroke-linecap="round"/>
<line y1="-46" y2="-62" transform="rotate(315)" style="stroke:var(--sun)" stroke-width="5" stroke-linecap="round"/>
</g>
<circle r="36" class="c-sun"/>
</g>
<!-- 黄昏落日 -->
<g id="sunDusk" transform="translate(742 318)">
<circle r="84" class="c-halo" opacity=".32"/>
<circle r="44" class="c-sun"/>
</g>
<!-- 星星(黄昏可见) -->
<g id="stars">
<circle cx="80" cy="60" r="1.6" class="c-star"/><circle cx="210" cy="40" r="1.2" class="c-star"/>
<circle cx="330" cy="90" r="1.8" class="c-star"/><circle cx="470" cy="50" r="1.3" class="c-star"/>
<circle cx="560" cy="120" r="1.1" class="c-star"/><circle cx="700" cy="60" r="1.7" class="c-star"/>
<circle cx="830" cy="100" r="1.3" class="c-star"/><circle cx="900" cy="40" r="1.5" class="c-star"/>
<circle cx="160" cy="150" r="1.2" class="c-star"/><circle cx="400" cy="160" r="1.1" class="c-star"/>
<circle cx="620" cy="170" r="1.4" class="c-star"/><circle cx="880" cy="160" r="1.2" class="c-star"/>
</g>
<!-- 远处的候鸟(JS 生成并扇翅) -->
<g id="birdsG" fill="none" stroke-width="2.5" stroke-linecap="round"></g>
<!-- 云(JS 生成实例) -->
<g id="cloudsG"></g>
<!-- 远山 / 近丘(无缝平铺滚动) -->
<g id="hillsFarW">
<path id="hfarTile" class="c-hfar" d="M0,420 C160,368 320,468 480,418 C640,368 800,468 960,420 L960,472 L0,472 Z"/>
<use href="#hfarTile" x="960"/>
</g>
<g id="hillsNearW">
<path id="hnearTile" class="c-hnear" d="M0,478 C160,438 320,502 480,478 C640,456 800,500 960,478 L960,512 L0,512 Z"/>
<use href="#hnearTile" x="960"/>
</g>
<!-- 草地与行道树 -->
<rect x="0" y="500" width="960" height="100" class="c-grass"/>
<g id="roadsideW">
<g id="roadTile"></g>
<use href="#roadTile" x="960"/>
</g>
<!-- 公路 -->
<rect x="0" y="528" width="960" height="56" class="c-road"/>
<line x1="0" y1="531.5" x2="960" y2="531.5" class="s-edge" stroke-width="3" opacity=".9"/>
<line x1="0" y1="580.5" x2="960" y2="580.5" class="s-edge" stroke-width="3" opacity=".9"/>
<line id="laneDash" x1="0" y1="538" x2="960" y2="538" class="s-dash" stroke-width="6" stroke-dasharray="46 40"/>
<!-- 速度线(JS 控制闪烁) -->
<g stroke-linecap="round" stroke-width="5">
<line class="sl s-speed" x1="95" y1="300" x2="165" y2="300" opacity="0"/>
<line class="sl s-speed" x1="55" y1="385" x2="140" y2="385" opacity="0"/>
<line class="sl s-speed" x1="150" y1="245" x2="205" y2="245" opacity="0"/>
<line class="sl s-speed" x1="80" y1="450" x2="150" y2="450" opacity="0"/>
</g>
<!-- 扬尘(JS 循环) -->
<g id="puffG"></g>
<!-- 车与鹈鹕 -->
<ellipse cx="505" cy="573" rx="212" ry="9" class="c-shadow"/>
<g id="bikeG">
<!-- 车轮 -->
<g id="wheelRear">
<circle cx="350" cy="488" r="78" class="s-tire" fill="none" stroke-width="13"/>
<circle cx="350" cy="488" r="66" class="s-rim" fill="none" stroke-width="3.5"/>
<g id="spokesR"></g>
<circle cx="350" cy="488" r="7" class="c-hub"/>
</g>
<g id="wheelFront">
<circle cx="640" cy="488" r="78" class="s-tire" fill="none" stroke-width="13"/>
<circle cx="640" cy="488" r="66" class="s-rim" fill="none" stroke-width="3.5"/>
<g id="spokesF"></g>
<circle cx="640" cy="488" r="7" class="c-hub"/>
</g>
<!-- 远侧:曲柄、踏板、腿、脚(画在车架后) -->
<line id="crankF" x1="475" y1="500" x2="511" y2="500" stroke="#23272D" stroke-width="7" stroke-linecap="round"/>
<polyline id="legF" class="s-legF" fill="none" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" points="420,344 490,430 511,527"/>
<g id="pedalF"><rect x="-14" y="-4" width="28" height="8" rx="3" fill="#20242A"/><rect x="-14" y="-4" width="28" height="3" rx="1.5" fill="#3A404A"/></g>
<path id="footF" class="c-legF" d="M-5,-7 L17,-3 Q21,-1 17,3 L12,6 Q4,9 -3,5 Q-9,1 -5,-7 Z"/>
<!-- 车架 -->
<path class="s-frame" fill="none" stroke-width="9" stroke-linecap="round"
d="M475,500 L409,366 M413,376 L598,372 M475,500 L604,380 M600,364 L610,396"/>
<path class="s-stay" fill="none" stroke-width="6.5" stroke-linecap="round"
d="M409,372 L350,488 M475,500 L350,488 M608,392 L640,488"/>
<!-- 链条与牙盘 -->
<path id="chainP" class="s-chain" fill="none" stroke-width="4" stroke-dasharray="7 5"
d="M475,478 L350,479 M475,522 Q412,527 350,497"/>
<circle cx="475" cy="500" r="22" class="s-ring" fill="none" stroke-width="3" stroke-dasharray="4 3"/>
<circle cx="475" cy="500" r="7" class="c-hub"/>
<circle cx="350" cy="488" r="9" class="s-ring" fill="none" stroke-width="2.5" stroke-dasharray="3 2"/>
<!-- 车座 -->
<path class="c-saddle" d="M386,356 Q408,346 430,356 Q428,366 408,364 Q388,366 386,356 Z"/>
<!-- 车把、车铃 -->
<line x1="601" y1="367" x2="590" y2="349" class="s-metal" stroke-width="7" stroke-linecap="round"/>
<path d="M592,350 Q572,338 556,346" class="s-metal" fill="none" stroke-width="7" stroke-linecap="round"/>
<line x1="562" y1="344" x2="554" y2="347" class="s-metal" style="stroke:var(--saddle)" stroke-width="9" stroke-linecap="round"/>
<circle cx="580" cy="341" r="4.5" class="c-bell"/>
<!-- 近侧:曲柄、踏板(画在车架前) -->
<line id="crankN" x1="475" y1="500" x2="511" y2="500" stroke="#2F343B" stroke-width="8" stroke-linecap="round"/>
<g id="pedalN"><rect x="-14" y="-4" width="28" height="8" rx="3" fill="#20242A"/><rect x="-14" y="-4" width="28" height="3" rx="1.5" fill="#3A404A"/></g>
<!-- 近侧腿、脚(藏在身体下缘之下) -->
<polyline id="legN" class="s-legN" fill="none" stroke-width="14" stroke-linecap="round" stroke-linejoin="round" points="420,344 490,430 511,527"/>
<path id="footN" class="c-legN" d="M-5,-7 L17,-3 Q21,-1 17,3 L12,6 Q4,9 -3,5 Q-9,1 -5,-7 Z"/>
<!-- 鹈鹕身体(随蹬踏起伏) -->
<g id="bodyG">
<g id="tailG">
<path class="c-wing" d="M372,296 C346,290 328,278 314,282 C326,294 342,300 366,303 Z"/>
<path class="c-shade" d="M372,298 C350,296 334,290 322,288 C332,300 348,306 368,307 Z"/>
<path class="c-wing" d="M372,300 C350,300 336,306 324,318 C340,318 356,314 370,308 Z"/>
</g>
<path class="c-body" d="M368,322 C352,286 368,242 416,230 C468,217 526,240 538,288 C547,324 524,350 476,356 C428,362 384,354 368,322 Z"/>
<path class="c-shade" d="M392,352 C428,362 476,358 512,342 C496,356 448,364 410,358 C402,356 396,354 392,352 Z"/>
<g id="wingG">
<path class="c-wing" d="M448,262 C506,264 534,296 524,330 C498,344 452,340 430,310 C420,286 428,266 448,262 Z"/>
<path class="s-line" fill="none" stroke-width="3" stroke-linecap="round" d="M446,300 Q470,307 492,301 M438,316 Q462,323 488,317"/>
</g>
</g>
<!-- 脖子(JS 逐帧重塑曲线) -->
<path id="neckP" class="s-body" fill="none" stroke-width="30" stroke-linecap="round" d="M506,262 C558,262 614,206 583,158"/>
<!-- 头部 -->
<g id="headG">
<path class="s-line" fill="none" stroke-width="3" stroke-linecap="round" d="M566,128 Q560,116 552,114 M570,126 Q568,112 562,106"/>
<circle cx="585" cy="150" r="26" class="c-body"/>
<line x1="562" y1="133" x2="577" y2="167" class="s-strap" stroke-width="2"/>
<line x1="608" y1="133" x2="597" y2="170" class="s-strap" stroke-width="2"/>
<path class="c-helmet" d="M559,131 C559,104 611,104 611,131 Q585,140 559,131 Z"/>
<path class="s-helmetdk" fill="none" stroke-width="5" stroke-linecap="round" d="M558,131 Q585,142 612,131"/>
<path class="s-helmetdk" fill="none" stroke-width="3" stroke-linecap="round" d="M572,112 Q576,108 580,110 M592,111 Q597,107 601,109"/>
<g id="billUpG">
<path class="c-bill" d="M601,137 Q655,134 691,154 Q686,161 675,162 L602,165 Z"/>
</g>
<path id="pouchP" class="c-pouch" d="M602,165 Q648,171 675,162 C667,185 640,197 614,187 Q604,178 602,165 Z"/>
<circle cx="594" cy="141" r="5" class="c-eye"/>
<circle cx="596" cy="139" r="1.7" class="c-white"/>
<g id="eyeLidG" transform="translate(594 141) scale(0)"><circle r="6.2" class="c-body"/></g>
</g>
<!-- 叫声气泡 -->
<g id="bubbleG" opacity="0" pointer-events="none">
<circle r="25" class="c-bubble"/>
<path d="M-6,22 L2,35 L9,20 Z" class="c-bubble"/>
<text y="6" text-anchor="middle" class="c-bubbletx" font-size="17" font-weight="700">嘎!</text>
</g>
</g>
<!-- 点击热区(戳鹈鹕) -->
<rect id="pokeHit" x="340" y="100" width="390" height="280" fill="transparent" pointer-events="all"/>
</svg>
</div>
<div class="bar">
<button id="btnPlay" class="ib" aria-label="暂停" title="播放 / 暂停(空格)">
<svg id="icoPause" viewBox="0 0 24 24" width="15" height="15"><path d="M7 5h3.4v14H7zM13.6 5H17v14h-3.6z" fill="currentColor"/></svg>
<svg id="icoPlay" viewBox="0 0 24 24" width="15" height="15" hidden><path d="M8 5l11 7-11 7z" fill="currentColor"/></svg>
</button>
<button id="btnTheme" class="ib" aria-label="切换日间 / 黄昏主题" title="日间 / 黄昏">
<svg id="icoMoon" viewBox="0 0 24 24" width="15" height="15"><path d="M20 13.2A8.5 8.5 0 0 1 10.8 4 8.5 8.5 0 1 0 20 13.2z" fill="currentColor"/></svg>
<svg id="icoSun" viewBox="0 0 24 24" width="15" height="15" hidden><circle cx="12" cy="12" r="4.4" fill="currentColor"/><path d="M12 2.5v3M12 18.5v3M2.5 12h3M18.5 12h3M5 5l2.1 2.1M16.9 16.9L19 19M19 5l-2.1 2.1M7.1 16.9L5 19" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
<label class="speed">速度
<input id="speed" type="range" min="0.25" max="2" step="0.05" value="1">
<span id="speedVal">1×</span>
</label>
<span class="hint">戳一下鹈鹕试试</span>
</div>
</main>
<script>
(() => {
'use strict';
const $ = id => document.getElementById(id);
const TAU = Math.PI * 2;
const NS = 'http://www.w3.org/2000/svg';
/* ================= 动画参数 ================= */
const CADENCE = 0.62; // 蹬踏频率(圈/秒)
const GEAR = 2.6; // 传动比(牙盘:飞轮)
const R_WHEEL = 78; // 车轮半径(px)
const V = TAU * CADENCE * GEAR * R_WHEEL; // 地面线速度(px/s)
const CHAIN_V = TAU * CADENCE * 9; // 链条线速度(飞轮半径 9px)
const BB = { x: 475, y: 500 }; // 五通(曲柄轴心)
const CRANK = 36; // 曲柄长
const HIP = { x: 420, y: 344 }; // 髋关节锚点(随身体起伏)
const L1 = 108, L2 = 108; // 大腿 / 小腿长
const WR = { x: 350, y: 488 }, WF = { x: 640, y: 488 }; // 车轮中心
/* ================= 引用 ================= */
const spokesR = $('spokesR'), spokesF = $('spokesF');
const hillsFarW = $('hillsFarW'), hillsNearW = $('hillsNearW'), roadsideW = $('roadsideW');
const laneDash = $('laneDash'), chainP = $('chainP');
const cloudsG = $('cloudsG'), birdsG = $('birdsG'), puffG = $('puffG');
const bikeG = $('bikeG'), bodyG = $('bodyG'), wingG = $('wingG'), tailG = $('tailG');
const neckP = $('neckP'), headG = $('headG'), billUpG = $('billUpG'), pouchP = $('pouchP');
const eyeLidG = $('eyeLidG'), bubbleG = $('bubbleG');
const crankF = $('crankF'), crankN = $('crankN'), pedalF = $('pedalF'), pedalN = $('pedalN');
const legF = $('legF'), legN = $('legN'), footF = $('footF'), footN = $('footN');
/* ================= 场景生成 ================= */
// 车轮辐条 + 气门嘴
function buildSpokes(g, c) {
for (let i = 0; i < 12; i++) {
const a = i * Math.PI / 6, l = document.createElementNS(NS, 'line');
l.setAttribute('x1', c.x + 8 * Math.cos(a)); l.setAttribute('y1', c.y + 8 * Math.sin(a));
l.setAttribute('x2', c.x + 62 * Math.cos(a)); l.setAttribute('y2', c.y + 62 * Math.sin(a));
l.setAttribute('class', 's-spoke'); l.setAttribute('stroke-width', '2.5');
g.appendChild(l);
}
const v = document.createElementNS(NS, 'line'); // 气门芯,让转动更可读
v.setAttribute('x1', c.x + 54); v.setAttribute('y1', c.y + 8);
v.setAttribute('x2', c.x + 64); v.setAttribute('y2', c.y + 9.5);
v.setAttribute('class', 's-rim'); v.setAttribute('stroke-width', '3');
g.appendChild(v);
}
buildSpokes(spokesR, WR); buildSpokes(spokesF, WF);
// 云
const CLOUDS = [{x:820,y:75,s:1.1,v:7},{x:240,y:122,s:.8,v:10.5},{x:560,y:58,s:1.25,v:5.5},{x:60,y:162,s:.65,v:13}];
CLOUDS.forEach(c => {
const u = document.createElementNS(NS, 'use');
u.setAttribute('href', '#cloud'); u.setAttribute('class', 'c-cloud');
cloudsG.appendChild(u); c.el = u;
});
// 远处候鸟
const BIRDS = [{x:180,y:88},{x:262,y:64},{x:118,y:140}];
BIRDS.forEach((b, i) => {
const p = document.createElementNS(NS, 'path');
p.setAttribute('class', 's-bird'); b.el = p; b.ph = i * 1.9; birdsG.appendChild(p);
});
// 行道树(一块 960 平铺瓦)
const TREES = [[40,'bush',.8],[130,'tree1',.95],[235,'tree2',1.15],[360,'bush',1],
[455,'tree1',1.3],[575,'tree2',.9],[700,'bush',.85],[815,'tree1',1.05],[905,'bush',.7]];
TREES.forEach(([x, id, s]) => {
const u = document.createElementNS(NS, 'use');
u.setAttribute('href', '#' + id);
u.setAttribute('transform', `translate(${x} 524) scale(${s})`);
$('roadTile').appendChild(u);
});
// 扬尘
const PUFFS = [];
for (let i = 0; i < 5; i++) {
const c = document.createElementNS(NS, 'circle');
c.setAttribute('class', 'c-puff'); c.setAttribute('r', '0'); c.setAttribute('opacity', '0');
puffG.appendChild(c); PUFFS.push(c);
}
const SL = Array.from(document.querySelectorAll('.sl'));
/* ================= 状态 ================= */
const S = { playing: true, speed: 1, t: 0 };
let nextBlink = 1.6, blinkStart = -9, honkStart = -1e9;
/* ================= 腿部逆向运动学 ================= */
function legIK(hx, hy, fx, fy) {
const dx = fx - hx, dy = fy - hy;
const d = Math.hypot(dx, dy) || 1e-6;
const dc = Math.min(d, L1 + L2 - .01);
const a = (L1 * L1 - L2 * L2 + dc * dc) / (2 * dc);
const h = Math.sqrt(Math.max(L1 * L1 - a * a, 0));
const ux = dx / d, uy = dy / d;
const px = hx + a * ux, py = hy + a * uy;
const k1x = px - h * uy, k1y = py + h * ux;
const k2x = px + h * uy, k2y = py - h * ux;
return k2x > k1x ? [k2x, k2y] : [k1x, k1y]; // 取膝盖朝前的解
}
/* ================= 逐帧更新 ================= */
function update() {
const t = S.t;
const th = TAU * CADENCE * t; // 曲柄角
const phi = th * GEAR; // 车轮角
const phiDeg = phi * 180 / Math.PI;
const off = V * t; // 地面滚动距离
// —— 场景滚动 ——
spokesR.setAttribute('transform', `rotate(${phiDeg} ${WR.x} ${WR.y})`);
spokesF.setAttribute('transform', `rotate(${phiDeg} ${WF.x} ${WF.y})`);
hillsFarW.setAttribute('transform', `translate(${-(off * .12 % 960)} 0)`);
hillsNearW.setAttribute('transform', `translate(${-(off * .3 % 960)} 0)`);
roadsideW.setAttribute('transform', `translate(${-(off % 960)} 0)`);
laneDash.setAttribute('stroke-dashoffset', -(off % 86));
chainP.setAttribute('stroke-dashoffset', -(CHAIN_V * t % 12));
CLOUDS.forEach(c => {
const x = ((c.x - c.v * t) % 1240 + 1240) % 1240 - 140;
c.el.setAttribute('transform', `translate(${x} ${c.y}) scale(${c.s})`);
});
BIRDS.forEach((b, i) => {
const x = ((b.x - (15 + i * 4) * t) % 1150 + 1150) % 1150 - 100;
const f = .5 + .5 * Math.sin(TAU * 2.2 * t + b.ph);
b.el.setAttribute('d', `M${x - 10},${b.y} Q${x - 5},${b.y - 7 * f} ${x},${b.y} Q${x + 5},${b.y - 7 * f} ${x + 10},${b.y}`);
});
// —— 鹈鹕起伏 ——
const bob = 4.5 * Math.sin(2 * th);
const headBob = 3 * Math.sin(2 * th - .7);
bikeG.setAttribute('transform', `translate(0 ${(.9 * Math.sin(phi)).toFixed(2)})`);
bodyG.setAttribute('transform', `translate(0 ${bob.toFixed(2)})`);
wingG.setAttribute('transform', `rotate(${(3 * Math.sin(2 * th - .9)).toFixed(2)} 452 272)`);
tailG.setAttribute('transform', `rotate(${(5 * Math.sin(2 * th - 1.5)).toFixed(2)} 372 296)`);
neckP.setAttribute('d',
`M506,${(262 + bob).toFixed(1)} C558,${(262 + 5 * Math.sin(2 * th - .4) + bob).toFixed(1)} ` +
`614,${(206 + 4.5 * Math.sin(2 * th - .9) + headBob).toFixed(1)} 583,${(158 + headBob).toFixed(1)}`);
// —— 叫声(按墙上时钟,暂停时也可触发) ——
const hp = (performance.now() - honkStart) / 900;
const honkA = hp >= 0 && hp < 1 ? Math.sin(Math.PI * hp) : 0;
const sag = 3 + 3 * Math.sin(2 * th - 1.2) + 12 * honkA;
pouchP.setAttribute('d',
`M602,165 Q648,171 675,162 C667,${(185 + sag * .6).toFixed(1)} 640,${(197 + sag).toFixed(1)} ` +
`614,187 Q604,178 602,165 Z`);
billUpG.setAttribute('transform', `rotate(${(-15 * Math.pow(honkA, .8)).toFixed(2)} 604 152)`);
headG.setAttribute('transform',
`translate(0 ${headBob.toFixed(2)}) rotate(${(-5 * honkA).toFixed(2)} 575 160)`);
bubbleG.setAttribute('opacity', (honkA * .95).toFixed(2));
bubbleG.setAttribute('transform', `translate(704 ${(118 - 26 * (hp < 0 || hp > 1 ? 0 : hp)).toFixed(1)})`);
// —— 眨眼 ——
if (t > nextBlink) { blinkStart = t; nextBlink = t + 2.4 + Math.random() * 2.2; }
const bp = (t - blinkStart) / .18;
const lid = bp >= 0 && bp < 1 ? Math.sin(Math.PI * bp) : 0;
eyeLidG.setAttribute('transform', `translate(594 141) scale(${lid.toFixed(2)})`);
// —— 腿部 IK:脚随踏板,髋随起伏 ——
const hipY = HIP.y + bob;
[[th + Math.PI, legF, footF, crankF, pedalF, -4, 2],
[th, legN, footN, crankN, pedalN, 0, 0]].forEach(([a, leg, foot, crank, pedal, ox, oy]) => {
const px = BB.x + CRANK * Math.cos(a), py = BB.y + CRANK * Math.sin(a);
const [kx, ky] = legIK(HIP.x + ox, hipY + oy, px + 3, py - 9);
leg.setAttribute('points', `${HIP.x + ox},${(hipY + oy).toFixed(1)} ${kx.toFixed(1)},${ky.toFixed(1)} ${px + 3},${py - 9}`);
crank.setAttribute('x2', px.toFixed(1)); crank.setAttribute('y2', py.toFixed(1));
pedal.setAttribute('transform', `translate(${px.toFixed(1)} ${py.toFixed(1)}) rotate(${(-a * 180 / Math.PI).toFixed(1)})`);
foot.setAttribute('transform', `translate(${px + 3} ${py - 9})`);
});
// —— 速度线与扬尘 ——
SL.forEach((l, i) => {
const o = Math.max(0, Math.sin(t * 6 + i * 1.7));
l.setAttribute('opacity', (0.12 + 0.55 * o * o * S.speed).toFixed(2));
});
PUFFS.forEach((c, i) => {
const p = (t * 1.4 + i * .2) % 1;
c.setAttribute('cx', 310 - 70 * p);
c.setAttribute('cy', 562 - 26 * p);
c.setAttribute('r', (3 + 9 * p).toFixed(1));
c.setAttribute('opacity', ((1 - p) * .45 * S.speed).toFixed(2));
});
}
/* ================= 主循环 ================= */
let last = null;
function loop(ts) {
if (last === null) last = ts;
const dt = Math.min((ts - last) / 1000, .05);
last = ts;
if (S.playing) S.t += dt * S.speed;
update();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
/* ================= 交互 ================= */
const btnPlay = $('btnPlay'), icoPlay = $('icoPlay'), icoPause = $('icoPause');
function setPlaying(v) {
S.playing = v;
icoPlay.hidden = v; icoPause.hidden = !v;
btnPlay.setAttribute('aria-label', v ? '暂停' : '播放');
}
btnPlay.addEventListener('click', () => setPlaying(!S.playing));
document.addEventListener('keydown', e => {
if (e.code === 'Space' && (e.target === document.body || e.target === document.documentElement)) {
e.preventDefault(); setPlaying(!S.playing);
}
});
const btnTheme = $('btnTheme'), icoSun = $('icoSun'), icoMoon = $('icoMoon');
function isDark() {
return document.documentElement.dataset.theme
? document.documentElement.dataset.theme === 'dark'
: matchMedia('(prefers-color-scheme: dark)').matches;
}
function syncThemeIcon() {
const d = isDark();
icoSun.hidden = !d; icoMoon.hidden = d; // 当前暗色 → 显示“切到亮色”的太阳
}
btnTheme.addEventListener('click', () => {
document.documentElement.dataset.theme = isDark() ? 'light' : 'dark';
syncThemeIcon();
});
matchMedia('(prefers-color-scheme: dark)').addEventListener('change', syncThemeIcon);
syncThemeIcon();
const speed = $('speed'), speedVal = $('speedVal');
speed.addEventListener('input', () => {
S.speed = parseFloat(speed.value);
speedVal.textContent = parseFloat(S.speed.toFixed(2)) + '×';
});
$('pokeHit').addEventListener('pointerdown', () => { honkStart = performance.now(); });
// 尊重“减少动态效果”偏好:初始暂停,画面停在完整一帧
if (matchMedia('(prefers-reduced-motion: reduce)').matches) setPlaying(false);
})();
</script>
最新回复 (5)
-
赵火火 09-17 09:151楼也许他真的在思考呢。。 ^-^ ~~
-
xuhow 09-17 09:252楼画的还可以啊,思考的效果是有的。
-
CooperLyu 09-17 09:263楼已进入题库了.z/这个测不出来效果..试试别的
-
星河璀璨 09-17 09:374楼确实,看思考过程,“Hmmm”"wait"看得火大,来回说
-
vickyy 09-17 09:435楼画的还不错的呀,不过这个思考也太久了吧
* 帖子来源Linux.do
附近帖子
飞读
合本丶
|
主题数 1 |
帖子数 1 |
注册排名 3 |