分享图
A
动画渲染工坊
就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>矩阵式伯努利非接触浮垫系统 — IFR原理动画</title>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;500;700;900&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config={theme:{extend:{fontFamily:{display:['Outfit','sans-serif'],mono:['JetBrains Mono','monospace']}}}}
</script>
<style>
:root{--bg:#050910;--surface:#0b1121;--fg:#d8e2f8;--muted:#4a5a7e;--accent:#00e5ff;--accent2:#ff7a2e;--accent3:#00ff88;--pcb:#0e5c35;--trace:#c9a832;--card:rgba(12,20,38,0.85);--border:#162040;--danger:#ff3860}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--fg);font-family:'Outfit',sans-serif;overflow:hidden;height:100vh}
#main-svg{width:100%;height:100%}
.glow-cyan{filter:drop-shadow(0 0 6px rgba(0,229,255,0.5))}
.glow-amber{filter:drop-shadow(0 0 5px rgba(255,122,46,0.5))}
.glow-green{filter:drop-shadow(0 0 5px rgba(0,255,136,0.5))}
.overlay-panel{background:var(--card);border:1px solid var(--border);border-radius:12px;backdrop-filter:blur(12px);padding:16px}
.phase-dot{width:10px;height:10px;border-radius:50%;border:2px solid var(--muted);transition:all .4s}
.phase-dot.active{background:var(--accent);border-color:var(--accent);box-shadow:0 0 10px var(--accent)}
.phase-dot.done{background:var(--accent3);border-color:var(--accent3)}
.ctrl-btn{background:var(--surface);border:1px solid var(--border);color:var(--fg);border-radius:8px;padding:8px 18px;cursor:pointer;font-family:'Outfit';font-size:14px;transition:all .25s}
.ctrl-btn:hover{border-color:var(--accent);color:var(--accent);box-shadow:0 0 12px rgba(0,229,255,0.15)}
.ctrl-btn:active{transform:scale(0.96)}
input[type=range]{-webkit-appearance:none;appearance:none;height:6px;background:var(--border);border-radius:3px;outline:none;cursor:pointer}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;border-radius:50%;background:var(--accent);border:2px solid var(--bg);cursor:pointer}
.data-val{font-family:'JetBrains Mono',monospace;font-weight:600;color:var(--accent)}
.data-label{font-size:12px;color:var(--muted);letter-spacing:0.5px}
.annotation{font-family:'Outfit';font-size:13px;fill:var(--fg);opacity:0;transition:opacity .6s}
.annotation.visible{opacity:1}
@keyframes dashFlow{to{stroke-dashoffset:-20}}
.dash-animate{animation:dashFlow .6s linear infinite}
@keyframes pulseGlow{0%,100%{opacity:.4}50%{opacity:.9}}
.pulse-glow{animation:pulseGlow 2s ease-in-out infinite}
</style>
</head>
<body class="relative">

<!-- 主SVG容器 -->
<svg id="main-svg" viewBox="0 0 1200 750" preserveAspectRatio="xMidYMid meet">
<defs>
  <!-- 背景网格 -->
  <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
    <path d="M 40 0 L 0 0 0 40" fill="none" stroke="#0e1830" stroke-width="0.5"/>
  </pattern>
  <!-- 伯努利模块渐变 -->
  <linearGradient id="moduleGrad" x1="0" y1="0" x2="0" y2="1">
    <stop offset="0%" stop-color="#3a4e70"/><stop offset="100%" stop-color="#1e2d48"/>
  </linearGradient>
  <!-- 气流发光 -->
  <filter id="airGlow" x="-80%" y="-80%" width="260%" height="260%">
    <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="b"/>
    <feColorMatrix in="b" type="matrix" values="0 0 0 0 0  0 0.9 1 0 0  0 0.9 1 0 0  0 0 0 0.6 0"/>
    <feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
  </filter>
  <!-- 间隙高亮 -->
  <filter id="gapGlow" x="-50%" y="-200%" width="200%" height="500%">
    <feGaussianBlur in="SourceGraphic" stdDeviation="6"/>
    <feColorMatrix type="matrix" values="0 0 0 0 0  0 1 1 0 0  0 1 1 0 0  0 0 0 0.35 0"/>
  </filter>
  <!-- PCB渐变 -->
  <linearGradient id="pcbGrad" x1="0" y1="0" x2="0" y2="1">
    <stop offset="0%" stop-color="#0f6b3a"/><stop offset="100%" stop-color="#0a4d2a"/>
  </linearGradient>
  <!-- 金属渐变 -->
  <linearGradient id="metalGrad" x1="0" y1="0" x2="0" y2="1">
    <stop offset="0%" stop-color="#5a6a88"/><stop offset="40%" stop-color="#3a4a68"/><stop offset="100%" stop-color="#2a3a58"/>
  </linearGradient>
  <!-- 柔性带渐变 -->
  <linearGradient id="beltGrad" x1="0" y1="0" x2="1" y2="0">
    <stop offset="0%" stop-color="#cc5500"/><stop offset="50%" stop-color="#ff8a30"/><stop offset="100%" stop-color="#cc5500"/>
  </linearGradient>
  <!-- 力箭头标记 -->
  <marker id="arrowCyan" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
    <path d="M 0 0 L 10 5 L 0 10 z" fill="#00e5ff"/>
  </marker>
  <marker id="arrowGreen" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
    <path d="M 0 0 L 10 5 L 0 10 z" fill="#00ff88"/>
  </marker>
  <marker id="arrowAmber" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
    <path d="M 0 0 L 10 5 L 0 10 z" fill="#ff7a2e"/>
  </marker>
  <marker id="arrowRed" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
    <path d="M 0 0 L 10 5 L 0 10 z" fill="#ff3860"/>
  </marker>
</defs>

<!-- 背景网格 -->
<rect width="1200" height="750" fill="url(#grid)"/>

<!-- 底部台面 -->
<rect x="100" y="560" width="1000" height="12" rx="2" fill="#141e34" stroke="#1e2d48" stroke-width="1"/>
<rect x="100" y="558" width="1000" height="3" rx="1" fill="#1a2844" opacity="0.6"/>

<!-- ===== 动态组:机械臂+模块 ===== -->
<g id="armGroup">
  <!-- 主横梁 -->
  <rect id="armBeam" x="230" y="250" width="640" height="28" rx="4" fill="url(#metalGrad)" stroke="#4a5a7a" stroke-width="1"/>
  <!-- 立柱左 -->
  <rect x="245" y="200" width="22" height="78" rx="3" fill="url(#metalGrad)" stroke="#4a5a7a" stroke-width="1"/>
  <!-- 立柱右 -->
  <rect x="833" y="200" width="22" height="78" rx="3" fill="url(#metalGrad)" stroke="#4a5a7a" stroke-width="1"/>
  <!-- 连接板左 -->
  <rect x="235" y="244" width="42" height="8" rx="2" fill="#4a5a78"/>
  <!-- 连接板右 -->
  <rect x="823" y="244" width="42" height="8" rx="2" fill="#4a5a78"/>
  
  <!-- 5个伯努利模块 -->
  <g id="modules"></g>
</g>

<!-- ===== 间隙发光区 ===== -->
<rect id="gapGlowRect" x="260" y="310" width="580" height="40" fill="#00e5ff" filter="url(#gapGlow)" opacity="0"/>

<!-- ===== 气流粒子组 ===== -->
<g id="particleGroup"></g>

<!-- ===== PCB组 ===== -->
<g id="pcbGroup">
  <rect id="pcbBody" x="270" y="520" width="560" height="24" rx="2" fill="url(#pcbGrad)" stroke="#1a8a50" stroke-width="1.2"/>
  <!-- PCB表面细节 -->
  <g id="pcbDetails"></g>
</g>

<!-- ===== 柔性带组 ===== -->
<g id="beltGroup"></g>

<!-- ===== 力矢量组 ===== -->
<g id="forceGroup"></g>

<!-- ===== 标注组 ===== -->
<g id="annotationGroup"></g>

<!-- ===== 俯视小图 ===== -->
<g id="topViewGroup" transform="translate(930,30)">
  <rect x="0" y="0" width="240" height="200" rx="10" fill="rgba(8,14,26,0.9)" stroke="#1e2d48" stroke-width="1"/>
  <text x="120" y="22" text-anchor="middle" fill="#5a6a8a" font-size="11" font-family="Outfit">俯视布局</text>
  <g transform="translate(20,32)">
    <!-- 俯视PCB -->
    <rect id="topPcb" x="20" y="20" width="160" height="120" rx="3" fill="#0e5c35" stroke="#1a8a50" stroke-width="1"/>
    <!-- 俯视模块 -->
    <circle cx="60" cy="55" r="12" fill="#2a3e60" stroke="#4a6a90" stroke-width="1" class="top-module"/>
    <circle cx="120" cy="55" r="12" fill="#2a3e60" stroke="#4a6a90" stroke-width="1" class="top-module"/>
    <circle cx="90" cy="80" r="12" fill="#2a3e60" stroke="#4a6a90" stroke-width="1" class="top-module"/>
    <circle cx="60" cy="105" r="12" fill="#2a3e60" stroke="#4a6a90" stroke-width="1" class="top-module"/>
    <circle cx="120" cy="105" r="12" fill="#2a3e60" stroke="#4a6a90" stroke-width="1" class="top-module"/>
    <!-- 俯视柔性带 -->
    <rect id="topBeltTop" x="15" y="14" width="170" height="8" rx="2" fill="url(#beltGrad)" opacity="0.5"/>
    <rect id="topBeltBottom" x="15" y="138" width="170" height="8" rx="2" fill="url(#beltGrad)" opacity="0.5"/>
    <rect id="topBeltLeft" x="14" y="14" width="8" height="148" rx="2" fill="url(#beltGrad)" opacity="0.5"/>
    <rect id="topBeltRight" x="178" y="14" width="8" height="148" rx="2" fill="url(#beltGrad)" opacity="0.5"/>
  </g>
</g>

</svg>

<!-- ===== 左侧数据面板 ===== -->
<div class="overlay-panel absolute top-4 left-4 w-56" style="font-size:13px">
  <div class="text-sm font-bold mb-3 tracking-wider" style="color:var(--accent)">
    <i class="fas fa-microchip mr-1"></i> 实时参数
  </div>
  <div class="space-y-2">
    <div class="flex justify-between"><span class="data-label">气垫间隙</span><span class="data-val" id="dataGap">0.0 mm</span></div>
    <div class="flex justify-between"><span class="data-label">法向吸力</span><span class="data-val" id="dataLift">0 kg</span></div>
    <div class="flex justify-between"><span class="data-label">板体重力</span><span style="color:var(--danger);font-family:'JetBrains Mono';font-weight:600">13.0 kg</span></div>
    <div class="flex justify-between"><span class="data-label">带预紧力</span><span class="data-val" id="dataTension" style="color:var(--accent2)">0 N</span></div>
    <div class="flex justify-between"><span class="data-label">气流速率</span><span class="data-val" id="dataFlow">0 L/min</span></div>
    <div class="flex justify-between"><span class="data-label">悬浮状态</span><span id="dataStatus" style="color:var(--muted);font-family:'JetBrains Mono';font-size:12px">待机</span></div>
  </div>
  <div class="mt-3 pt-3" style="border-top:1px solid var(--border)">
    <div class="text-xs mb-2" style="color:var(--accent3)">
      <i class="fas fa-lightbulb mr-1"></i> IFR核心:功能分离
    </div>
    <div class="text-xs" style="color:var(--muted);line-height:1.6">
      <span style="color:var(--accent)">■</span> 重力克服 → 气动力场<br>
      <span style="color:var(--accent2)">■</span> 水平约束 → 柔性张紧<br>
      <span style="color:var(--accent3)">■</span> 零接触 → 无损抓取
    </div>
  </div>
</div>

<!-- ===== 阶段指示器 ===== -->
<div class="absolute top-4 left-1/2 -translate-x-1/2 overlay-panel flex items-center gap-3 py-2 px-5" style="font-size:13px">
  <span id="phaseName" class="font-bold tracking-wide" style="color:var(--accent);min-width:90px">待机</span>
  <div class="flex gap-2">
    <div class="phase-dot active" data-phase="0"></div>
    <div class="phase-dot" data-phase="1"></div>
    <div class="phase-dot" data-phase="2"></div>
    <div class="phase-dot" data-phase="3"></div>
    <div class="phase-dot" data-phase="4"></div>
    <div class="phase-dot" data-phase="5"></div>
  </div>
</div>

<!-- ===== 底部控制面板 ===== -->
<div class="absolute bottom-4 left-1/2 -translate-x-1/2 overlay-panel flex items-center gap-5 py-3 px-6">
  <button class="ctrl-btn" id="playBtn"><i class="fas fa-play mr-1"></i>播放</button>
  <button class="ctrl-btn" id="stepBtn"><i class="fas fa-forward-step mr-1"></i>单步</button>
  <button class="ctrl-btn" id="resetBtn"><i class="fas fa-rotate-left mr-1"></i>重置</button>
  <div class="h-6 w-px" style="background:var(--border)"></div>
  <div class="flex flex-col gap-1">
    <label class="data-label">气流强度 <span id="airVal" class="data-val text-xs">80%</span></label>
    <input type="range" id="airSlider" min="0" max="100" value="80" class="w-36"/>
  </div>
  <div class="flex flex-col gap-1">
    <label class="data-label">带张力 <span id="beltVal" class="data-val text-xs" style="color:var(--accent2)">50%</span></label>
    <input type="range" id="beltSlider" min="0" max="100" value="50" class="w-36"/>
  </div>
</div>

<!-- 右下角失效提示 -->
<div id="failWarning" class="absolute bottom-20 right-4 overlay-panel text-xs hidden" style="border-color:var(--danger);max-width:220px">
  <div style="color:var(--danger)" class="font-bold mb-1"><i class="fas fa-triangle-exclamation mr-1"></i>失效警告</div>
  <div style="color:var(--muted);line-height:1.5" id="failText"></div>
</div>

<script>
const NS='http://www.w3.org/2000/svg';

/* ========== 配置 ========== */
const CFG={
  arm:{initY:0,workY:140,beamY:250}, // armGroup初始偏移,工作偏移
  pcb:{x:270,w:560,h:24,restY:520,liftedY:380},
  modules:{positions:[320,440,550,660,780],w:58,h:34,beamBottom:278},
  gap:{real:1.5,visual:38},
  belt:{pcbLeft:270,pcbRight:830},
  particles:{count:240},
  phases:[
    {name:'待机',dur:800},
    {name:'下降就位',dur:2200},
    {name:'气流启动',dur:2000},
    {name:'柔性带收紧',dur:1600},
    {name:'高速搬运',dur:3500},
    {name:'释放放板',dur:2200}
  ]
};

/* ========== 状态 ========== */
let state={
  phase:0,progress:0,playing:false,phaseTime:0,
  armY:0,pcbY:CFG.pcb.restY,gapSize:0,
  airOn:false,airIntensity:0.8,
  beltEngaged:false,beltTension:0.5,
  liftForce:0,transportX:0,
  failMode:false
};

/* ========== 缓动函数 ========== */
const ease={
  outCubic:t=>1-Math.pow(1-t,3),
  inOutCubic:t=>t<.5?4*t*t*t:1-Math.pow(-2*t+2,3)/2,
  outBack:t=>{const c=1.7;return 1+(c+1)*Math.pow(t-1,3)+c*Math.pow(t-1,2)},
  outElastic:t=>t===0?0:t===1?1:Math.pow(2,-10*t)*Math.sin((t*10-0.75)*(2*Math.PI)/3)+1,
  inOutQuad:t=>t<.5?2*t*t:1-Math.pow(-2*t+2,2)/2
};

/* ========== SVG引用 ========== */
const svg=document.getElementById('main-svg');
const armGroup=document.getElementById('armGroup');
const pcbGroup=document.getElementById('pcbGroup');
const particleGroup=document.getElementById('particleGroup');
const beltGroup=document.getElementById('beltGroup');
const forceGroup=document.getElementById('forceGroup');
const annotationGroup=document.getElementById('annotationGroup');
const gapGlowRect=document.getElementById('gapGlowRect');

/* ========== 创建伯努利模块 ========== */
const moduleEls=[];
CFG.modules.positions.forEach((mx,i)=>{
  const g=document.createElementNS(NS,'g');
  g.setAttribute('transform',`translate(${mx-CFG.modules.w/2},278)`);
  // 模块主体
  const body=document.createElementNS(NS,'rect');
  body.setAttribute('width',CFG.modules.w);body.setAttribute('height',CFG.modules.h);
  body.setAttribute('rx','4');body.setAttribute('fill','url(#moduleGrad)');
  body.setAttribute('stroke','#4a6a90');body.setAttribute('stroke-width','1');
  g.appendChild(body);
  // 进气口标识
  const intake=document.createElementNS(NS,'rect');
  intake.setAttribute('x','8');intake.setAttribute('y','4');
  intake.setAttribute('width',CFG.modules.w-16);intake.setAttribute('height','6');
  intake.setAttribute('rx','2');intake.setAttribute('fill','#1a2a44');
  intake.setAttribute('stroke','#2a4060');intake.setAttribute('stroke-width','0.5');
  g.appendChild(intake);
  // 出风口(底部缝隙)
  const vent=document.createElementNS(NS,'rect');
  vent.setAttribute('x','6');vent.setAttribute('y',CFG.modules.h-4);
  vent.setAttribute('width',CFG.modules.w-12);vent.setAttribute('height','3');
  vent.setAttribute('rx','1');vent.setAttribute('fill','#0a1424');
  vent.setAttribute('stroke','#1a3050');vent.setAttribute('stroke-width','0.5');
  vent.classList.add('module-vent');
  g.appendChild(vent);
  // 发光指示
  const indicator=document.createElementNS(NS,'circle');
  indicator.setAttribute('cx',CFG.modules.w/2);indicator.setAttribute('cy','2');
  indicator.setAttribute('r','2.5');indicator.setAttribute('fill','#1a2a44');
  indicator.classList.add('module-indicator');
  g.appendChild(indicator);
  document.getElementById('modules').appendChild(g);
  moduleEls.push({g,body,vent,indicator});
});

/* ========== PCB表面细节 ========== */
(function createPCBDetails(){
  const dg=document.getElementById('pcbDetails');
  // 铜箔走线
  for(let i=0;i<18;i++){
    const line=document.createElementNS(NS,'line');
    line.setAttribute('x1',280+Math.random()*540);
    line.setAttribute('y1',524+Math.random()*16);
    line.setAttribute('x2',280+Math.random()*540);
    line.setAttribute('y2',524+Math.random()*16);
    line.setAttribute('stroke','#8a7a20');line.setAttribute('stroke-width','0.6');
    line.setAttribute('opacity','0.5');
    dg.appendChild(line);
  }
  // IC芯片
  const chipPositions=[[300,527],[420,530],[540,526],[650,532],[760,528]];
  chipPositions.forEach(([cx,cy])=>{
    const r=document.createElementNS(NS,'rect');
    r.setAttribute('x',cx);r.setAttribute('y',cy);
    r.setAttribute('width',14+Math.random()*10);r.setAttribute('height',6+Math.random()*4);
    r.setAttribute('rx','1');r.setAttribute('fill','#1a1a2e');
    r.setAttribute('stroke','#2a2a4e');r.setAttribute('stroke-width','0.5');
    dg.appendChild(r);
  });
  // 电容
  for(let i=0;i<10;i++){
    const c=document.createElementNS(NS,'rect');
    c.setAttribute('x',290+Math.random()*530);
    c.setAttribute('y',525+Math.random()*14);
    c.setAttribute('width',4);c.setAttribute('height',4);
    c.setAttribute('rx','0.5');c.setAttribute('fill','#8b6914');
    c.setAttribute('opacity','0.7');
    dg.appendChild(c);
  }
  // 焊盘
  for(let i=0;i<15;i++){
    const p=document.createElementNS(NS,'circle');
    p.setAttribute('cx',285+Math.random()*530);
    p.setAttribute('cy',526+Math.random()*14);
    p.setAttribute('r','1.5');p.setAttribute('fill','#c0a030');
    p.setAttribute('opacity','0.6');
    dg.appendChild(p);
  }
})();

/* ========== 粒子系统 ========== */
const particles=[];
(function initParticles(){
  for(let i=0;i<CFG.particles.count;i++){
    const c=document.createElementNS(NS,'circle');
    c.setAttribute('r','1.8');c.setAttribute('fill','#00e5ff');
    c.setAttribute('opacity','0');
    particleGroup.appendChild(c);
    particles.push({
      el:c,active:false,
      x:0,y:0,vx:0,vy:0,
      life:0,maxLife:0,type:'down',modIdx:0
    });
  }
})();

function spawnParticle(modIdx){
  const p=particles.find(p=>!p.active);
  if(!p)return;
  const mx=CFG.modules.positions[modIdx];
  p.active=true;p.modIdx=modIdx;
  p.life=0;p.maxLife=35+Math.random()*25;
  p.type='down';
  p.x=mx+(Math.random()-0.5)*40;
  p.y=CFG.modules.beamBottom+CFG.modules.h+state.armY;
  p.vx=(Math.random()-0.5)*0.6;
  p.vy=2.5+Math.random()*2;
  p.el.setAttribute('fill','#00e5ff');
  p.el.setAttribute('r','1.8');
}

function updateParticles(){
  const pcbTop=state.pcbY;
  for(const p of particles){
    if(!p.active)continue;
    p.life++;
    if(p.life>p.maxLife){p.active=false;p.el.setAttribute('opacity','0');continue}
    p.x+=p.vx;p.y+=p.vy;
    // 到达PCB表面 → 扩散
    if(p.type==='down'&&p.y>=pcbTop-4){
      p.type='spread';
      const dir=p.x<550?-1:1;
      p.vx=dir*(1.5+Math.random()*2.5);
      p.vy=-0.3+Math.random()*0.4;
    }
    const alpha=Math.max(0,(1-p.life/p.maxLife)*0.65*state.airIntensity);
    p.el.setAttribute('cx',p.x);
    p.el.setAttribute('cy',p.y);
    p.el.setAttribute('opacity',String(alpha));
    if(p.type==='spread'){
      p.el.setAttribute('r',String(1.2+p.life*0.04));
      p.el.setAttribute('fill','#00c8e0');
    }
  }
}

/* ========== 柔性带 ========== */
let beltPaths=[];
function createBelts(){
  beltGroup.innerHTML='';
  beltPaths=[];
  ['left','right'].forEach(side=>{
    const path=document.createElementNS(NS,'path');
    path.setAttribute('fill','none');
    path.setAttribute('stroke','url(#beltGrad)');
    path.setAttribute('stroke-width','8');
    path.setAttribute('stroke-linecap','round');
    path.setAttribute('opacity','0');
    beltGroup.appendChild(path);
    // 防滑微凸起
    const teeth=document.createElementNS(NS,'path');
    teeth.setAttribute('fill','none');
    teeth.setAttribute('stroke','#ffaa55');
    teeth.setAttribute('stroke-width','1');
    teeth.setAttribute('stroke-dasharray','2 4');
    teeth.setAttribute('opacity','0');
    beltGroup.appendChild(teeth);
    beltPaths.push({path,teeth,side});
  });
}
createBelts();

function updateBeltPaths(){
  const pcbY=state.pcbY;
  const pcbLeft=CFG.pcb.x;
  const pcbRight=CFG.pcb.x+CFG.pcb.w;
  const armBottom=278+CFG.modules.h+state.armY;
  const engage=state.beltEngaged?ease.outCubic(Math.min(1,state.beltTension*2)):0;
  const beltOffset=engage*15; // 收缩偏移
  
  // 左带
  const lx1=pcbLeft-8+beltOffset;
  const ly1=armBottom;
  const lx2=pcbLeft+beltOffset*0.3;
  const ly2=pcbY+4;
  const lx3=pcbLeft+beltOffset*0.1;
  const ly3=pcbY+CFG.pcb.h-4;
  beltPaths[0].path.setAttribute('d',`M${lx1},${ly1} L${lx2},${ly2} L${lx3},${ly3}`);
  beltPaths[0].path.setAttribute('opacity',String(0.3+engage*0.7));
  beltPaths[0].teeth.setAttribute('d',`M${lx1+2},${ly1} L${lx2+2},${ly2} L${lx3+2},${ly3}`);
  beltPaths[0].teeth.setAttribute('opacity',String(engage*0.8));
  
  // 右带
  const rx1=pcbRight+8-beltOffset;
  const ry1=armBottom;
  const rx2=pcbRight-beltOffset*0.3;
  const ry2=pcbY+4;
  const rx3=pcbRight-beltOffset*0.1;
  const ry3=pcbY+CFG.pcb.h-4;
  beltPaths[1].path.setAttribute('d',`M${rx1},${ry1} L${rx2},${ry2} L${rx3},${ry3}`);
  beltPaths[1].path.setAttribute('opacity',String(0.3+engage*0.7));
  beltPaths[1].teeth.setAttribute('d',`M${rx1-2},${ry1} L${rx2-2},${ry2} L${rx3-2},${ry3}`);
  beltPaths[1].teeth.setAttribute('opacity',String(engage*0.8));
}

/* ========== 力矢量 ========== */
let forceArrows=[];
function createForceArrows(){
  forceGroup.innerHTML='';
  forceArrows=[];
  // 重力箭头
  const gArrow=document.createElementNS(NS,'line');
  gArrow.setAttribute('stroke','#ff3860');gArrow.setAttribute('stroke-width','2.5');
  gArrow.setAttribute('marker-end','url(#arrowRed)');gArrow.setAttribute('opacity','0');
  forceGroup.appendChild(gArrow);
  const gLabel=document.createElementNS(NS,'text');
  gLabel.setAttribute('fill','#ff3860');gLabel.setAttribute('font-size','12');
  gLabel.setAttribute('font-family','JetBrains Mono');gLabel.setAttribute('opacity','0');
  forceGroup.appendChild(gLabel);
  forceArrows.push({line:gArrow,label:gLabel,type:'gravity'});
  
  // 升力箭头(每个模块一个)
  CFG.modules.positions.forEach((mx,i)=>{
    const l=document.createElementNS(NS,'line');
    l.setAttribute('stroke','#00e5ff');l.setAttribute('stroke-width','2');
    l.setAttribute('marker-end','url(#arrowCyan)');l.setAttribute('opacity','0');
    forceGroup.appendChild(l);
    forceArrows.push({line:l,type:'lift',modIdx:i});
  });
  
  // 张力箭头(左右各一个)
  ['left','right'].forEach(side=>{
    const l=document.createElementNS(NS,'line');
    l.setAttribute('stroke','#ff7a2e');l.setAttribute('stroke-width','2');
    l.setAttribute('marker-end','url(#arrowAmber)');l.setAttribute('opacity','0');
    forceGroup.appendChild(l);
    forceArrows.push({line:l,type:'tension',side});
  });
}
createForceArrows();

function updateForceArrows(){
  const pcbCY=state.pcbY+CFG.pcb.h/2;
  const pcbCX=CFG.pcb.x+CFG.pcb.w/2;
  const armBottom=278+CFG.modules.h+state.armY;
  
  forceArrows.forEach(fa=>{
    if(fa.type==='gravity'){
      const show=state.phase>=2?1:0;
      const len=60;
      fa.line.setAttribute('x1',pcbCX);fa.line.setAttribute('y1',pcbCY+15);
      fa.line.setAttribute('x2',pcbCX);fa.line.setAttribute('y2',pcbCY+15+len);
      fa.line.setAttribute('opacity',String(show*0.8));
      fa.label.setAttribute('x',pcbCX+10);fa.label.setAttribute('y',pcbCY+15+len/2+4);
      fa.label.textContent='13kg';
      fa.label.setAttribute('opacity',String(show*0.7));
    }
    if(fa.type==='lift'){
      const mx=CFG.modules.positions[fa.modIdx];
      const show=state.liftForce>0?Math.min(1,state.liftForce/20):0;
      const len=40*show;
      fa.line.setAttribute('x1',mx);fa.line.setAttribute('y1',pcbCY-15);
      fa.line.setAttribute('x2',mx);fa.line.setAttribute('y2',pcbCY-15-len);
      fa.line.setAttribute('opacity',String(show*0.8));
    }
    if(fa.type==='tension'){
      const show=state.beltEngaged?ease.outCubic(state.beltTension*2):0;
      const len=30*show;
      const y=pcbCY;
      if(fa.side==='left'){
        fa.line.setAttribute('x1',CFG.pcb.x-5);fa.line.setAttribute('y1',y);
        fa.line.setAttribute('x2',CFG.pcb.x-5+len);fa.line.setAttribute('y2',y);
      }else{
        fa.line.setAttribute('x1',CFG.pcb.x+CFG.pcb.w+5);fa.line.setAttribute('y1',y);
        fa.line.setAttribute('x2',CFG.pcb.x+CFG.pcb.w+5-len);fa.line.setAttribute('y2',y);
      }
      fa.line.setAttribute('opacity',String(show*0.7));
    }
  });
}

/* ========== 标注 ========== */
function updateAnnotations(){
  annotationGroup.innerHTML='';
  const pcbY=state.pcbY;
  const armBottom=278+CFG.modules.h+state.armY;
  const gap=pcbY-armBottom;
  
  // 间隙标注(仅在悬浮时显示)
  if(state.gapSize>0.3){
    const gapMidX=CFG.pcb.x+CFG.pcb.w+30;
    // 引出线
    const line1=document.createElementNS(NS,'line');
    line1.setAttribute('x1',CFG.pcb.x+CFG.pcb.w);line1.setAttribute('y1',armBottom);
    line1.setAttribute('x2',gapMidX+20);line1.setAttribute('y2',armBottom);
    line1.setAttribute('stroke','#5a6a8a');line1.setAttribute('stroke-width','0.5');
    line1.setAttribute('stroke-dasharray','3 2');
    annotationGroup.appendChild(line1);
    
    const line2=document.createElementNS(NS,'line');
    line2.setAttribute('x1',CFG.pcb.x+CFG.pcb.w);line2.setAttribute('y1',pcbY);
    line2.setAttribute('x2',gapMidX+20);line2.setAttribute('y2',pcbY);
    line2.setAttribute('stroke','#5a6a8a');line2.setAttribute('stroke-width','0.5');
    line2.setAttribute('stroke-dasharray','3 2');
    annotationGroup.appendChild(line2);
    
    // 尺寸线
    const dimLine=document.createElementNS(NS,'line');
    dimLine.setAttribute('x1',gapMidX+10);dimLine.setAttribute('y1',armBottom+2);
    dimLine.setAttribute('x2',gapMidX+10);dimLine.setAttribute('y2',pcbY-2);
    dimLine.setAttribute('stroke','#00e5ff');dimLine.setAttribute('stroke-width','1');
    dimLine.setAttribute('marker-start','url(#arrowCyan)');dimLine.setAttribute('marker-end','url(#arrowCyan)');
    annotationGroup.appendChild(dimLine);
    
    // 标签
    const label=document.createElementNS(NS,'text');
    label.setAttribute('x',gapMidX+18);label.setAttribute('y',(armBottom+pcbY)/2+4);
    label.setAttribute('fill','#00e5ff');label.setAttribute('font-size','13');
    label.setAttribute('font-family','JetBrains Mono');label.setAttribute('font-weight','600');
    label.textContent=`${state.gapSize.toFixed(1)}mm`;
    annotationGroup.appendChild(label);
    
    // "零接触"标签
    const ncLabel=document.createElementNS(NS,'text');
    ncLabel.setAttribute('x',gapMidX+18);ncLabel.setAttribute('y',(armBottom+pcbY)/2+20);
    ncLabel.setAttribute('fill','#00ff88');ncLabel.setAttribute('font-size','10');
    ncLabel.setAttribute('font-family','Outfit');ncLabel.setAttribute('opacity','0.7');
    ncLabel.textContent='ZERO CONTACT';
    annotationGroup.appendChild(ncLabel);
  }
  
  // 伯努利原理标注
  if(state.airOn&&state.phase>=2){
    const bx=200;const by=armBottom+15;
    const t1=document.createElementNS(NS,'text');
    t1.setAttribute('x',bx);t1.setAttribute('y',by);
    t1.setAttribute('fill','#00e5ff');t1.setAttribute('font-size','11');
    t1.setAttribute('font-family','Outfit');t1.setAttribute('opacity','0.6');
    t1.textContent='高速气流 → 负压提升';
    annotationGroup.appendChild(t1);
    const t2=document.createElementNS(NS,'text');
    t2.setAttribute('x',bx);t2.setAttribute('y',by+16);
    t2.setAttribute('fill','#00c8e0');t2.setAttribute('font-size','11');
    t2.setAttribute('font-family','Outfit');t2.setAttribute('opacity','0.5');
    t2.textContent='四周排气 → 气垫悬浮';
    annotationGroup.appendChild(t2);
  }
  
  // 柔性带标注
  if(state.beltEngaged&&state.beltTension>0.3){
    const label=document.createElementNS(NS,'text');
    label.setAttribute('x',CFG.pcb.x-50);label.setAttribute('y',state.pcbY+CFG.pcb.h+20);
    label.setAttribute('fill','#ff7a2e');label.setAttribute('font-size','11');
    label.setAttribute('font-family','Outfit');label.setAttribute('opacity','0.7');
    label.textContent='柔性张紧 · 水平约束';
    annotationGroup.appendChild(label);
  }
}

/* ========== 俯视图更新 ========== */
function updateTopView(){
  const engage=state.beltEngaged?state.beltTension:0;
  const offset=engage*6;
  document.getElementById('topBeltTop').setAttribute('y',20-offset+6);
  document.getElementById('topBeltBottom').setAttribute('y',140+offset-6);
  document.getElementById('topBeltLeft').setAttribute('x',20-offset+6);
  document.getElementById('topBeltRight').setAttribute('x',180+offset-6);
  
  const beltOp=0.3+engage*0.7;
  document.getElementById('topBeltTop').setAttribute('opacity',beltOp);
  document.getElementById('topBeltBottom').setAttribute('opacity',beltOp);
  document.getElementById('topBeltLeft').setAttribute('opacity',beltOp);
  document.getElementById('topBeltRight').setAttribute('opacity',beltOp);
  
  // 模块发光
  document.querySelectorAll('.top-module').forEach((el,i)=>{
    if(state.airOn){
      el.setAttribute('fill','#00e5ff');el.setAttribute('fill-opacity','0.3');
      el.setAttribute('stroke','#00e5ff');
    }else{
      el.setAttribute('fill','#2a3e60');el.setAttribute('fill-opacity','1');
      el.setAttribute('stroke','#4a6a90');
    }
  });
}

/* ========== 模块指示灯 ========== */
function updateModuleIndicators(){
  moduleEls.forEach(m=>{
    if(state.airOn){
      m.indicator.setAttribute('fill','#00e5ff');
      m.indicator.classList.add('pulse-glow');
    }else{
      m.indicator.setAttribute('fill','#1a2a44');
      m.indicator.classList.remove('pulse-glow');
    }
  });
}

/* ========== 间隙发光 ========== */
function updateGapGlow(){
  const armBottom=278+CFG.modules.h+state.armY;
  const pcbTop=state.pcbY;
  gapGlowRect.setAttribute('y',armBottom);
  gapGlowRect.setAttribute('height',Math.max(0,pcbTop-armBottom));
  gapGlowRect.setAttribute('opacity',state.airOn?String(0.15*state.airIntensity):'0');
}

/* ========== 数据面板 ========== */
function updateDataPanel(){
  document.getElementById('dataGap').textContent=state.gapSize.toFixed(1)+' mm';
  document.getElementById('dataLift').textContent=state.liftForce.toFixed(1)+' kg';
  document.getElementById('dataTension').textContent=(state.beltEngaged?(state.beltTension*15*4).toFixed(0):'0')+' N';
  document.getElementById('dataFlow').textContent=state.airOn?Math.round(state.airIntensity*320)+' L/min':'0 L/min';
  
  const statusEl=document.getElementById('dataStatus');
  const phaseNames=['待机','下降中','悬浮提升','带收紧','搬运中','释放中'];
  statusEl.textContent=phaseNames[state.phase]||'待机';
  statusEl.style.color=state.airOn?'var(--accent)':'var(--muted)';
  
  // 失效警告
  const failEl=document.getElementById('failWarning');
  const failText=document.getElementById('failText');
  if(state.airIntensity<0.3&&state.airOn){
    failEl.classList.remove('hidden');
    failText.textContent='气流强度不足,伯努利负压无法维持悬浮,PCB有坠落风险!';
    state.failMode=true;
  }else if(state.gapSize>2.5&&state.airOn){
    failEl.classList.remove('hidden');
    failText.textContent='气垫间隙过大,负压泄露,吸附力急剧下降!';
    state.failMode=true;
  }else{
    failEl.classList.add('hidden');
    state.failMode=false;
  }
}

/* ========== 阶段指示器 ========== */
function updatePhaseIndicator(){
  document.getElementById('phaseName').textContent=CFG.phases[state.phase].name;
  document.querySelectorAll('.phase-dot').forEach((dot,i)=>{
    dot.classList.toggle('active',i===state.phase);
    dot.classList.toggle('done',i<state.phase);
  });
}

/* ========== 阶段逻辑 ========== */
function updatePhase(dt){
  if(!state.playing)return;
  state.phaseTime+=dt;
  const phase=CFG.phases[state.phase];
  const t=Math.min(1,state.phaseTime/phase.dur);
  
  switch(state.phase){
    case 0: // 待机
      state.armY=0;
      state.pcbY=CFG.pcb.restY;
      state.gapSize=0;state.liftForce=0;
      state.airOn=false;state.beltEngaged=false;
      break;
    case 1: // 下降
      state.armY=ease.outCubic(t)*CFG.arm.workY;
      state.pcbY=CFG.pcb.restY;
      break;
    case 2: // 气流启动
      state.armY=CFG.arm.workY;
      state.airOn=true;
      state.airIntensity=parseFloat(document.getElementById('airSlider').value)/100;
      const liftT=ease.outElastic(t);
      state.pcbY=CFG.pcb.restY-(CFG.pcb.restY-CFG.pcb.liftedY)*liftT;
      state.gapSize=CFG.gap.real*liftT;
      state.liftForce=5*4*state.airIntensity*liftT; // 5模块 × 4kg
      break;
    case 3: // 柔性带收紧
      state.armY=CFG.arm.workY;
      state.airOn=true;
      state.beltEngaged=true;
      state.beltTension=parseFloat(document.getElementById('beltSlider').value)/100;
      state.pcbY=CFG.pcb.liftedY;
      state.gapSize=CFG.gap.real;
      state.liftForce=5*4*state.airIntensity;
      break;
    case 4: // 高速搬运
      state.armY=CFG.arm.workY;
      state.airOn=true;
      state.beltEngaged=true;
      const moveT=ease.inOutQuad(t);
      state.transportX=Math.sin(moveT*Math.PI*2)*80;
      state.pcbY=CFG.pcb.liftedY+Math.sin(moveT*Math.PI*4)*3; // 轻微振动
      break;
    case 5: // 释放放板
      state.armY=CFG.arm.workY;
      const releaseT=ease.inOutCubic(t);
      if(t>0.4)state.beltEngaged=false;
      if(t>0.6){state.airOn=false;state.liftForce=5*4*state.airIntensity*(1-(t-0.6)/0.4);}
      state.pcbY=CFG.pcb.liftedY+(CFG.pcb.restY-CFG.pcb.liftedY)*releaseT;
      state.gapSize=CFG.gap.real*(1-releaseT);
      state.transportX=state.transportX*(1-releaseT);
      break;
  }
  
  // 阶段完成
  if(t>=1){
    state.phaseTime=0;
    if(state.phase<5){
      state.phase++;
    }else{
      state.playing=false;
      state.phase=0;
      state.transportX=0;
      updatePlayBtn();
    }
    updatePhaseIndicator();
  }
}

/* ========== 主渲染循环 ========== */
let lastTime=0;
let spawnAccum=0;

function render(time){
  const dt=lastTime?time-lastTime:16;
  lastTime=time;
  
  // 更新阶段
  updatePhase(dt);
  
  // 生成粒子
  if(state.airOn&&state.airIntensity>0.1){
    spawnAccum+=dt;
    const spawnInterval=20/state.airIntensity;
    while(spawnAccum>=spawnInterval){
      spawnAccum-=spawnInterval;
      const modIdx=Math.floor(Math.random()*5);
      spawnParticle(modIdx);
    }
  }
  
  // 更新粒子
  updateParticles();
  
  // 更新视觉
  const armTranslate=state.armY+state.transportX*0; // 臂不水平移,PCB和臂一起
  armGroup.setAttribute('transform',`translate(${state.transportX},${-state.armY})`);
  pcbGroup.setAttribute('transform',`translate(${state.transportX},${state.pcbY-CFG.pcb.restY})`);
  
  // 更新柔性带
  updateBeltPaths();
  
  // 更新力矢量
  updateForceArrows();
  
  // 更新标注
  updateAnnotations();
  
  // 更新间隙发光
  updateGapGlow();
  
  // 更新模块指示灯
  updateModuleIndicators();
  
  // 更新俯视图
  updateTopView();
  
  // 更新数据面板
  updateDataPanel();
  
  requestAnimationFrame(render);
}

/* ========== 控制逻辑 ========== */
const playBtn=document.getElementById('playBtn');
const stepBtn=document.getElementById('stepBtn');
const resetBtn=document.getElementById('resetBtn');
const airSlider=document.getElementById('airSlider');
const beltSlider=document.getElementById('beltSlider');

function updatePlayBtn(){
  playBtn.innerHTML=state.playing?'<i class="fas fa-pause mr-1"></i>暂停':'<i class="fas fa-play mr-1"></i>播放';
}

playBtn.addEventListener('click',()=>{
  if(state.phase>=5&&!state.playing){
    resetState();
  }
  state.playing=!state.playing;
  updatePlayBtn();
});

stepBtn.addEventListener('click',()=>{
  state.playing=false;
  updatePlayBtn();
  state.phaseTime=0;
  if(state.phase<5)state.phase++;
  else resetState();
  updatePhaseIndicator();
});

resetBtn.addEventListener('click',()=>{
  resetState();
  updatePlayBtn();
});

function resetState(){
  state.phase=0;state.phaseTime=0;state.progress=0;state.playing=false;
  state.armY=0;state.pcbY=CFG.pcb.restY;state.gapSize=0;
  state.airOn=false;state.liftForce=0;state.beltEngaged=false;
  state.beltTension=0.5;state.transportX=0;state.failMode=false;
  state.airIntensity=0.8;
  particles.forEach(p=>{p.active=false;p.el.setAttribute('opacity','0')});
  updatePhaseIndicator();
}

airSlider.addEventListener('input',e=>{
  state.airIntensity=e.target.value/100;
  document.getElementById('airVal').textContent=e.target.value+'%';
  // 如果正在运行,实时影响
  if(state.airOn){
    state.liftForce=5*4*state.airIntensity;
  }
});

beltSlider.addEventListener('input',e=>{
  state.beltTension=e.target.value/100;
  document.getElementById('beltVal').textContent=e.target.value+'%';
});

/* ========== 初始化 ========== */
updatePhaseIndicator();
requestAnimationFrame(render);
</script>
</body>
</html>

实现说明

这个高保真SVG原理动画完整呈现了"矩阵式伯努利非接触浮垫与张紧柔性边框"的工作机理,严格遵循TRIZ最终理想解(IFR)的设计思想:

视觉架构

  • 深色工程蓝图风格:暗海军蓝底色搭配精密网格,营造专业技术氛围
  • 功能色彩分离:青色(cyan)表征气动力场,琥珀色(amber)表征柔性约束,翠绿色表征零接触理想状态,红色表征重力/风险
  • 双视图联动:主剖面图 + 右上角俯视布局图同步更新

IFR核心表达

  1. 直接展示理想解:动画从待机直接进入工作流程,不做前后对比,聚焦"零接触悬浮"这一最终理想状态
  2. 资源利用可视化:气流粒子系统直观展示如何利用空气(免费资源)产生负压提升力和气垫悬浮,标注"高速气流→负压提升 / 四周排气→气垫悬浮"
  3. 功能分离标注:左侧面板用色块明确区分"重力克服→气动力场 / 水平约束→柔性张紧 / 零接触→无损抓取"

六阶段动作时序

待机→下降就位→气流启动(伯努利悬浮)→柔性带收紧→高速搬运(含微振动)→释放放板,每阶段有专属缓动函数

交互设计

  • 气流强度滑块:降低至30%以下触发失效警告(负压不足,PCB坠落风险)
  • 带张力滑块:实时调节水平约束力
  • 播放/单步/重置:支持自动演示和逐步观察
积分规则:第一轮对话扣减6分,后续每轮扣4分