分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh">
<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:#0c1222;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;font-family:-apple-system,'Segoe UI',sans-serif;color:#e2e8f0;padding:16px}
h1{font-size:1.25rem;margin-bottom:4px;color:#f1f5f9;text-align:center;letter-spacing:.5px}
.sub{font-size:.82rem;color:#94a3b8;margin-bottom:10px;text-align:center;max-width:680px;line-height:1.5}
.wrap{width:96vw;max-width:1060px}
svg{width:100%;height:auto;border-radius:10px;background:linear-gradient(180deg,#1e293b 0%,#0f172a 60%)}
.ctrls{margin-top:12px;display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:center}
button{padding:6px 18px;border:none;border-radius:6px;background:#3b82f6;color:#fff;cursor:pointer;font-size:.82rem;transition:background .2s}
button:hover{background:#2563eb}
.plbl{font-size:.9rem;color:#fbbf24;min-width:170px;text-align:center}
.legend{margin-top:8px;display:flex;gap:14px;flex-wrap:wrap;justify-content:center;font-size:.75rem;color:#94a3b8}
.li{display:flex;align-items:center;gap:4px}
.ld{width:11px;height:11px;border-radius:2px}
@keyframes gPulse{0%,100%{opacity:.55}50%{opacity:1}}
.gimbal-pulse{animation:gPulse .6s ease-in-out infinite}
</style>
</head>
<body>
<div class="wrap">
<h1>行星轮越障 + 主动云台补偿 原理动画</h1>
<p class="sub">底层行星轮组几何翻转强行跨越台阶,上层主动云台反向补偿切断颠簸传递路径——移动跨越与保持平衡解耦</p>

<svg id="scene" viewBox="0 0 1000 550">
<defs>
  <radialGradient id="wg" cx="38%" cy="38%"><stop offset="0%" stop-color="#64748b"/><stop offset="100%" stop-color="#334155"/></radialGradient>
  <linearGradient id="cg" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#475569"/><stop offset="100%" stop-color="#334155"/></linearGradient>
  <linearGradient id="pg" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#22c55e"/><stop offset="100%" stop-color="#16a34a"/></linearGradient>
  <linearGradient id="cag" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#fbbf24"/><stop offset="100%" stop-color="#f59e0b"/></linearGradient>
  <pattern id="bk" width="22" height="14" patternUnits="userSpaceOnUse">
    <rect width="22" height="14" fill="#78350f"/>
    <rect width="10" height="6" x="0" y="0" fill="#92400e" rx="1"/>
    <rect width="10" height="6" x="12" y="0" fill="#92400e" rx="1"/>
    <rect width="10" height="6" x="5" y="8" fill="#92400e" rx="1"/>
    <rect width="10" height="6" x="17" y="8" fill="#92400e" rx="1"/>
  </pattern>
  <filter id="gl"><feGaussianBlur stdDeviation="4" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
  <filter id="gl2"><feGaussianBlur stdDeviation="2" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
</defs>

<!-- 地面 -->
<rect x="0" y="420" width="560" height="140" fill="#14532d"/>
<rect x="0" y="418" width="560" height="3" fill="#22c55e" rx="1" opacity=".7"/>
<!-- 地面纹理 -->
<line x1="100" y1="440" x2="130" y2="440" stroke="#166534" stroke-width="1" opacity=".4"/>
<line x1="250" y1="450" x2="290" y2="450" stroke="#166534" stroke-width="1" opacity=".4"/>
<line x1="400" y1="435" x2="420" y2="435" stroke="#166534" stroke-width="1" opacity=".4"/>

<!-- 台阶 -->
<rect x="560" y="340" width="440" height="220" fill="url(#bk)"/>
<rect x="560" y="338" width="440" height="3" fill="#22c55e" rx="1" opacity=".7"/>
<rect x="558" y="340" width="3" height="80" fill="#a16207" rx="1" opacity=".8"/>
<!-- 台阶高度标注 -->
<line x1="548" y1="340" x2="548" y2="420" stroke="#fbbf24" stroke-width="1" stroke-dasharray="3,2" opacity=".5"/>
<line x1="543" y1="340" x2="553" y2="340" stroke="#fbbf24" stroke-width="1" opacity=".5"/>
<line x1="543" y1="420" x2="553" y2="420" stroke="#fbbf24" stroke-width="1" opacity=".5"/>
<text x="540" y="384" fill="#fbbf24" font-size="8" text-anchor="end" opacity=".6" transform="rotate(-90,540,384)">臂长 = 台阶高</text>

<!-- 水平基准线 -->
<line x1="30" y1="270" x2="970" y2="270" stroke="#475569" stroke-width=".5" stroke-dasharray="6,5" opacity=".3"/>
<text x="975" y="273" fill="#475569" font-size="7" opacity=".4">水平基准</text>

<!-- ════════ 车辆 ════════ -->
<g id="vehicle">

  <!-- ── 底盘倾斜组(绕后铰接点旋转) ── -->
  <g transform="translate(-70,0)">
    <g id="chassisTilt">
      <g transform="translate(70,0)">

        <!-- 底盘本体 -->
        <rect x="-92" y="-26" width="184" height="27" rx="5" fill="url(#cg)" stroke="#64748b" stroke-width="1.2"/>
        <line x1="-82" y1="-18" x2="82" y2="-18" stroke="#64748b" stroke-width=".4" opacity=".3"/>
        <line x1="-82" y1="-10" x2="82" y2="-10" stroke="#64748b" stroke-width=".4" opacity=".2"/>
        <!-- 底盘标签 -->
        <text id="chassisLabel" x="0" y="-3" text-anchor="middle" fill="#94a3b8" font-size="8" opacity=".6">底 盘</text>

        <!-- ── 前行星轮组 ── -->
        <g transform="translate(70,0)">
          <g id="frontBracket">
            <circle r="6" fill="#64748b" stroke="#94a3b8" stroke-width="1.2"/>
            <circle r="2.5" fill="#94a3b8"/>
            <!-- 三条臂 -->
            <line x1="0" y1="6" x2="0" y2="68" stroke="#94a3b8" stroke-width="3.2" stroke-linecap="round"/>
            <line x1="-5.2" y1="-3" x2="-60.1" y2="-34.6" stroke="#94a3b8" stroke-width="3.2" stroke-linecap="round"/>
            <line x1="5.2" y1="-3" x2="60.1" y2="-34.6" stroke="#94a3b8" stroke-width="3.2" stroke-linecap="round"/>
            <!-- 三角框架 -->
            <polygon points="0,80 -69.3,-40 69.3,-40" fill="none" stroke="#64748b" stroke-width=".8" opacity=".25"/>
            <!-- 三个小轮 -->
            <circle cx="0" cy="80" r="12" fill="url(#wg)" stroke="#94a3b8" stroke-width="1.2"/>
            <circle cx="0" cy="80" r="3.5" fill="#475569" stroke="#64748b" stroke-width=".5"/>
            <circle cx="-69.3" cy="-40" r="12" fill="url(#wg)" stroke="#94a3b8" stroke-width="1.2"/>
            <circle cx="-69.3" cy="-40" r="3.5" fill="#475569" stroke="#64748b" stroke-width=".5"/>
            <circle cx="69.3" cy="-40" r="12" fill="url(#wg)" stroke="#94a3b8" stroke-width="1.2"/>
            <circle cx="69.3" cy="-40" r="3.5" fill="#475569" stroke="#64748b" stroke-width=".5"/>
          </g>
        </g>

        <!-- ── 后行星轮组 ── -->
        <g transform="translate(-70,0)">
          <g id="rearBracket">
            <circle r="6" fill="#64748b" stroke="#94a3b8" stroke-width="1.2"/>
            <circle r="2.5" fill="#94a3b8"/>
            <line x1="0" y1="6" x2="0" y2="68" stroke="#94a3b8" stroke-width="3.2" stroke-linecap="round"/>
            <line x1="-5.2" y1="-3" x2="-60.1" y2="-34.6" stroke="#94a3b8" stroke-width="3.2" stroke-linecap="round"/>
            <line x1="5.2" y1="-3" x2="60.1" y2="-34.6" stroke="#94a3b8" stroke-width="3.2" stroke-linecap="round"/>
            <polygon points="0,80 -69.3,-40 69.3,-40" fill="none" stroke="#64748b" stroke-width=".8" opacity=".25"/>
            <circle cx="0" cy="80" r="12" fill="url(#wg)" stroke="#94a3b8" stroke-width="1.2"/>
            <circle cx="0" cy="80" r="3.5" fill="#475569" stroke="#64748b" stroke-width=".5"/>
            <circle cx="-69.3" cy="-40" r="12" fill="url(#wg)" stroke="#94a3b8" stroke-width="1.2"/>
            <circle cx="-69.3" cy="-40" r="3.5" fill="#475569" stroke="#64748b" stroke-width=".5"/>
            <circle cx="69.3" cy="-40" r="12" fill="url(#wg)" stroke="#94a3b8" stroke-width="1.2"/>
            <circle cx="69.3" cy="-40" r="3.5" fill="#475569" stroke="#64748b" stroke-width=".5"/>
          </g>
        </g>

        <!-- 云台底座(随底盘倾斜) -->
        <rect id="gimbalBase" x="-38" y="-34" width="76" height="8" rx="2" fill="#dc2626" opacity=".85"/>
        <circle cx="0" cy="-30" r="3.5" fill="#ef4444" stroke="#fca5a5" stroke-width=".8"/>
        <!-- 云台活塞(随底盘倾斜,用于显示与平台的分离) -->
        <rect id="pistonL" x="-28" y="-56" width="5" height="22" rx="2" fill="#b91c1c" opacity=".7"/>
        <rect id="pistonR" x="23" y="-56" width="5" height="22" rx="2" fill="#b91c1c" opacity=".7"/>

      </g>
    </g>
  </g>

  <!-- ── 载货平台组(绕云台铰接点反向补偿旋转) ── -->
  <g transform="translate(0,-38)">
    <g id="platformRotator">
      <g transform="translate(0,38)">

        <!-- 云台顶部连接 -->
        <circle id="gimbalTop" cx="0" cy="-56" r="3.5" fill="#ef4444" stroke="#fca5a5" stroke-width=".8"/>

        <!-- 载货平台 -->
        <rect id="platform" x="-82" y="-70" width="164" height="13" rx="3" fill="url(#pg)" stroke="#4ade80" stroke-width="1"/>
        <!-- 水平仪 -->
        <rect id="lvlOuter" x="-18" y="-68" width="36" height="9" rx="4.5" fill="none" stroke="#bbf7d0" stroke-width=".7" opacity=".6"/>
        <circle id="lvlBubble" cx="0" cy="-63.5" r="3.2" fill="#86efac" opacity=".8"/>

        <!-- 货物箱 -->
        <g id="cargo">
          <rect x="-26" y="-100" width="52" height="30" rx="3" fill="url(#cag)" stroke="#fcd34d" stroke-width="1"/>
          <text x="0" y="-81" text-anchor="middle" fill="#78350f" font-size="10" font-weight="bold">货物</text>
        </g>

      </g>
    </g>
  </g>

  <!-- ── 动态标签(随车辆移动但保持水平) ── -->
  <g id="dynLabels">
    <g id="gimbalLabel" opacity="0">
      <rect x="-48" y="-112" width="96" height="18" rx="4" fill="#dc2626" opacity=".15"/>
      <text x="0" y="-99" text-anchor="middle" fill="#fca5a5" font-size="9.5" font-weight="bold">⚡ 云台补偿中</text>
    </g>
    <g id="bracketLabel" opacity="0">
      <text x="120" y="-10" text-anchor="start" fill="#94a3b8" font-size="8.5" opacity=".7">← 行星轮翻转120°</text>
    </g>
    <g id="tiltArcGroup" opacity="0">
      <path id="tiltArc" d="" fill="none" stroke="#fbbf24" stroke-width="1.5" opacity=".7"/>
      <text id="tiltText" x="0" y="0" fill="#fbbf24" font-size="9" text-anchor="middle" font-weight="bold"></text>
    </g>
  </g>

</g>

<!-- 碰撞闪光 -->
<circle id="impactFlash" r="18" fill="#fbbf24" opacity="0" filter="url(#gl)"/>

<!-- 阶段文字 -->
<text id="phaseText" x="500" y="28" text-anchor="middle" fill="#fbbf24" font-size="14" font-weight="bold" opacity="0"/>

<!-- 右下角说明 -->
<g opacity=".45">
  <text x="975" y="530" text-anchor="end" fill="#94a3b8" font-size="7.5">臂长200mm · 云台响应 &lt;20ms</text>
  <text x="975" y="542" text-anchor="end" fill="#94a3b8" font-size="7">台阶高度 ≤ 臂长 → 几何翻转可越障</text>
</g>

</svg>

<div class="ctrls">
  <button id="btnReplay">重新播放</button>
  <button id="btnPause">暂停 / 继续</button>
  <span class="plbl" id="phaseLabel">准备中…</span>
</div>
<div class="legend">
  <div class="li"><div class="ld" style="background:#475569"></div>底盘</div>
  <div class="li"><div class="ld" style="background:#94a3b8"></div>行星轮组</div>
  <div class="li"><div class="ld" style="background:#dc2626"></div>主动云台</div>
  <div class="li"><div class="ld" style="background:#22c55e"></div>载货平台</div>
  <div class="li"><div class="ld" style="background:#fbbf24"></div>货物</div>
</div>
</div>

<script>
/* ──────────────────────────────────────
   行星轮越障 + 主动云台补偿 原理动画
   ────────────────────────────────────── */

// ─── DOM ───
const vehicle      = document.getElementById("vehicle");
const chassisTilt  = document.getElementById("chassisTilt");
const frontBracket = document.getElementById("frontBracket");
const rearBracket  = document.getElementById("rearBracket");
const platRotator  = document.getElementById("platformRotator");
const cargo        = document.getElementById("cargo");
const phaseText    = document.getElementById("phaseText");
const phaseLabel   = document.getElementById("phaseLabel");
const impactFlash  = document.getElementById("impactFlash");
const gimbalLabel  = document.getElementById("gimbalLabel");
const bracketLabel = document.getElementById("bracketLabel");
const tiltArcGroup = document.getElementById("tiltArcGroup");
const tiltArc      = document.getElementById("tiltArc");
const tiltText     = document.getElementById("tiltText");
const gimbalBase   = document.getElementById("gimbalBase");

// ─── 常量 ───
const GY  = 420;   // 地面 y
const STY = 340;   // 台阶顶 y
const STX = 560;   // 台阶立面 x
const FY  = GY  - 80 - 12; // 328 平地时底盘铰接点 y
const SY  = STY - 80 - 12; // 248 台阶顶时底盘铰接点 y
const TILT = 35;   // 底盘最大倾斜角(≈atan(80/140)≈35°)

// ─── 初始位置 ───
gsap.set(vehicle,      { x: 160, y: FY });
gsap.set(chassisTilt,  { rotation: 0,  transformOrigin: "0px 0px" });
gsap.set(frontBracket, { rotation: 0,  transformOrigin: "0px 0px" });
gsap.set(rearBracket,  { rotation: 0,  transformOrigin: "0px 0px" });
gsap.set(platRotator,  { rotation: 0,  transformOrigin: "0px 0px" });
gsap.set(cargo,        { rotation: 0,  transformOrigin: "0px -85px" });
gsap.set(impactFlash,  { x: STX, y: GY - 35, transformOrigin: "50% 50%" });
gsap.set(phaseText,    { opacity: 0 });

// ─── 辅助:绘制倾斜角弧线 ───
function updateTiltArc(angle) {
  const cx = -70, cy = -30, r = 28;
  const rad = angle * Math.PI / 180;
  const sx = cx, sy = cy - r;
  const ex = cx + r * Math.sin(rad), ey = cy - r * Math.cos(rad);
  const large = Math.abs(angle) > 180 ? 1 : 0;
  const sweep = angle > 0 ? 1 : 0;
  tiltArc.setAttribute("d", `M${sx},${sy} A${r},${r} 0 ${large},${sweep} ${ex},${ey}`);
  tiltText.setAttribute("x", cx - r - 14);
  tiltText.setAttribute("y", cy - 4);
  tiltText.textContent = Math.abs(Math.round(angle)) + "°";
}

// ─── 主时间轴 ───
const tl = gsap.timeline({
  repeat: -1,
  repeatDelay: 2.5,
  defaults: { ease: "power1.inOut" }
});

/* ── 阶段1:平地行驶 ── */
tl.addLabel("phase1")
  .to(phaseText, { opacity: 1, duration: 0.4 })
  .call(() => { phaseText.textContent = "① 平地行驶"; phaseLabel.textContent = "平地行驶"; })
  .to(vehicle, { x: 488, duration: 3.2, ease: "none" }, "phase1")

/* ── 阶段2:前行星轮撞击 → 翻转跨级 ── */
.addLabel("phase2", "+=0.1")
  .call(() => { phaseText.textContent = "② 前轮组撞击台阶 · 支架翻转跨级"; phaseLabel.textContent = "行星支架翻转跨级"; })
  // 碰撞闪光
  .set(impactFlash, { x: STX, y: GY - 40, scale: 0.3, opacity: 0 })
  .to(impactFlash, { opacity: 0.9, scale: 1.2, duration: 0.12 }, "phase2")
  .to(impactFlash, { opacity: 0, scale: 1.8, duration: 0.35 }, "phase2+=0.12")
  // 底盘绕后铰接点倾斜
  .to(chassisTilt, { rotation: -TILT, duration: 2.4, ease: "power2.in",
      onUpdate: function() {
        const a = gsap.getProperty(chassisTilt, "rotation");
        updateTiltArc(a);
      }
    }, "phase2")
  // 前支架翻转120°
  .to(frontBracket, { rotation: 120, duration: 2.4, ease: "power2.inOut" }, "phase2")
  // 车辆继续前行
  .to(vehicle, { x: 540, duration: 2.4, ease: "none" }, "phase2")
  // 云台反向补偿(平台保持水平)
  .to(platRotator, { rotation: TILT, duration: 2.4, ease: "power2.in" }, "phase2")
  // 货物微颤(体现极短延迟)
  .to(cargo, { rotation: 1.5, duration: 0.08, ease: "power4.out" }, "phase2+=0.08")
  .to(cargo, { rotation: -0.8, duration: 0.1 }, "phase2+=0.16")
  .to(cargo, { rotation: 0, duration: 0.25, ease: "power2.out" }, "phase2+=0.26")
  // 显示动态标签
  .to(gimbalLabel, { opacity: 1, duration: 0.3 }, "phase2+=0.3")
  .to(bracketLabel, { opacity: 1, duration: 0.3 }, "phase2+=0.3")
  .to(tiltArcGroup, { opacity: 1, duration: 0.3 }, "phase2+=0.3")
  // 云台底座发光
  .to(gimbalBase, { attr: { opacity: 1 }, duration: 0.3 }, "phase2+=0.2")
  .call(() => { gimbalBase.classList.add("gimbal-pulse"); })

/* ── 阶段3:后行星轮跨级 ── */
.addLabel("phase3", "+=0.2")
  .call(() => { phaseText.textContent = "③ 后轮组跨级 · 云台持续补偿"; phaseLabel.textContent = "后轮跨级 · 云台补偿"; })
  // 车辆整体上升至台阶顶
  .to(vehicle, { y: SY, duration: 2.4, ease: "power1.inOut" }, "phase3")
  // 底盘恢复水平
  .to(chassisTilt, { rotation: 0, duration: 2.4, ease: "power2.out",
      onUpdate: function() {
        const a = gsap.getProperty(chassisTilt, "rotation");
        updateTiltArc(a);
      }
    }, "phase3")
  // 后支架翻转120°
  .to(rearBracket, { rotation: 120, duration: 2.4, ease: "power2.inOut" }, "phase3")
  // 车辆继续前行
  .to(vehicle, { x: 620, duration: 2.4, ease: "none" }, "phase3")
  // 平台恢复0°
  .to(platRotator, { rotation: 0, duration: 2.4, ease: "power2.out" }, "phase3")
  // 货物微颤
  .to(cargo, { rotation: 1, duration: 0.08 }, "phase3+=0.08")
  .to(cargo, { rotation: -0.5, duration: 0.1 }, "phase3+=0.16")
  .to(cargo, { rotation: 0, duration: 0.2 }, "phase3+=0.26")
  // 隐藏标签
  .to(gimbalLabel, { opacity: 0, duration: 0.5 }, "phase3+=1.5")
  .to(bracketLabel, { opacity: 0, duration: 0.5 }, "phase3+=1.5")
  .to(tiltArcGroup, { opacity: 0, duration: 0.5 }, "phase3+=1.5")
  .call(() => { gimbalBase.classList.remove("gimbal-pulse"); })
  .to(gimbalBase, { attr: { opacity: 0.85 }, duration: 0.3 }, "phase3+=1.8")

/* ── 阶段4:平稳驶离 ── */
.addLabel("phase4", "+=0.1")
  .call(() => { phaseText.textContent = "④ 完成跨级 · 平稳驶离"; phaseLabel.textContent = "完成跨级"; })
  .to(vehicle, { x: 850, duration: 2.8, ease: "none" }, "phase4")

/* ── 重置 ── */
.addLabel("reset")
  .to(phaseText, { opacity: 0, duration: 0.5 })
  .call(() => { phaseLabel.textContent = "准备重播…"; })
  .set(vehicle,      { x: 160, y: FY })
  .set(chassisTilt,  { rotation: 0 })
  .set(frontBracket, { rotation: 0 })
  .set(rearBracket,  { rotation: 0 })
  .set(platRotator,  { rotation: 0 })
  .set(cargo,        { rotation: 0 })
  .set(gimbalLabel,  { opacity: 0 })
  .set(bracketLabel, { opacity: 0 })
  .set(tiltArcGroup, { opacity: 0 });

// ─── 控制 ───
document.getElementById("btnReplay").addEventListener("click", () => { tl.restart(); });
document.getElementById("btnPause").addEventListener("click", () => {
  tl.isActive() ? tl.pause() : tl.resume();
});
</script>
</body>
</html>

实现说明:

  1. 层级解耦建模:底盘(含行星轮组)与载货平台分为独立图层。底盘倾斜组 chassisTilt 通过嵌套 translate(-70,0) 将旋转中心锁定在后铰接点,用 transformOrigin: "0px 0px" 确保绕后枢轴旋转;平台组 platformRotator 独立补偿旋转,绕云台铰接点反向转动。

  2. 行星轮翻转:前/后支架各自以铰接点为圆心旋转 120°,三小轮均布 120° 的三角框架随公转翻转,视觉上呈现"上方小轮越过台阶边缘成为新支撑点"的跨步过程。

  3. 云台补偿时机:底盘倾斜与平台反向补偿在同一时间段内同步进行("<" 同步启动),货物仅有极短暂的微颤(<0.3s 衰减),体现 <20ms 响应延迟的约束。

  4. 视觉引导:碰撞瞬间有黄色闪光;云台底座在补偿期间红色脉冲发光;倾斜角弧线与角度数值实时更新;平台上水平仪气泡始终居中,直观表达"绝对水平"。

  5. 边界条件:台阶高度 = 行星臂长(80px),右侧标注"臂长 = 台阶高"提示几何极限;底部注释说明越障失效条件。

积分规则:第一轮对话扣减8分,后续每轮扣6分