独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>负压吸附楼梯攀爬机器人 — 最终理想解原理动画</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#0d1117;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;font-family:-apple-system,'Segoe UI',sans-serif;color:#c9d1d9;padding:16px}
.hd{font-size:1.35rem;color:#58a6ff;margin-bottom:6px;text-align:center}
.sub{font-size:.85rem;color:#8b949e;margin-bottom:16px;text-align:center;max-width:700px}
.wrap{width:96%;max-width:1140px;background:#161b22;border-radius:12px;border:1px solid #30363d;overflow:hidden}
svg{display:block;width:100%;height:auto}
.ctrls{margin-top:14px;display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center}
button{padding:7px 18px;border:1px solid #30363d;border-radius:6px;background:#21262d;color:#c9d1d9;cursor:pointer;font-size:.82rem;transition:background .2s}
button:hover{background:#30363d}
.spd{display:flex;align-items:center;gap:6px;font-size:.82rem;color:#8b949e}
.spd input{width:90px}
.legend{margin-top:12px;display:flex;gap:20px;flex-wrap:wrap;justify-content:center;font-size:.78rem;color:#8b949e}
.leg-i{display:flex;align-items:center;gap:5px}
.leg-d{width:11px;height:11px;border-radius:50%}
</style>
</head>
<body>
<div class="hd">负压吸附楼梯攀爬机器人 — 最终理想解原理演示</div>
<div class="sub">核心矛盾:传统机械越障结构复杂、故障率高、冲击大 → 理想解:用负压场作为"无形抓手",以极简结构实现法向抓地力</div>
<div class="wrap">
<svg id="scene" viewBox="0 0 1120 680">
<defs>
<filter id="glCyan"><feGaussianBlur stdDeviation="6" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
<filter id="glGreen"><feGaussianBlur stdDeviation="5" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
<filter id="glOrange"><feGaussianBlur stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
<linearGradient id="stG" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#3b4252"/><stop offset="100%" stop-color="#2e3440"/></linearGradient>
<radialGradient id="vacG" cx="50%" cy="40%"><stop offset="0%" stop-color="#00E5FF" stop-opacity=".35"/><stop offset="100%" stop-color="#00E5FF" stop-opacity="0"/></radialGradient>
</defs>
<!-- 背景 -->
<rect width="1120" height="680" fill="#0d1117"/>
<g opacity=".07"><defs><pattern id="gr" width="40" height="40" patternUnits="userSpaceOnUse"><path d="M40 0L0 0 0 40" fill="none" stroke="#58a6ff" stroke-width=".5"/></pattern></defs><rect width="1120" height="680" fill="url(#gr)"/></g>
<!-- 台阶 -->
<g id="stairs">
<path d="M50 560 L390 560 390 450 L670 450 670 340 L950 340 950 230 L1070 230 1070 650 50 650Z" fill="url(#stG)" stroke="#4c566a" stroke-width="1.5"/>
<!-- 踏面高亮 -->
<line x1="50" y1="560" x2="390" y2="560" stroke="#5e6a7a" stroke-width="2"/>
<line x1="390" y1="450" x2="670" y2="450" stroke="#5e6a7a" stroke-width="2"/>
<line x1="670" y1="340" x2="950" y2="340" stroke="#5e6a7a" stroke-width="2"/>
<line x1="950" y1="230" x2="1070" y2="230" stroke="#5e6a7a" stroke-width="2"/>
<!-- 踢面 -->
<line x1="390" y1="560" x2="390" y2="450" stroke="#4c566a" stroke-width="2"/>
<line x1="670" y1="450" x2="670" y2="340" stroke="#4c566a" stroke-width="2"/>
<line x1="950" y1="340" x2="950" y2="230" stroke="#4c566a" stroke-width="2"/>
<!-- 标签 -->
<text x="215" y="582" fill="#5e6a7a" font-size="13" text-anchor="middle" font-family="sans-serif">地面</text>
<text x="525" y="472" fill="#5e6a7a" font-size="13" text-anchor="middle" font-family="sans-serif">台阶 1</text>
<text x="805" y="362" fill="#5e6a7a" font-size="13" text-anchor="middle" font-family="sans-serif">台阶 2</text>
<text x="1005" y="252" fill="#5e6a7a" font-size="13" text-anchor="middle" font-family="sans-serif">台阶 3</text>
<!-- 尺寸标注 -->
<g opacity=".35">
<line x1="28" y1="560" x2="28" y2="450" stroke="#58a6ff" stroke-width="1" stroke-dasharray="4,3"/>
<line x1="24" y1="560" x2="32" y2="560" stroke="#58a6ff" stroke-width="1"/>
<line x1="24" y1="450" x2="32" y2="450" stroke="#58a6ff" stroke-width="1"/>
<text x="22" y="510" fill="#58a6ff" font-size="10" text-anchor="end" transform="rotate(-90,22,510)">110mm</text>
</g>
</g>
<!-- 状态指示栏 -->
<rect x="0" y="0" width="1120" height="38" fill="rgba(13,17,23,.85)"/>
<text id="status" x="560" y="25" fill="#58a6ff" font-size="14" text-anchor="middle" font-family="sans-serif" font-weight="bold">系统就绪 — 准备攀爬</text>
<!-- ========== 机器人 ========== -->
<g id="robot">
<!-- 底部负压场 -->
<g id="bottomVac" opacity="0">
<rect x="-68" y="20" width="136" height="22" rx="5" fill="url(#vacG)" filter="url(#glCyan)"/>
<rect x="-62" y="22" width="124" height="14" rx="3" fill="#00E5FF" opacity=".08"/>
<circle class="vp" cx="-30" cy="30" r="2" fill="#00E5FF" opacity=".6"/>
<circle class="vp" cx="0" cy="32" r="2.5" fill="#00E5FF" opacity=".7"/>
<circle class="vp" cx="30" cy="30" r="2" fill="#00E5FF" opacity=".6"/>
<circle class="vp" cx="-12" cy="36" r="1.5" fill="#00E5FF" opacity=".4"/>
<circle class="vp" cx="18" cy="36" r="1.5" fill="#00E5FF" opacity=".4"/>
</g>
<!-- 裙边 -->
<path id="skirt" d="M-68 18 Q-70 22 -68 26 L68 26 Q70 22 68 18Z" fill="#434c5e" stroke="#3b4252" stroke-width="1"/>
<line x1="-68" y1="26" x2="68" y2="26" stroke="#4c566a" stroke-width="1" stroke-dasharray="3,2"/>
<!-- 车轮 -->
<g id="whR">
<circle cx="-45" cy="24" r="14" fill="#1a1a1a" stroke="#333" stroke-width="2"/>
<circle cx="-45" cy="24" r="6" fill="#252525"/>
<line x1="-51" y1="21" x2="-42" y2="27" stroke="#555" stroke-width="1.5" stroke-linecap="round"/>
<line x1="-42" y1="21" x2="-51" y2="27" stroke="#555" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="-45" cy="24" r="2" fill="#444" class="whDot"/>
</g>
<g id="whF">
<circle cx="45" cy="24" r="14" fill="#1a1a1a" stroke="#333" stroke-width="2"/>
<circle cx="45" cy="24" r="6" fill="#252525"/>
<line x1="39" y1="21" x2="48" y2="27" stroke="#555" stroke-width="1.5" stroke-linecap="round"/>
<line x1="48" y1="21" x2="39" y2="27" stroke="#555" stroke-width="1.5" stroke-linecap="round"/>
<circle cx="45" cy="24" r="2" fill="#444" class="whDot"/>
</g>
<!-- 车体 -->
<rect id="bodyMain" x="-60" y="-16" width="120" height="36" rx="6" fill="#1565C0" stroke="#0D47A1" stroke-width="2"/>
<rect x="-55" y="-14" width="110" height="5" rx="2" fill="#1976D2" opacity=".5"/>
<line x1="-50" y1="-3" x2="50" y2="-3" stroke="#0D47A1" stroke-width=".5" opacity=".5"/>
<rect x="-36" y="8" width="7" height="3" rx="1" fill="#0D47A1"/>
<rect x="-22" y="8" width="7" height="3" rx="1" fill="#0D47A1"/>
<rect x="-8" y="8" width="7" height="3" rx="1" fill="#0D47A1"/>
<!-- 风机 -->
<g id="fanGroup" transform="translate(0,-16)">
<circle cx="0" cy="-10" r="12" fill="#0D47A1" stroke="#0A3D8F" stroke-width="1.5"/>
<g id="fanBlades">
<line x1="-7" y1="-10" x2="7" y2="-10" stroke="#42A5F5" stroke-width="2.5" stroke-linecap="round"/>
<line x1="0" y1="-17" x2="0" y2="-3" stroke="#42A5F5" stroke-width="2.5" stroke-linecap="round"/>
<line x1="-5" y1="-15" x2="5" y2="-5" stroke="#42A5F5" stroke-width="2" stroke-linecap="round"/>
<line x1="5" y1="-15" x2="-5" y2="-5" stroke="#42A5F5" stroke-width="2" stroke-linecap="round"/>
</g>
</g>
<!-- 气流箭头 (真空指示) -->
<g id="airArrows" opacity="0">
<path d="M-15 10 L-15 0 L-18 3" stroke="#00E5FF" stroke-width="1.2" fill="none" opacity=".5"/>
<path d="M0 12 L0 0 L-3 3" stroke="#00E5FF" stroke-width="1.2" fill="none" opacity=".5"/>
<path d="M15 10 L15 0 L12 3" stroke="#00E5FF" stroke-width="1.2" fill="none" opacity=".5"/>
</g>
<!-- 机械足 — 左(主) -->
<g id="legL" transform="translate(50,16)">
<rect x="-4" y="-5" width="8" height="10" rx="2" fill="#E65100" stroke="#BF360C" stroke-width="1"/>
<line id="rodL" x1="0" y1="0" x2="0" y2="0" stroke="#FF9800" stroke-width="5" stroke-linecap="round"/>
<g id="cupL">
<ellipse class="cupGlow" cx="0" cy="0" rx="16" ry="10" fill="#00E676" opacity="0" filter="url(#glGreen)"/>
<ellipse class="sCup" cx="0" cy="0" rx="11" ry="5.5" fill="#BF360C" stroke="#8D2B0B" stroke-width="1.5"/>
<ellipse cx="0" cy="-1.5" rx="7" ry="2.5" fill="#D84315"/>
</g>
</g>
<!-- 机械足 — 右(辅) -->
<g id="legR" transform="translate(38,16)">
<rect x="-3.5" y="-4" width="7" height="9" rx="2" fill="#FF8F00" stroke="#E65100" stroke-width="1"/>
<line id="rodR" x1="0" y1="0" x2="0" y2="0" stroke="#FFB74D" stroke-width="4" stroke-linecap="round"/>
<g id="cupR">
<ellipse class="cupGlow" cx="0" cy="0" rx="14" ry="9" fill="#00E676" opacity="0" filter="url(#glGreen)"/>
<ellipse class="sCup" cx="0" cy="0" rx="9" ry="4.5" fill="#BF360C" stroke="#8D2B0B" stroke-width="1.5"/>
<ellipse cx="0" cy="-1" rx="6" ry="2" fill="#D84315"/>
</g>
</g>
<!-- 负压标注 -->
<g id="annoVac" opacity="0">
<line x1="-68" y1="24" x2="-90" y2="38" stroke="#00E5FF" stroke-width="1" opacity=".6"/>
<rect x="-142" y="30" width="54" height="18" rx="3" fill="rgba(0,229,255,.1)" stroke="#00E5FF" stroke-width=".8"/>
<text x="-115" y="43" fill="#00E5FF" font-size="10" text-anchor="middle" font-family="sans-serif" font-weight="bold">负压场</text>
</g>
<!-- 吸盘标注 -->
<g id="annoSuc" opacity="0">
<line x1="12" y1="-106" x2="28" y2="-120" stroke="#00E676" stroke-width="1" opacity=".6"/>
<rect x="26" y="-130" width="54" height="18" rx="3" fill="rgba(0,230,118,.1)" stroke="#00E676" stroke-width=".8"/>
<text x="53" y="-117" fill="#00E676" font-size="10" text-anchor="middle" font-family="sans-serif" font-weight="bold">吸盘锁定</text>
</g>
<!-- 牵引标注 -->
<g id="annoPull" opacity="0">
<line x1="-60" y1="0" x2="-82" y2="-6" stroke="#FF9800" stroke-width="1" opacity=".6"/>
<rect x="-134" y="-16" width="54" height="18" rx="3" fill="rgba(255,152,0,.1)" stroke="#FF9800" stroke-width=".8"/>
<text x="-107" y="-3" fill="#FF9800" font-size="10" text-anchor="middle" font-family="sans-serif" font-weight="bold">收缩牵引</text>
</g>
<!-- 压力指示 -->
<g id="pressureInd" opacity="0" transform="translate(-70,-30)">
<rect x="0" y="0" width="36" height="14" rx="2" fill="#0D47A1" stroke="#1565C0" stroke-width="1"/>
<text x="18" y="11" fill="#00E5FF" font-size="8" text-anchor="middle" font-family="monospace">-2kPa</text>
</g>
</g>
<!-- 攀爬方向指示 -->
<g id="dirArrow" opacity="0">
<path d="M120 620 Q 250 540 380 460" stroke="#58a6ff" stroke-width="1.5" stroke-dasharray="6,4" fill="none" opacity=".4"/>
<text x="230" y="555" fill="#58a6ff" font-size="11" text-anchor="middle" opacity=".4" font-family="sans-serif">攀爬方向</text>
</g>
</svg>
</div>
<div class="ctrls">
<button id="btnRestart">重新播放</button>
<button id="btnPause">暂停</button>
<button id="btnPlay">播放</button>
<div class="spd">
<span>速度:</span>
<input type="range" id="spdSlider" min="0.2" max="3" step="0.1" value="1">
<span id="spdVal">1.0x</span>
</div>
</div>
<div class="legend">
<div class="leg-i"><div class="leg-d" style="background:#00E5FF"></div><span>底部负压场(法向抓地力)</span></div>
<div class="leg-i"><div class="leg-d" style="background:#00E676"></div><span>吸盘锁定(上方台阶锚点)</span></div>
<div class="leg-i"><div class="leg-d" style="background:#FF9800"></div><span>伸缩机械足(牵引执行)</span></div>
<div class="leg-i"><div class="leg-d" style="background:#1565C0"></div><span>车体 + 离心风机</span></div>
</div>
<script>
/* ===== 常量与位置 ===== */
const STEP_H = 110;
const LEG_EXT = 106; // 机械足伸出量(局部 y 偏移)
const CUP_END = 4; // 牵引结束后杯体局部 y
// 各台阶上机器人中心坐标
const POS = [
{ x: 345, y: 540 }, // 地面
{ x: 625, y: 430 }, // 台阶 1
{ x: 875, y: 320 } // 台阶 2
];
/* ===== 初始状态 ===== */
gsap.set("#robot", { x: POS[0].x, y: POS[0].y });
gsap.set("#cupL", { y: 0 });
gsap.set("#cupR", { y: 0 });
gsap.set("#rodL", { attr: { y2: 0 } });
gsap.set("#rodR", { attr: { y2: 0 } });
/* ===== 主时间轴 ===== */
const master = gsap.timeline({
repeat: -1,
repeatDelay: 1.2,
defaults: { ease: "power2.inOut" }
});
// —— 重置(每次循环开始) ——
master.set("#robot", { x: POS[0].x, y: POS[0].y })
.set("#cupL", { y: 0 })
.set("#cupR", { y: 0 })
.set("#rodL", { attr: { y2: 0 } })
.set("#rodR", { attr: { y2: 0 } })
.set("#bottomVac", { opacity: 0 })
.set(".cupGlow", { opacity: 0 })
.set(".sCup", { fill: "#BF360C", stroke: "#8D2B0B" })
.set("#annoVac", { opacity: 0 })
.set("#annoSuc", { opacity: 0 })
.set("#annoPull", { opacity: 0 })
.set("#pressureInd", { opacity: 0 })
.set("#airArrows", { opacity: 0 })
.set("#dirArrow", { opacity: 0 })
.set("#fanBlades", { rotation: 0 })
// —— 入场 ——
master.from("#robot", { opacity: 0, duration: 0.6 })
.to("#dirArrow", { opacity: 1, duration: 0.4 }, "-=0.2")
.to("#status", { text: "系统启动 — 准备攀爬", duration: 0.01 }, 0);
// —— 攀爬循环 1:地面 → 台阶 1 ——
addClimbCycle(master, 0, "c1");
// —— 攀爬循环 2:台阶 1 → 台阶 2 ——
addClimbCycle(master, 1, "c2");
// —— 结束 ——
master.to("#status", { text: "攀爬完成 — 负压吸附全程稳定", duration: 0.01 })
.to("#robot", { opacity: 0, duration: 0.6 }, "+=1.5")
.to("#dirArrow", { opacity: 0, duration: 0.3 }, "-=0.4");
/* ===== 攀爬循环函数 ===== */
function addClimbCycle(tl, stepIdx, label) {
const from = POS[stepIdx];
const to = POS[stepIdx + 1];
const pullY = from.y - STEP_H; // 纯垂直牵引后的 y
tl.addLabel(label);
// ① 负压吸附启动
tl.to("#bottomVac", { opacity: 1, duration: 0.5 }, label)
.to("#fanBlades", { rotation: "+=360", repeat: -1, duration: 0.12, ease: "none" }, label)
.to("#pressureInd", { opacity: 1, duration: 0.3 }, label + "+=0.2")
.to("#airArrows", { opacity: 1, duration: 0.3 }, label + "+=0.2")
.to("#annoVac", { opacity: 1, duration: 0.3 }, label + "+=0.3")
.to("#status", { text: "① 负压吸附启动 — 裙边密封,风机抽真空", duration: 0.01 }, label + "+=0.1");
// 负压粒子脉动
tl.fromTo(".vp", { scale: .5, opacity: .3 }, { scale: 1.2, opacity: .8, stagger: 0.08, duration: 0.4, repeat: -1, yoyo: true }, label);
// ② 机械足向上探索
tl.to("#cupL", { y: -LEG_EXT, duration: 1.2, ease: "power2.out" }, label + "+=0.9")
.to("#cupR", { y: -(LEG_EXT - 6), duration: 1.1, ease: "power2.out" }, label + "+=0.95")
.to("#rodL", { attr: { y2: -LEG_EXT }, duration: 1.2, ease: "power2.out" }, label + "+=0.9")
.to("#rodR", { attr: { y2: -(LEG_EXT - 6) }, duration: 1.1, ease: "power2.out" }, label + "+=0.95")
.to("#status", { text: "② 机械足伸出 — 探索上一级台阶", duration: 0.01 }, label + "+=0.9");
// ③ 吸盘锁定
tl.to(".sCup", { fill: "#00C853", stroke: "#00E676", duration: 0.3 }, label + "+=2.2")
.to(".cupGlow", { opacity: 0.5, duration: 0.3 }, label + "+=2.2")
.to("#annoVac", { opacity: 0, duration: 0.15 }, label + "+=2.2")
.to("#annoSuc", { opacity: 1, duration: 0.3 }, label + "+=2.3")
.to("#status", { text: "③ 吸盘锁定 — 真空吸盘锚定上方台阶", duration: 0.01 }, label + "+=2.2");
// ④ 收缩牵引(纯垂直上移,杯体不动)
tl.to("#robot", { y: pullY, duration: 1.6 }, label + "+=2.8")
.to("#cupL", { y: CUP_END, duration: 1.6 }, label + "+=2.8")
.to("#cupR", { y: CUP_END - 2, duration: 1.6 }, label + "+=2.8")
.to("#rodL", { attr: { y2: CUP_END }, duration: 1.6 }, label + "+=2.8")
.to("#rodR", { attr: { y2: CUP_END - 2 }, duration: 1.6 }, label + "+=2.8")
.to("#annoSuc", { opacity: 0, duration: 0.15 }, label + "+=3.0")
.to("#annoPull", { opacity: 1, duration: 0.3 }, label + "+=3.0")
.to("#status", { text: "④ 收缩牵引 — 机械足回收,拉动车体上移", duration: 0.01 }, label + "+=2.8");
// ⑤ 释放吸盘 + 收回机械足
tl.to(".sCup", { fill: "#BF360C", stroke: "#8D2B0B", duration: 0.25 }, label + "+=4.6")
.to(".cupGlow", { opacity: 0, duration: 0.25 }, label + "+=4.6")
.to("#cupL", { y: 0, duration: 0.4 }, label + "+=4.9")
.to("#cupR", { y: 0, duration: 0.4 }, label + "+=4.95")
.to("#rodL", { attr: { y2: 0 }, duration: 0.4 }, label + "+=4.9")
.to("#rodR", { attr: { y2: 0 }, duration: 0.4 }, label + "+=4.95")
.to("#annoPull", { opacity: 0, duration: 0.15 }, label + "+=4.6")
.to("#status", { text: "⑤ 释放复位 — 吸盘脱开,机械足收回", duration: 0.01 }, label + "+=4.6");
// ⑥ 全向轮驱动前移
tl.to("#robot", { x: to.x, duration: 0.7, ease: "power1.inOut" }, label + "+=5.4")
.to(".whDot", { rotation: "+=720", svgOrigin: "0 0", duration: 0.7, ease: "none" }, label + "+=5.4")
.to("#status", { text: "⑥ 全向轮驱动 — 横向调整至下级台阶前沿", duration: 0.01 }, label + "+=5.4");
}
/* ===== 控件 ===== */
const btnRestart = document.getElementById("btnRestart");
const btnPause = document.getElementById("btnPause");
const btnPlay = document.getElementById("btnPlay");
const spdSlider = document.getElementById("spdSlider");
const spdVal = document.getElementById("spdVal");
btnRestart.addEventListener("click", () => {
master.restart();
});
btnPause.addEventListener("click", () => {
master.pause();
});
btnPlay.addEventListener("click", () => {
master.play();
});
spdSlider.addEventListener("input", () => {
const v = parseFloat(spdSlider.value);
master.timeScale(v);
spdVal.textContent = v.toFixed(1) + "x";
});
/* ===== 自动播放(重开即播) ===== */
// GSAP timeline 默认自动播放,无需额外触发
</script>
</body>
</html>
动画设计要点说明:
IFR 聚焦:动画直接展示最终理想解——负压场作为"无形抓手"替代复杂机械变形结构。底部青色负压场和吸盘绿色锁定是两大核心视觉焦点,通过发光滤镜与颜色突变引导用户关注创新点。
时序编排:每次攀爬严格遵循 6 步时序——①负压吸附→②机械足伸出→③吸盘锁定→④收缩牵引→⑤释放复位→⑥全向轮前移,全部组织在一个
gsap.timeline()中,用addLabel精确控制同步与错峰。牵引物理:第④步中,杯体在绝对坐标系中保持不动(锁定于上方台阶),车体整体上移的同时杯体局部 y 从 −106 渐变至 +4,保证杯体绝对位置恒定——这正是真实牵引动作的力学等效。
分离垂直/水平运动:牵引阶段只做纯垂直上拉(模拟裙边沿踢面滑移),水平位移由全向轮独立完成——忠实反映"负压吸附 + 轮式辅助"的协同机理。
交互控件:速度滑块(0.2x–3.0x)可实时调整时间轴速率,便于观察快速牵引或慢放细节;暂停/播放/重播按钮支持逐帧研究。
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
