分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TRIZ IFR: 负压悬浮攀爬系统</title>
<style>
  :root {
    --bg-base: #050914;
    --grid-color: rgba(30, 58, 138, 0.2);
    --neon-blue: #00f0ff;
    --neon-amber: #ffb703;
    --metal-dark: #1e293b;
    --metal-light: #475569;
    --text-main: #e2e8f0;
    --text-muted: #64748b;
  }

  body, html {
    margin: 0;
    padding: 0;
    width: 100%;
    height: 100%;
    background-color: var(--bg-base);
    color: var(--text-main);
    font-family: 'SF Pro Display', 'Inter', -apple-system, sans-serif;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
  }

  /* 背景网格 */
  .grid-bg {
    position: absolute;
    inset: 0;
    background-image: 
      linear-gradient(to right, var(--grid-color) 1px, transparent 1px),
      linear-gradient(to bottom, var(--grid-color) 1px, transparent 1px);
    background-size: 40px 40px;
    opacity: 0.5;
    z-index: 0;
  }

  /* 绝对定位的小字号 HUD 层,严格避免遮挡核心区 */
  .hud {
    position: absolute;
    z-index: 10;
    background: rgba(15, 23, 42, 0.8);
    border: 1px solid rgba(0, 240, 255, 0.3);
    border-radius: 6px;
    padding: 12px 16px;
    backdrop-filter: blur(8px);
    pointer-events: none;
  }

  .hud-tl { top: 24px; left: 24px; width: 280px; }
  .hud-br { bottom: 24px; right: 24px; width: 320px; text-align: right; }
  .hud-bl { bottom: 24px; left: 24px; width: 240px; }

  .title {
    font-size: 14px;
    font-weight: 700;
    color: #fff;
    letter-spacing: 1.5px;
    margin-bottom: 8px;
    display: flex;
    align-items: center;
    gap: 8px;
  }

  .title::before {
    content: '';
    display: block;
    width: 8px;
    height: 8px;
    background: var(--neon-blue);
    border-radius: 50%;
    box-shadow: 0 0 8px var(--neon-blue);
  }

  .text-sm {
    font-size: 12px;
    line-height: 1.6;
    color: var(--text-muted);
  }

  .mono {
    font-family: 'JetBrains Mono', 'Fira Code', monospace;
  }

  .highlight { color: var(--neon-blue); font-weight: 600; }
  .amber { color: var(--neon-amber); font-weight: 600; }

  .data-row {
    display: flex;
    justify-content: space-between;
    margin-top: 6px;
    font-size: 11px;
    border-top: 1px dashed rgba(100, 116, 139, 0.3);
    padding-top: 6px;
  }

  /* SVG 核心动画区 */
  #scene {
    position: relative;
    z-index: 5;
    width: 100vw;
    height: 100vh;
  }

  /* SVG 元素样式 */
  .stair-polygon { fill: #0f172a; stroke: none; }
  .stair-edge { stroke: var(--neon-blue); stroke-width: 2; filter: drop-shadow(0 0 4px rgba(0, 240, 255, 0.6)); }
  .stair-surface { stroke: #1e293b; stroke-width: 1; stroke-dasharray: 4 4; }

  .rail { stroke: var(--metal-light); stroke-width: 10; stroke-linecap: round; }
  .piston-rod { stroke: #cbd5e1; stroke-width: 4; stroke-linecap: round; }
  .joint { fill: #94a3b8; stroke: var(--bg-base); stroke-width: 2; }
  
  .suction-cup { transition: fill 0.3s, filter 0.3s; }
  .cup-idle { fill: var(--metal-light); }
  .cup-active { fill: var(--neon-blue); filter: drop-shadow(0 0 8px var(--neon-blue)); }

  .vac-flow {
    stroke: var(--neon-blue);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-dasharray: 4 8;
    animation: flowUp 0.6s linear infinite;
    opacity: 0;
    transition: opacity 0.3s;
  }
  .vac-active .vac-flow { opacity: 1; }

  @keyframes flowUp {
    to { stroke-dashoffset: -12; }
  }

  .robot-body {
    fill: rgba(30, 41, 59, 0.9);
    stroke: var(--neon-amber);
    stroke-width: 2;
    filter: drop-shadow(0 10px 20px rgba(0,0,0,0.5));
  }
  
  .target-line {
    stroke: var(--neon-amber);
    stroke-width: 1.5;
    stroke-dasharray: 4 4;
    opacity: 0.4;
  }
</style>
</head>
<body>

<div class="grid-bg"></div>

<!-- 左上角:状态与核心参数 -->
<div class="hud hud-tl">
  <div class="title">IFR: 空气负压悬浮攀爬系统</div>
  <div class="text-sm">消除轮式打滑矛盾,变连续滚动为交替步行。</div>
  <div class="data-row mono">
    <span>系统状态</span>
    <span id="sys-status" class="highlight">初始化序列...</span>
  </div>
  <div class="data-row mono">
    <span>前置机械足极值</span>
    <span id="front-press">-0.0 kPa</span>
  </div>
  <div class="data-row mono">
    <span>后置机械足极值</span>
    <span id="rear-press">-0.0 kPa</span>
  </div>
</div>

<!-- 左下角:实时推杆行程 -->
<div class="hud hud-bl">
  <div class="text-sm" style="color: #fff; margin-bottom: 4px;">两自由度电动推杆监测</div>
  <div class="data-row mono">
    <span>前足垂直行程</span>
    <span id="front-stroke">0 mm</span>
  </div>
  <div class="data-row mono">
    <span>后足垂直行程</span>
    <span id="rear-stroke">0 mm</span>
  </div>
</div>

<!-- 右下角:TRIZ 原理说明 -->
<div class="hud hud-br">
  <div class="text-sm" style="color: #fff; margin-bottom: 4px;">TRIZ 最终理想解 (IFR)</div>
  <div class="text-sm">
    <span class="highlight">原理剖析:</span>利用环境中无处不在的空气作为“隐形锚点资源”。当机械足贴合台阶表面时,微型真空泵瞬间建立极高负压(-60kPa),将环境大气压转化为强大的法向抓取力。车身无需依赖任何斜面摩擦力,通过纯粹的悬空拉拽实现绝对抓地。
  </div>
</div>

<svg id="scene" viewBox="0 0 1000 600" preserveAspectRatio="xMidYMid meet">
  <defs>
    <!-- 发光滤镜 -->
    <filter id="glow-amber" x="-20%" y="-20%" width="140%" height="140%">
      <feGaussianBlur stdDeviation="4" result="blur" />
      <feComposite in="SourceGraphic" in2="blur" operator="over" />
    </filter>
  </defs>

  <g id="world">
    <!-- 台阶环境 -->
    <g id="stairs-group"></g>
    
    <!-- 机器人主体 -->
    <g id="robot">
      <!-- 预测运动轨迹 -->
      <line id="target-vector" class="target-line" x1="0" y1="0" x2="0" y2="0" />
      
      <!-- 后置机械足 -->
      <g id="rear-leg">
        <line id="r-h-rail" class="rail" x1="0" y1="0" x2="0" y2="0"/>
        <line id="r-h-piston" class="piston-rod" x1="0" y1="0" x2="0" y2="0"/>
        <line id="r-v-rail" class="rail" x1="0" y1="0" x2="0" y2="0"/>
        <line id="r-v-piston" class="piston-rod" x1="0" y1="0" x2="0" y2="0"/>
        <circle id="r-joint" class="joint" cx="0" cy="0" r="6"/>
        <g id="r-cup-group">
          <path id="r-cup" class="suction-cup cup-idle" d="M -15 -8 L 15 -8 L 20 0 L -20 0 Z"/>
          <g id="r-vac-fx" class="vac-active" style="opacity: 0;">
            <line class="vac-flow" x1="-10" y1="15" x2="-2" y2="0"/>
            <line class="vac-flow" x1="0" y1="20" x2="0" y2="0"/>
            <line class="vac-flow" x1="10" y1="15" x2="2" y2="0"/>
          </g>
          <text x="25" y="0" fill="#64748b" font-size="10" font-family="monospace">REAR_VAC</text>
        </g>
      </g>

      <!-- 前置机械足 -->
      <g id="front-leg">
        <line id="f-h-rail" class="rail" x1="0" y1="0" x2="0" y2="0"/>
        <line id="f-h-piston" class="piston-rod" x1="0" y1="0" x2="0" y2="0"/>
        <line id="f-v-rail" class="rail" x1="0" y1="0" x2="0" y2="0"/>
        <line id="f-v-piston" class="piston-rod" x1="0" y1="0" x2="0" y2="0"/>
        <circle id="f-joint" class="joint" cx="0" cy="0" r="6"/>
        <g id="f-cup-group">
          <path id="f-cup" class="suction-cup cup-idle" d="M -15 -8 L 15 -8 L 20 0 L -20 0 Z"/>
          <g id="f-vac-fx" class="vac-active" style="opacity: 0;">
            <line class="vac-flow" x1="-10" y1="15" x2="-2" y2="0"/>
            <line class="vac-flow" x1="0" y1="20" x2="0" y2="0"/>
            <line class="vac-flow" x1="10" y1="15" x2="2" y2="0"/>
          </g>
          <text x="25" y="0" fill="#00f0ff" font-size="10" font-family="monospace">FRONT_VAC</text>
        </g>
      </g>

      <!-- 主车身箱体 -->
      <rect id="main-body" class="robot-body" x="-70" y="-35" width="140" height="70" rx="8" />
      <circle cx="0" cy="0" r="4" fill="#ffb703" filter="url(#glow-amber)"/>
      <text x="-55" y="-15" fill="#94a3b8" font-size="10" font-family="monospace" letter-spacing="1">CORE BOX</text>
      <line x1="-55" y1="15" x2="55" y2="15" stroke="#334155" stroke-width="2" stroke-dasharray="2 4"/>
    </g>
  </g>
</svg>

<script>
/**
 * 动画状态机引擎 (抗标签页节流)
 */
let logicalTime = 0;
let lastRealTime = performance.now();
const tweens = [];

function tween(obj, prop, target, duration, delay = 0, ease = t => t) {
  return new Promise(resolve => {
    tweens.push({
      obj, prop, start: obj[prop], target,
      startTime: logicalTime + delay,
      duration, ease, resolve
    });
  });
}

function sleep(ms) {
  return new Promise(resolve => {
    tweens.push({ isSleep: true, startTime: logicalTime, duration: ms, resolve });
  });
}

// 缓动函数
const easeInOutCubic = t => t < .5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
const easeOutQuad = t => t * (2 - t);
const easeInQuad = t => t * t;

/**
 * 状态与场景数据
 */
const state = {
  camX: 300, camY: 200, // 相机中心点
  bodyX: 300, bodyY: 180, // 车身中心点
  fFootX: 500, fFootY: 200, // 前足落点
  rFootX: 300, rFootY: 300, // 后足落点
  fVac: false, rVac: false  // 真空状态
};

// 物理映射常量
const SCALE = 3; // 1px = 3mm (行程300mm映射为100px)
const BODY_HALF_W = 70;
const RAIL_OFFSET_Y = -10; // 导轨在车身的相对高度

/**
 * 构建无限循环台阶几何
 */
function buildStairs() {
  const group = document.getElementById('stairs-group');
  let html = '';
  // 预生成足够宽的台阶,通过后续平移实现无限视觉循环
  for(let i = -3; i <= 6; i++) {
    let x = i * 200;
    let y = 400 - i * 100;
    // 台阶实体
    html += `<polygon class="stair-polygon" points="${x},1000 ${x},${y} ${x+200},${y} ${x+200},1000" />`;
    // 发光边缘
    html += `<polyline class="stair-edge" points="${x},${y} ${x+200},${y} ${x+200},${y-100}" />`;
    // 表面纹理
    html += `<line class="stair-surface" x1="${x+10}" y1="${y+10}" x2="${x+190}" y2="${y+10}" />`;
  }
  group.innerHTML = html;
}

/**
 * 核心渲染循环
 */
function render() {
  // 更新相机世界视图
  const world = document.getElementById('world');
  world.setAttribute('transform', `translate(${500 - state.camX}, ${300 - state.camY})`);

  // 更新车身
  const body = document.getElementById('main-body');
  body.setAttribute('x', state.bodyX - BODY_HALF_W);
  body.setAttribute('y', state.bodyY - 35);
  
  // 核心连接点
  const attachY = state.bodyY + RAIL_OFFSET_Y;
  const fAttachX = state.bodyX + BODY_HALF_W;
  const rAttachX = state.bodyX - BODY_HALF_W;

  // 更新前机械足 (两自由度结构: 水平导轨 + 垂直推杆)
  document.getElementById('f-h-rail').setAttribute('x1', fAttachX);
  document.getElementById('f-h-rail').setAttribute('y1', attachY);
  document.getElementById('f-h-rail').setAttribute('x2', state.fFootX);
  document.getElementById('f-h-rail').setAttribute('y2', attachY);
  
  document.getElementById('f-h-piston').setAttribute('x1', fAttachX);
  document.getElementById('f-h-piston').setAttribute('y1', attachY);
  document.getElementById('f-h-piston').setAttribute('x2', state.fFootX);
  document.getElementById('f-h-piston').setAttribute('y2', attachY);

  document.getElementById('f-v-rail').setAttribute('x1', state.fFootX);
  document.getElementById('f-v-rail').setAttribute('y1', attachY);
  document.getElementById('f-v-rail').setAttribute('x2', state.fFootX);
  document.getElementById('f-v-rail').setAttribute('y2', state.fFootY - 30);

  document.getElementById('f-v-piston').setAttribute('x1', state.fFootX);
  document.getElementById('f-v-piston').setAttribute('y1', state.fFootY - 30);
  document.getElementById('f-v-piston').setAttribute('x2', state.fFootX);
  document.getElementById('f-v-piston').setAttribute('y2', state.fFootY);

  document.getElementById('f-joint').setAttribute('cx', state.fFootX);
  document.getElementById('f-joint').setAttribute('cy', attachY);

  const fCupGroup = document.getElementById('f-cup-group');
  fCupGroup.setAttribute('transform', `translate(${state.fFootX}, ${state.fFootY})`);

  // 更新后机械足
  document.getElementById('r-h-rail').setAttribute('x1', rAttachX);
  document.getElementById('r-h-rail').setAttribute('y1', attachY);
  document.getElementById('r-h-rail').setAttribute('x2', state.rFootX);
  document.getElementById('r-h-rail').setAttribute('y2', attachY);
  
  document.getElementById('r-h-piston').setAttribute('x1', rAttachX);
  document.getElementById('r-h-piston').setAttribute('y1', attachY);
  document.getElementById('r-h-piston').setAttribute('x2', state.rFootX);
  document.getElementById('r-h-piston').setAttribute('y2', attachY);

  document.getElementById('r-v-rail').setAttribute('x1', state.rFootX);
  document.getElementById('r-v-rail').setAttribute('y1', attachY);
  document.getElementById('r-v-rail').setAttribute('x2', state.rFootX);
  document.getElementById('r-v-rail').setAttribute('y2', state.rFootY - 30);

  document.getElementById('r-v-piston').setAttribute('x1', state.rFootX);
  document.getElementById('r-v-piston').setAttribute('y1', state.rFootY - 30);
  document.getElementById('r-v-piston').setAttribute('x2', state.rFootX);
  document.getElementById('r-v-piston').setAttribute('y2', state.rFootY);

  document.getElementById('r-joint').setAttribute('cx', state.rFootX);
  document.getElementById('r-joint').setAttribute('cy', attachY);

  const rCupGroup = document.getElementById('r-cup-group');
  rCupGroup.setAttribute('transform', `translate(${state.rFootX}, ${state.rFootY})`);

  // 更新目标牵引线
  const targetLine = document.getElementById('target-vector');
  targetLine.setAttribute('x1', state.bodyX);
  targetLine.setAttribute('y1', state.bodyY);
  targetLine.setAttribute('x2', state.fFootX);
  targetLine.setAttribute('y2', state.fFootY);

  // 更新 UI 状态
  const fCup = document.getElementById('f-cup');
  const fVacFx = document.getElementById('f-vac-fx');
  if(state.fVac) {
    fCup.setAttribute('class', 'suction-cup cup-active');
    fVacFx.style.opacity = '1';
    document.getElementById('front-press').textContent = '-60.0 kPa';
    document.getElementById('front-press').classList.add('highlight');
  } else {
    fCup.setAttribute('class', 'suction-cup cup-idle');
    fVacFx.style.opacity = '0';
    document.getElementById('front-press').textContent = '-0.0 kPa';
    document.getElementById('front-press').classList.remove('highlight');
  }

  const rCup = document.getElementById('r-cup');
  const rVacFx = document.getElementById('r-vac-fx');
  if(state.rVac) {
    rCup.setAttribute('class', 'suction-cup cup-active');
    rVacFx.style.opacity = '1';
    document.getElementById('rear-press').textContent = '-60.0 kPa';
    document.getElementById('rear-press').classList.add('highlight');
  } else {
    rCup.setAttribute('class', 'suction-cup cup-idle');
    rVacFx.style.opacity = '0';
    document.getElementById('rear-press').textContent = '-0.0 kPa';
    document.getElementById('rear-press').classList.remove('highlight');
  }

  // 动态行程计算 (垂直距 * SCALE)
  const fStroke = Math.max(0, state.fFootY - attachY) * SCALE;
  const rStroke = Math.max(0, state.rFootY - attachY) * SCALE;
  document.getElementById('front-stroke').textContent = fStroke.toFixed(0) + ' mm';
  document.getElementById('rear-stroke').textContent = rStroke.toFixed(0) + ' mm';
}

function updateSysStatus(text, isAction = true) {
  const el = document.getElementById('sys-status');
  el.textContent = text;
  el.className = isAction ? 'amber' : 'highlight';
}

/**
 * 动画主循环
 */
function animateLoop(now) {
  let dt = now - lastRealTime;
  lastRealTime = now;
  if (dt > 100) dt = 16; // 防止切换标签页导致的暴走
  logicalTime += dt;

  let activeTweens = [];
  for (let tw of tweens) {
    if (logicalTime < tw.startTime) {
      activeTweens.push(tw);
      continue;
    }
    if (tw.isSleep) {
      if (logicalTime >= tw.startTime + tw.duration) {
        if(tw.resolve) tw.resolve();
      } else {
        activeTweens.push(tw);
      }
      continue;
    }

    let progress = (logicalTime - tw.startTime) / tw.duration;
    if (progress >= 1) {
      tw.obj[tw.prop] = tw.target;
      if (tw.resolve) tw.resolve();
    } else {
      tw.obj[tw.prop] = tw.start + (tw.target - tw.start) * tw.ease(progress);
      activeTweens.push(tw);
    }
  }
  
  tweens.length = 0;
  tweens.push(...activeTweens);

  render();
  requestAnimationFrame(animateLoop);
}

/**
 * 核心业务序列 (锚定-拉拽-换脚)
 */
async function runSequence() {
  await sleep(500);

  while(true) {
    // ---- 阶段 1: 前足建立锚定 ----
    updateSysStatus("前置机械足下探锚定台阶");
    tween(state, 'fFootY', 200, 400, 0, easeOutQuad);
    await sleep(500);
    state.fVac = true;
    updateSysStatus("建立极端负压场 [-60kPa]", false);
    await sleep(800);

    // ---- 阶段 2: 拉拽车身悬空 ----
    updateSysStatus("后足释放,推杆收缩悬空拉拽");
    state.rVac = false;
    
    // 车身移动到 S2 级台阶的正上方
    tween(state, 'bodyX', 500, 1500, 0, easeInOutCubic);
    tween(state, 'bodyY', 80, 1500, 0, easeInOutCubic);
    
    // 相机跟随平移,保持居中
    tween(state, 'camX', 500, 1500, 0, easeInOutCubic);
    tween(state, 'camY', 100, 1500, 0, easeInOutCubic);
    
    // 后足离地前摆收缩
    tween(state, 'rFootX', 500, 1200, 0, easeInOutCubic);
    tween(state, 'rFootY', 150, 1000, 0, easeInOutCubic);

    await sleep(1500);

    // ---- 阶段 3: 后足降落锚定 ----
    updateSysStatus("后置机械足精准就位");
    tween(state, 'rFootY', 200, 400, 0, easeOutQuad);
    await sleep(400);
    state.rVac = true;
    updateSysStatus("后置防滑负压锁定", false);
    await sleep(600);

    // ---- 阶段 4: 前足释放前探 ----
    updateSysStatus("交替步行:前足释放探寻新锚点");
    state.fVac = false;
    
    // 前足抬起
    tween(state, 'fFootY', 50, 400, 0, easeInOutCubic);
    await sleep(400);
    
    // 前足水平前移至下一台阶 S3
    tween(state, 'fFootX', 700, 800, 0, easeInOutCubic);
    await sleep(800);
    
    // ---- 阶段 5: 无缝重置循环 (视觉无感空间平移) ----
    // 车身从 (300,180) -> (500,80),平移量 (200, -100)
    // 整体减去平移量,实现无限阶梯逻辑
    state.bodyX -= 200; state.bodyY += 100;
    state.rFootX -= 200; state.rFootY += 100;
    state.fFootX -= 200; state.fFootY += 100;
    state.camX -= 200; state.camY += 100;
    
    // 重置渲染跳过中间态
    render();
  }
}

// 初始化
buildStairs();
requestAnimationFrame(animateLoop);
runSequence();

</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分