分享图
A
动画渲染工坊
就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>漏斗导引 · 超声波真空贴合原理动画</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;600;700&family=JetBrains+Mono:wght@300;400;500&display=swap" rel="stylesheet">
<style>
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
:root{
  --bg:#060a12;--surface:#0d1422;--elevated:#162034;--border:#1e3050;
  --text:#d8e2f0;--dim:#5a7090;--copper:#e89040;--copper-hi:#f4b868;
  --cyan:#00cce0;--purple:#9068f8;--green:#20d088;--red:#f04848;
}
html,body{width:100%;height:100%;background:var(--bg);color:var(--text);
  font-family:'JetBrains Mono',monospace;overflow-x:hidden}
body{display:flex;flex-direction:column;align-items:center;min-height:100vh;padding:20px 12px 32px}
.hdr{text-align:center;margin-bottom:16px}
.hdr h1{font-family:'Rajdhani',sans-serif;font-weight:700;font-size:clamp(20px,3vw,30px);
  letter-spacing:3px;color:var(--cyan);text-transform:uppercase}
.hdr p{font-size:11px;color:var(--dim);margin-top:2px;letter-spacing:1px}
.wrap{width:100%;max-width:1120px;display:flex;flex-direction:column;gap:12px}
.phase-bar{display:flex;gap:4px}
.pst{flex:1;height:5px;border-radius:3px;background:var(--border);transition:background .5s,box-shadow .5s}
.pst.active{background:var(--cyan);box-shadow:0 0 8px rgba(0,204,224,.4)}
.pst.done{background:var(--green)}
.svg-box{width:100%;background:var(--surface);border:1px solid var(--border);border-radius:10px;overflow:hidden}
.svg-box svg{display:block;width:100%;height:auto}
.ctrl{display:flex;flex-wrap:wrap;gap:10px;align-items:center;padding:14px 18px;
  background:var(--surface);border:1px solid var(--border);border-radius:10px}
.btn{font-family:'Rajdhani',sans-serif;font-weight:600;font-size:13px;padding:7px 16px;
  border:1px solid var(--border);border-radius:5px;background:var(--elevated);color:var(--text);
  cursor:pointer;transition:all .15s;letter-spacing:1px;text-transform:uppercase;user-select:none}
.btn:hover{background:var(--border)}.btn:active{transform:scale(.96)}
.btn.pri{border-color:var(--cyan);color:var(--cyan)}.btn.pri:hover{background:rgba(0,204,224,.1)}
.sld{display:flex;align-items:center;gap:6px}
.sld label{font-size:10px;color:var(--dim);white-space:nowrap}
.sld input[type=range]{-webkit-appearance:none;width:90px;height:3px;background:var(--border);border-radius:2px;outline:none}
.sld input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:12px;height:12px;border-radius:50%;background:var(--cyan);cursor:pointer}
.sld .v{font-size:10px;color:var(--cyan);min-width:52px}
.plbl{font-family:'Rajdhani',sans-serif;font-size:15px;font-weight:600;color:var(--copper);letter-spacing:1px;margin-left:auto}
.info{padding:14px 18px;background:var(--surface);border:1px solid var(--border);border-radius:10px;
  font-size:11px;line-height:1.8;color:var(--dim)}
.info .hl{color:var(--cyan);font-weight:500}
.info .wr{color:var(--red)}
.info .ok{color:var(--green)}
.sep{width:1px;height:22px;background:var(--border);margin:0 4px}
</style>
</head>
<body>
<header class="hdr">
  <h1>漏斗状微孔导引 · 超声波真空贴合</h1>
  <p>Funnel-Guided Micro-Hole Alignment &middot; Ultrasonic Vacuum Bonding</p>
</header>
<div class="wrap">
  <div class="phase-bar" id="pBar"></div>
  <div class="svg-box">
    <svg id="svg" viewBox="0 0 1020 720" xmlns="http://www.w3.org/2000/svg"></svg>
  </div>
  <div class="ctrl">
    <button class="btn" id="bPrev">◀ 上一步</button>
    <button class="btn pri" id="bNext">下一步 ▶</button>
    <button class="btn" id="bAuto">自动播放</button>
    <button class="btn" id="bReset">重置</button>
    <div class="sep"></div>
    <div class="sld"><label>超声频率</label><input type="range" id="sFreq" min="20" max="40" value="28"><span class="v" id="vFreq">28 kHz</span></div>
    <div class="sld"><label>真空度</label><input type="range" id="sVac" min="10" max="90" value="50"><span class="v" id="vVac">-0.50 atm</span></div>
    <span class="plbl" id="pLbl">准备就绪</span>
  </div>
  <div class="info" id="info">点击 <span class="hl">下一步</span> 开始动画。本动画展示漏斗导引掩膜板 + 超声波真空贴合方案的核心原理——利用漏斗将 0.12mm 靶区扩大至 0.5mm(降低 90% 对准难度),超声波克服微米级摩擦与静电粘附,真空场提供穿透驱动力,实现铜柱自组织入孔。</div>
</div>

<script>
(function(){
'use strict';
const NS='http://www.w3.org/2000/svg';
const svg=document.getElementById('svg');

/* ── 工具函数 ── */
function $el(tag,a,p){const e=document.createElementNS(NS,tag);if(a)Object.entries(a).forEach(([k,v])=>e.setAttribute(k,v));if(p)p.appendChild(e);return e}
function $a(e,a){Object.entries(a).forEach(([k,v])=>e.setAttribute(k,v))}
function $t(txt,a,p){const e=$el('text',a,p);e.textContent=txt;return e}
function lerp(a,b,t){return a+(b-a)*t}
function clamp(v,lo,hi){return Math.max(lo,Math.min(hi,v))}

/* ── 配置 ── */
const C={
  ax:190,aw:640,ar:830,
  holes:5,
  hx:[254,382,510,638,766],
  // 掩膜板
  my:178,mh:105,mTop:28,mFunnel:58,mChan:19,
  ftW:58,fbW:13,
  // 玻璃板
  gy:283,gh:38,ghW:11,
  // 真空腔
  cy:321,ch:162,
  // 铜柱
  pw:9,ph:14,total:30,
  // 物理参数
  grav:500,vibAmp:2.2,attract:150,vacPull:100
};
const phaseNames=['准备就绪','贴合掩膜板','开启真空','撒布铜柱','超声波振动','吹气清理','移除掩膜板'];
const phaseInfos=[
  '点击 <span class="hl">下一步</span> 开始动画。本动画展示漏斗导引掩膜板 + 超声波真空贴合方案的核心原理——利用漏斗将 0.12mm 靶区扩大至 0.5mm(降低 90% 对准难度),超声波克服微米级摩擦与静电粘附,真空场提供穿透驱动力,实现铜柱自组织入孔。',
  '将漏斗状导引掩膜板精准贴合在玻璃板上方。掩膜板孔径<span class="hl">上大下小</span>(上部 Φ0.5mm,下部 Φ0.13mm),与玻璃微孔一一对应,形成倒角漏斗导引结构。',
  '启动底部真空腔,真空度达 <span class="hl">-0.5 atm</span>,产生持续向下的气流牵引力。气流穿过漏斗与玻璃孔,为铜柱提供穿透驱动力。',
  '将大量铜柱(Φ0.12mm)散布在掩膜板表面。铜柱随机落在漏斗开口附近或平板区域,等待导引。',
  '开启超声波振动(<span class="hl">28kHz / 5μm振幅</span>),铜柱在声流效应与漏斗斜面引导下<span class="hl">自动寻孔滑入</span>,在真空吸力下穿过掩膜板落入玻璃孔——这是最终理想解的核心时刻。',
  '关闭超声波,从上方吹气清理表面未入孔的铜柱。只有已正确入孔的铜柱在真空吸力下稳固保留。',
  '移除掩膜板,所有铜柱已精准就位于玻璃微孔中。<span class="ok">对准难度降低 90%,无需逐根机械插入。</span> 注意:<span class="wr">0.13→0.12mm 缩径处存在"架桥"卡死风险</span>,需确保铜柱无毛刺。'
];

/* ── 状态 ── */
let S={phase:0,pt:0,gt:0,auto:false,atimer:null,
  pillars:[],maskG:null,airEls:[],usEls:[],zoomG:null,
  freq:28,vac:50};

/* ── 初始化场景 ── */
const gBg=$el('g',{id:'bg'},svg);
const gChamber=$el('g',{id:'gChamber'},svg);
const gGlass=$el('g',{id:'gGlass'},svg);
const gMask=$el('g',{id:'gMask'},svg);
const gAirflow=$el('g',{id:'gAirflow'},svg);
const gPillars=$el('g',{id:'gPillars'},svg);
const gUsonic=$el('g',{id:'gUsonic'},svg);
const gAnno=$el('g',{id:'gAnno'},svg);
const gZoom=$el('g',{id:'gZoom'},svg);
const gFx=$el('g',{id:'gFx'},svg);

/* 滤镜与渐变 */
const defs=$el('defs',null,svg);
function mkFilter(id,color,sd){
  const f=$el('filter',{id,x:'-50%',y:'-50%',width:'200%',height:'200%'},defs);
  $el('feGaussianBlur',{in:'SourceGraphic',stdDeviation:String(sd),result:'b'},f);
  $el('feFlood',{'flood-color':color,'flood-opacity':'0.55'},f);
  const c=$el('feComposite',{in2:'b',operator:'in',result:'g'},f);
  $el('feMerge',null,f);
  $el('feMergeNode',{in:'g'},f.lastElementChild||f);
  $el('feMergeNode',{in:'SourceGraphic'},f.lastElementChild);
  return f;
}
mkFilter('gCyan','#00cce0',4);
mkFilter('gPurple','#9068f8',3);
mkFilter('gGreen','#20d088',6);
mkFilter('gCopper','#e89040',2);
mkFilter('gRed','#f04848',5);

const gCopper=$el('linearGradient',{id:'gcopper',x1:0,y1:0,x2:1,y2:1},defs);
$el('stop',{offset:'0%','stop-color':'#f4b868'},gCopper);
$el('stop',{offset:'50%','stop-color':'#e89040'},gCopper);
$el('stop',{offset:'100%','stop-color':'#b86818'},gCopper);

const gMaskF=$el('linearGradient',{id:'gmask',x1:0,y1:0,x2:0,y2:1},defs);
$el('stop',{offset:'0%','stop-color':'#3a5878'},gMaskF);
$el('stop',{offset:'100%','stop-color':'#1c3450'},gMaskF);

const gGlassF=$el('linearGradient',{id:'gglass',x1:0,y1:0,x2:0,y2:1},defs);
$el('stop',{offset:'0%','stop-color':'rgba(130,190,250,0.18)'},gGlassF);
$el('stop',{offset:'100%','stop-color':'rgba(100,160,220,0.06)'},gGlassF);

const gChamF=$el('linearGradient',{id:'gcham',x1:0,y1:0,x2:0,y2:1},defs);
$el('stop',{offset:'0%','stop-color':'#0c1822'},gChamF);
$el('stop',{offset:'100%','stop-color':'#060c14'},gChamF);

const arrowM=$el('marker',{id:'arrC',viewBox:'0 0 10 6',refX:10,refY:3,markerWidth:7,markerHeight:5,orient:'auto'},defs);
$el('path',{d:'M0,0 L10,3 L0,6 Z',fill:'#00cce0'},arrowM);

/* 背景网格 */
$el('rect',{width:1020,height:720,fill:'#080e18'},gBg);
const gridP=$el('pattern',{id:'grid',width:40,height:40,patternUnits:'userSpaceOnUse'},defs);
$el('path',{d:'M40,0 L0,0 0,40',fill:'none',stroke:'#14203a','stroke-width':0.5},gridP);
$el('rect',{width:1020,height:720,fill:'url(#grid)',opacity:0.6},gBg);

/* ── 绘制静态组件 ── */
function drawChamber(){
  const g=gChamber;
  $el('rect',{x:C.ax+30,y:C.cy,width:C.aw-60,height:C.ch,fill:'url(#gcham)',stroke:'#2a3a52','stroke-width':1.5,rx:6},g);
  $el('rect',{x:C.ax+50,y:C.cy-5,width:C.aw-100,height:10,fill:'#162838',stroke:'#2a4058','stroke-width':0.8,rx:2},g);
  const px=C.ax+C.aw/2;
  $el('rect',{x:px-14,y:C.cy+C.ch-4,width:28,height:22,fill:'#12202e',stroke:'#2a3a52','stroke-width':1,rx:3},g);
  $t('真空抽气口',{x:px,y:C.cy+C.ch+30,fill:'#4a6a8a','font-size':9,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},g);
  $t('真空腔',{x:C.ax-8,y:C.cy+C.ch/2+4,fill:'#4a6a8a','font-size':10,'text-anchor':'end','font-family':'JetBrains Mono,monospace'},g);
  // 真空度指示
  const vg=$el('g',{id:'vacInd',opacity:0},g);
  $t('-0.5 atm',{x:px,y:C.cy+C.ch/2+4,fill:'#00cce0','font-size':12,'text-anchor':'middle','font-weight':500,'font-family':'JetBrains Mono,monospace',filter:'url(#gCyan)'},vg);
}

function drawGlass(){
  const g=gGlass;
  $el('rect',{x:C.ax,y:C.gy,width:C.aw,height:C.gh,fill:'url(#gglass)',stroke:'#4a6a8a','stroke-width':1.5,rx:2},g);
  C.hx.forEach(cx=>{
    $el('rect',{x:cx-C.ghW/2,y:C.gy,width:C.ghW,height:C.gh,fill:'#060c14',stroke:'#3a5a7a','stroke-width':0.5},g);
  });
  $t('玻璃板',{x:C.ax-8,y:C.gy+C.gh/2+4,fill:'#5a8aaa','font-size':10,'text-anchor':'end','font-family':'JetBrains Mono,monospace'},g);
}

function drawMask(){
  if(S.maskG)return;
  const g=gMask;
  // 掩膜板主体
  $el('rect',{x:C.ax,y:C.my,width:C.aw,height:C.mh,fill:'url(#gmask)',stroke:'#4a6a8a','stroke-width':1.5,rx:2},g);
  // 漏斗孔
  C.hx.forEach(cx=>{
    const ftop=C.my+C.mTop;
    const fbot=ftop+C.mFunnel;
    const cbot=fbot+C.mChan;
    // 漏斗空间(内部)
    $el('path',{d:`M${cx-C.ftW/2},${C.my} L${cx-C.fbW/2},${fbot} L${cx-C.fbW/2},${cbot} L${cx+C.fbW/2},${cbot} L${cx+C.fbW/2},${fbot} L${cx+C.ftW/2},${C.my} Z`,
      fill:'#080e18',stroke:'none'},g);
    // 漏斗壁高亮
    $el('line',{x1:cx-C.ftW/2,y1:C.my,x2:cx-C.fbW/2,y2:fbot,stroke:'#5a8ab8','stroke-width':1.2},g);
    $el('line',{x1:cx+C.ftW/2,y1:C.my,x2:cx+C.fbW/2,y2:fbot,stroke:'#5a8ab8','stroke-width':1.2},g);
    // 漏斗底部通道壁
    $el('line',{x1:cx-C.fbW/2,y1:fbot,x2:cx-C.fbW/2,y2:cbot,stroke:'#4a7a9a','stroke-width':0.8},g);
    $el('line',{x1:cx+C.fbW/2,y1:fbot,x2:cx+C.fbW/2,y2:cbot,stroke:'#4a7a9a','stroke-width':0.8},g);
  });
  $t('导引掩膜板',{x:C.ax-8,y:C.my+C.mh/2+4,fill:'#6a9ac0','font-size':10,'text-anchor':'end','font-family':'JetBrains Mono,monospace'},g);
  $t('Φ0.5',{x:C.hx[0],y:C.my-6,fill:'#00cce0','font-size':8,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},g);
  $t('Φ0.13',{x:C.hx[0],y:C.my+C.mTop+C.mFunnel+C.mChan+12,fill:'#00cce0','font-size':8,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},g);
  S.maskG=g;
}

/* ── 气流箭头 ── */
function createAirflow(){
  clearG(gAirflow);
  C.hx.forEach(cx=>{
    // 漏斗内的气流线
    for(let i=0;i<3;i++){
      const off=(i-1)*6;
      const l=$el('line',{x1:cx+off,y1:C.my+10,x2:cx+off*0.3,y2:C.gy,
        stroke:'#00cce0','stroke-width':0.8,'stroke-dasharray':'4,6',
        'marker-end':'url(#arrC)',opacity:0},gAirflow);
      S.airEls.push({el:l,cx:cx+off,off,delay:i*0.3});
    }
    // 玻璃孔内的气流线
    const l2=$el('line',{x1:cx,y1:C.gy,x2:cx,y2:C.cy+C.ch*0.5,
      stroke:'#00cce0','stroke-width':1,'stroke-dasharray':'3,5',
      'marker-end':'url(#arrC)',opacity:0},gAirflow);
    S.airEls.push({el:l2,cx,delay:0.5});
  });
}
function showAirflow(prog){
  S.airEls.forEach((a,i)=>{
    const p=clamp((prog-a.delay)*2,0,1);
    $a(a.el,{opacity:p*0.7,'stroke-dashoffset':-S.gt*60});
  });
}

/* ── 超声波可视化 ── */
function createUsonic(){
  clearG(gUsonic);
  // 左右两侧的波纹
  for(let side=0;side<2;side++){
    const bx=side===0?C.ax-4:C.ar+4;
    for(let i=0;i<4;i++){
      const a=$el('path',{
        d:`M${bx},${C.my+20+i*22} Q${bx+(side===0?-12:12)},${C.my+26+i*22} ${bx},${C.my+32+i*22}`,
        stroke:'#9068f8','stroke-width':1.5,fill:'none',opacity:0,filter:'url(#gPurple)'
      },gUsonic);
      S.usEls.push({el:a,side,idx:i});
    }
  }
}
function showUsonic(prog){
  const freq=S.freq;
  const t=S.gt;
  S.usEls.forEach(u=>{
    const p=clamp(prog*3-u.idx*0.2,0,1);
    const wave=Math.sin(t*freq*0.3+u.idx*1.5)*3;
    const bx=u.side===0?C.ax-4+wave:C.ar+4+wave;
    $a(u.el,{opacity:p*0.8,
      d:`M${bx},${C.my+20+u.idx*22} Q${bx+(u.side===0?-14:14)},${C.my+26+u.idx*22} ${bx},${C.my+32+u.idx*22}`
    });
  });
  // 掩膜板微振动
  if(S.maskG){
    const dx=Math.sin(t*freq*0.3)*1.5*(prog);
    const dy=Math.cos(t*freq*0.2)*0.8*(prog);
    $a(S.maskG,{transform:`translate(${dx},${dy})`});
  }
}

/* ── 铜柱 ── */
const PST={SCATTER:0,ON_MASK:1,ATTRACT:2,IN_FUNNEL:3,FALLING:4,SEATED:5,BLOWN:6};

function createPillars(){
  clearG(gPillars);
  S.pillars=[];
  // 5个目标铜柱(每个漏斗一个)
  C.hx.forEach((cx,hi)=>{
    const startX=cx+(Math.random()-0.5)*80;
    const p={
      el:null,x:startX,y:40+Math.random()*30,
      vx:0,vy:0,
      state:PST.SCATTER,
      targetHole:hi,
      phase:Math.random()*Math.PI*2,
      seated:false,
      seatTime:0,
      isTarget:true,
      funnelEntry:0
    };
    p.el=$el('rect',{width:C.pw,height:C.ph,rx:2,fill:'url(#gcopper)',stroke:'#c07020','stroke-width':0.5,opacity:0},gPillars);
    S.pillars.push(p);
  });
  // 其余随机铜柱
  for(let i=0;i<C.total-C.holes;i++){
    const startX=C.ax+30+Math.random()*(C.aw-60);
    const p={
      el:null,x:startX,y:30+Math.random()*50,
      vx:(Math.random()-0.5)*20,vy:Math.random()*30,
      state:PST.SCATTER,
      targetHole:-1,
      phase:Math.random()*Math.PI*2,
      seated:false,
      seatTime:0,
      isTarget:false,
      funnelEntry:0
    };
    p.el=$el('rect',{width:C.pw,height:C.ph,rx:2,fill:'url(#gcopper)',stroke:'#c07020','stroke-width':0.5,opacity:0},gPillars);
    S.pillars.push(p);
  }
}

function updatePillars(dt){
  const freq=S.freq;
  const vacStr=S.vac/50;
  const t=S.gt;
  S.pillars.forEach(p=>{
    if(p.state===PST.SCATTER){
      // 下落
      p.vy+=C.grav*dt;
      p.y+=p.vy*dt;
      p.x+=p.vx*dt;
      const surfY=C.my-C.ph;
      if(p.y>=surfY&&p.x>C.ax&&p.x<C.ar){
        p.y=surfY;p.vy=0;p.vx=0;p.state=PST.ON_MASK;
      }
      if(p.y>720){p.state=PST.BLOWN;$a(p.el,{opacity:0});}
      $a(p.el,{opacity:1});
    }
    else if(p.state===PST.ON_MASK){
      // 等待Phase4
      $a(p.el,{opacity:1});
    }
    else if(p.state===PST.ATTRACT){
      // 超声振动+向目标孔移动
      const vibX=Math.sin(t*freq*0.3+p.phase)*C.vibAmp*(freq/28);
      const vibY=Math.cos(t*freq*0.25+p.phase)*C.vibAmp*0.6*(freq/28);
      const hx=C.hx[p.targetHole];
      const dx=hx-p.x;
      const dist=Math.abs(dx);
      const force=C.attract*vacStr/(dist+10);
      p.vx+=Math.sign(dx)*force*dt*60;
      p.vy+=C.vacPull*vacStr*dt*2;
      p.vx*=0.96;p.vy*=0.96;
      p.x+=p.vx*dt+p.vibDx||0;
      p.y+=p.vy*dt;
      // 进入漏斗检测
      if(dist<C.ftW/2-4&&p.y>C.my-2){
        p.state=PST.IN_FUNNEL;
        p.funnelEntry=t;
        p.x=hx;
      }
      p.x+=vibX*0.3;p.y=clamp(p.y,C.my-C.ph-5,C.my-C.ph);
      $a(p.el,{opacity:1});
    }
    else if(p.state===PST.IN_FUNNEL){
      // 沿漏斗下滑
      const hx=C.hx[p.targetHole];
      const elapsed=t-p.funnelEntry;
      const prog=clamp(elapsed/2.5,0,1);
      const topY=C.my;
      const botY=C.my+C.mTop+C.mFunnel+C.mChan;
      // 漏斗内位置插值
      p.y=lerp(topY-C.ph,botY-C.ph+10,prog*prog);
      p.x=hx+Math.sin(t*freq*0.2+p.phase)*0.5*(1-prog);
      // 检查是否到达玻璃孔
      if(p.y>=C.gy-C.ph+2){
        p.state=PST.FALLING;
        p.funnelEntry=t;
      }
      $a(p.el,{opacity:1});
    }
    else if(p.state===PST.FALLING){
      // 穿过玻璃孔
      const elapsed=t-p.funnelEntry;
      const prog=clamp(elapsed/0.8,0,1);
      const hx=C.hx[p.targetHole];
      p.y=lerp(C.gy,C.gy+C.gh-C.ph,prog);
      p.x=hx;
      if(prog>=1){
        p.state=PST.SEATED;p.seated=true;p.seatTime=t;
        // 绿色闪光
        $a(p.el,{fill:'#20d088',filter:'url(#gGreen)'});
        setTimeout(()=>{$a(p.el,{fill:'url(#gcopper)',filter:'none'});},600);
      }
      $a(p.el,{opacity:1});
    }
    else if(p.state===PST.SEATED){
      $a(p.el,{opacity:1});
    }
    else if(p.state===PST.BLOWN){
      $a(p.el,{opacity:0});
    }
    // 更新SVG位置
    $a(p.el,{x:p.x-C.pw/2,y:p.y,transform:p.state>=PST.IN_FUNNEL?`rotate(0,${p.x},${p.y+C.ph/2})`:'');
    });
}

/* ── 吹气效果 ── */
function blowPillars(){
  S.pillars.forEach(p=>{
    if(p.state===PST.ON_MASK||p.state===PST.ATTRACT){
      p.state=PST.BLOWN;
      const dir=p.x<500?-1:1;
      p.vx=dir*(200+Math.random()*200);
      p.vy=-100-Math.random()*150;
    }
  });
  // 吹气动画
  let blowT=0;
  function blowAnim(){
    blowT+=0.016;
    S.pillars.forEach(p=>{
      if(p.state===PST.BLOWN){
        p.x+=p.vx*0.016;p.y+=p.vy*0.016;p.vy+=300*0.016;
        $a(p.el,{x:p.x-C.pw/2,y:p.y,opacity:Math.max(0,1-blowT*1.5)});
      }
    });
    if(blowT<1)requestAnimationFrame(blowAnim);
    else S.pillars.filter(p=>p.state===PST.BLOWN).forEach(p=>$a(p.el,{opacity:0}));
  }
  blowAnim();
  // 吹气箭头
  for(let i=0;i<8;i++){
    const bx=C.ax+50+Math.random()*(C.aw-100);
    const ba=$el('line',{x1:bx,y1:C.my-40,x2:bx,y2:C.my-5,
      stroke:'#5a9aca','stroke-width':1.5,'marker-end':'url(#arrC)',opacity:0.8},gFx);
    setTimeout(()=>{ba.remove();},800);
  }
}

/* ── 标注 ── */
function drawAnnotations(phase){
  clearG(gAnno);
  if(phase<1)return;
  const g=gAnno;
  // 45° 收敛角标注
  if(phase>=1){
    const cx=C.hx[4];
    const ftop=C.my+C.mTop;
    // 角度弧线
    $el('path',{d:`M${cx-C.ftW/2+8},${C.my+2} A20,20 0 0,1 ${cx-C.ftW/2+14},${C.my+14}`,
      fill:'none',stroke:'#f0b060','stroke-width':1},g);
    $t('45°',{x:cx-C.ftW/2-8,y:C.my+18,fill:'#f0b060','font-size':9,'text-anchor':'end','font-family':'JetBrains Mono,monospace'},g);
  }
  if(phase>=2){
    // 真空度
    const vg=document.getElementById('vacInd');
    if(vg)$a(vg,{opacity:1});
  }
  if(phase>=4){
    // IFR提示
    $t('IFR: 自组织入孔',{x:510,y:C.cy+C.ch+55,fill:'#20d088','font-size':11,'text-anchor':'middle',
      'font-family':'Rajdhani, sans-serif','font-weight':600,filter:'url(#gGreen)',opacity:0.9},g);
    $t('无需逐根机械对准',{x:510,y:C.cy+C.ch+72,fill:'#5a8a6a','font-size':9,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},g);
  }
}

/* ── 放大视图 ── */
function drawZoomInset(){
  clearG(gZoom);
  if(S.phase<4)return;
  const g=gZoom;
  const zx=830,zy=30,zw=170,zh=150;
  // 边框
  $el('rect',{x:zx,y:zy,width:zw,height:zh,fill:'rgba(8,14,24,0.85)',stroke:'#2a4a6a','stroke-width':1,rx:4},g);
  $t('局部放大 ×40',{x:zx+zw/2,y:zy+14,fill:'#00cce0','font-size':9,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},g);
  // 放大的漏斗底部→玻璃孔过渡
  const ccx=zx+zw/2;
  const ftopY=zy+30;
  const fbotY=zy+80;
  const gbotY=zy+120;
  // 漏斗壁
  $el('line',{x1:ccx-40,y1:ftopY,x2:ccx-10,y2:fbotY,stroke:'#5a8ab8','stroke-width':1.5},g);
  $el('line',{x1:ccx+40,y1:ftopY,x2:ccx+10,y2:fbotY,stroke:'#5a8ab8','stroke-width':1.5},g);
  // 通道壁
  $el('line',{x1:ccx-10,y1:fbotY,x2:ccx-10,y2:gbotY-20,stroke:'#4a7a9a','stroke-width':1},g);
  $el('line',{x1:ccx+10,y1:fbotY,x2:ccx+10,y2:gbotY-20,stroke:'#4a7a9a','stroke-width':1},g);
  // 玻璃板
  $el('rect',{x:ccx-45,y:gbotY-20,width:90,height:24,fill:'rgba(130,190,250,0.12)',stroke:'#4a6a8a','stroke-width':0.8},g);
  // 玻璃孔
  $el('rect',{x:ccx-8,y:gbotY-20,width:16,height:24,fill:'#060c14'},g);
  // 关键尺寸标注
  $t('Φ0.13',{x:ccx+18,y:fbotY+4,fill:'#f0b060','font-size':8,'font-family':'JetBrains Mono,monospace'},g);
  $t('Φ0.12',{x:ccx+16,y:gbotY-6,fill:'#f04848','font-size':8,'font-family':'JetBrains Mono,monospace'},g);
  // 缩径处红色警告
  $el('rect',{x:ccx-12,y:gbotY-24,width:24,height:6,fill:'none',stroke:'#f04848','stroke-width':1,'stroke-dasharray':'2,2',rx:1},g);
  $t('架桥风险',{x:ccx,y:gbotY+14,fill:'#f04848','font-size':7,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},g);
  // 铜柱(如果已入孔)
  const seatedCount=S.pillars.filter(p=>p.seated).length;
  if(seatedCount>0){
    $el('rect',{x:ccx-5,y:gbotY-18,width:10,height:16,rx:1.5,fill:'url(#gcopper)',stroke:'#c07020','stroke-width':0.5},g);
    $t('✓',{x:ccx,y:gbotY+28,fill:'#20d088','font-size':10,'text-anchor':'middle','font-weight':700},g);
  }
  // 连接线(从主图到放大图)
  const srcCx=C.hx[2],srcFbot=C.my+C.mTop+C.mFunnel;
  $el('line',{x1:srcCx+30,y1:srcFbot,x2:zx,y2:zy+zh/2,stroke:'#2a4a6a','stroke-width':0.5,'stroke-dasharray':'3,3'},g);
}

/* ── 清理辅助 ── */
function clearG(g){while(g.firstChild)g.removeChild(g.firstChild)}

/* ── 阶段管理 ── */
function enterPhase(n){
  const prev=S.phase;
  S.phase=n;S.pt=0;
  updatePhaseBar();
  document.getElementById('pLbl').textContent=phaseNames[n];
  document.getElementById('info').innerHTML=phaseInfos[n];

  if(n===1){
    drawMask();
    // 掩膜板从上方滑入动画
    if(S.maskG){
      $a(S.maskG,{transform:'translate(0,-180)',opacity:0});
      let t0=null;
      function slideIn(ts){
        if(!t0)t0=ts;
        const p=clamp((ts-t0)/1200,0,1);
        const ease=1-Math.pow(1-p,3);
        $a(S.maskG,{transform:`translate(0,${lerp(-180,0,ease)})`,opacity:ease});
        if(p<1)requestAnimationFrame(slideIn);
        else drawAnnotations(n);
      }
      requestAnimationFrame(slideIn);
    }
  }
  if(n===2){
    createAirflow();
    document.getElementById('vacInd')&&$a(document.getElementById('vacInd'),{opacity:1});
    drawAnnotations(n);
  }
  if(n===3){
    createPillars();
    drawAnnotations(n);
  }
  if(n===4){
    createUsonic();
    // 将目标铜柱设为ATTRACT状态
    S.pillars.forEach(p=>{
      if(p.isTarget&&p.state===PST.ON_MASK){
        p.state=PST.ATTRACT;
        // 错开时间
        p.vx=(C.hx[p.targetHole]-p.x)*0.3;
      }
    });
    drawAnnotations(n);
  }
  if(n===5){
    // 关闭超声波
    clearG(gUsonic);
    if(S.maskG)$a(S.maskG,{transform:''});
    // 吹气
    blowPillars();
    drawAnnotations(n);
    drawZoomInset();
  }
  if(n===6){
    // 掩膜板升起
    if(S.maskG){
      let t0=null;
      function slideUp(ts){
        if(!t0)t0=ts;
        const p=clamp((ts-t0)/1500,0,1);
        const ease=p*p*p;
        $a(S.maskG,{transform:`translate(0,${lerp(0,-200,ease)})`,opacity:1-ease*0.5});
        if(p<1)requestAnimationFrame(slideUp);
        else{$a(S.maskG,{opacity:0});}
      }
      requestAnimationFrame(slideUp);
    }
    // 隐藏气流
    S.airEls.forEach(a=>$a(a.el,{opacity:0}));
    clearG(gZoom);
    drawAnnotations(n);
    // 最终成功标注
    $t('装配完成 — 铜柱全部精准就位',{x:510,y:C.gy+C.gh+30,fill:'#20d088','font-size':13,'text-anchor':'middle',
      'font-family':'Rajdhani, sans-serif','font-weight':700,filter:'url(#gGreen)'},gFx);
  }
}

function nextPhase(){
  if(S.phase>=6)return;
  enterPhase(S.phase+1);
}
function prevPhase(){
  if(S.phase<=0)return;
  // 简化:重置到上一阶段
  resetToPhase(S.phase-1);
}
function resetToPhase(n){
  clearG(gFx);clearG(gAnno);clearG(gZoom);clearG(gUsonic);clearG(gAirflow);clearG(gPillars);
  if(S.maskG){clearG(S.maskG);S.maskG=null;}
  S.pillars=[];S.airEls=[];S.usEls=[];
  document.getElementById('vacInd')&&$a(document.getElementById('vacInd'),{opacity:0});
  S.phase=0;S.pt=0;
  if(n>0)enterPhase(1);
  if(n>1)enterPhase(2);
  if(n>2){enterPhase(3);
    // 立即完成散布
    S.pillars.forEach(p=>{p.y=C.my-C.ph;p.vy=0;p.state=PST.ON_MASK;$a(p.el,{opacity:1,x:p.x-C.pw/2,y:p.y});});
  }
  if(n>3){enterPhase(4);S.pt=5;}
  if(n>4)enterPhase(5);
  if(n>5)enterPhase(6);
  S.phase=n;S.pt=0;
  updatePhaseBar();
  document.getElementById('pLbl').textContent=phaseNames[n];
  document.getElementById('info').innerHTML=phaseInfos[n];
}

function resetAnim(){
  clearG(gFx);clearG(gAnno);clearG(gZoom);clearG(gUsonic);clearG(gAirflow);clearG(gPillars);
  if(S.maskG){clearG(S.maskG);S.maskG=null;}
  S.pillars=[];S.airEls=[];S.usEls=[];S.phase=0;S.pt=0;S.gt=0;
  document.getElementById('vacInd')&&$a(document.getElementById('vacInd'),{opacity:0});
  document.getElementById('pLbl').textContent=phaseNames[0];
  document.getElementById('info').innerHTML=phaseInfos[0];
  updatePhaseBar();
  stopAuto();
}

function updatePhaseBar(){
  const bars=document.querySelectorAll('.pst');
  bars.forEach((b,i)=>{
    b.className='pst';
    if(i+1<S.phase)b.classList.add('done');
    else if(i+1===S.phase)b.classList.add('active');
  });
}

/* ── 自动播放 ── */
function toggleAuto(){
  if(S.auto)stopAuto();
  else startAuto();
}
function startAuto(){
  S.auto=true;
  document.getElementById('bAuto').textContent='暂停';
  autoStep();
}
function stopAuto(){
  S.auto=false;
  document.getElementById('bAuto').textContent='自动播放';
  clearTimeout(S.atimer);
}
function autoStep(){
  if(!S.auto||S.phase>=6)return;
  nextPhase();
  const delays=[0,2500,2000,3000,9000,3000,2500];
  S.atimer=setTimeout(autoStep,delays[S.phase]||3000);
}

/* ── 主循环 ── */
let lastT=0;
function loop(ts){
  const dt=Math.min((ts-lastT)/1000,0.05);
  lastT=ts;S.gt+=dt;S.pt+=dt;

  // Phase 2: 气流动画
  if(S.phase===2)showAirflow(S.pt);
  if(S.phase>=3&&S.airEls.length)showAirflow(1);

  // Phase 3: 铜柱散布
  if(S.phase===3)updatePillars(dt);

  // Phase 4: 超声波+铜柱寻孔
  if(S.phase===4){
    showUsonic(clamp(S.pt/1.5,0,1));
    updatePillars(dt);
    drawZoomInset();
  }

  // Phase 5+: 已入孔铜柱保持
  if(S.phase>=5){
    S.pillars.filter(p=>p.seated).forEach(p=>{
      const hx=C.hx[p.targetHole];
      $a(p.el,{x:hx-C.pw/2,y:C.gy+C.gh-C.ph-2,opacity:1,fill:'url(#gcopper)',filter:'none'});
    });
    if(S.phase>=6){
      // 最终状态:铜柱在玻璃孔中可见
      S.pillars.filter(p=>p.seated).forEach(p=>{
        const hx=C.hx[p.targetHole];
        $a(p.el,{x:hx-C.pw/2,y:C.gy+2,opacity:1});
      });
    }
  }

  requestAnimationFrame(loop);
}

/* ── 初始化 ── */
function init(){
  // 创建阶段条
  const pb=document.getElementById('pBar');
  for(let i=1;i<=6;i++){
    const d=document.createElement('div');
    d.className='pst';d.dataset.phase=i;
    pb.appendChild(d);
  }

  drawChamber();
  drawGlass();

  // 初始标注
  $t('Φ0.12mm 微孔',{x:C.hx[2],y:C.gy+C.gh+16,fill:'#5a8aaa','font-size':9,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},gAnno);

  // 绑定事件
  document.getElementById('bNext').onclick=nextPhase;
  document.getElementById('bPrev').onclick=prevPhase;
  document.getElementById('bAuto').onclick=toggleAuto;
  document.getElementById('bReset').onclick=resetAnim;
  document.getElementById('sFreq').oninput=function(){
    S.freq=+this.value;
    document.getElementById('vFreq').textContent=S.freq+' kHz';
  };
  document.getElementById('sVac').oninput=function(){
    S.vac=+this.value;
    document.getElementById('vVac').textContent='-0.'+S.vac.toFixed(0)+' atm';
  };

  requestAnimationFrame(loop);
}

init();
})();
</script>
</body>
</html>

这段代码实现了一个完整的分步原理动画,包含以下核心特性:

视觉设计

  • 深色工程蓝图风格背景,带微妙网格纹理
  • 铜柱采用金属渐变填充,掩膜板用蓝色调区分,真空气流为青色,超声波为紫色
  • 每个关键力/效应都有独立的颜色编码与辉光滤镜

六步动画时序

  1. 贴合掩膜板 — 从上方滑入并贴合,带缓动动画
  2. 开启真空 — 气流箭头出现并持续流动,显示真空度
  3. 撒布铜柱 — 30个铜柱受重力散落在掩膜板表面
  4. 超声波振动 — 核心阶段:目标铜柱在声流效应+漏斗引导+真空牵引下自组织入孔,两侧波纹可视化振动,掩膜板微振
  5. 吹气清理 — 未入孔铜柱被吹走,已入孔铜柱保留
  6. 移除掩膜板 — 掩膜板升起,露出精准就位的铜柱

IFR 思想体现

  • 直接展示最终理想解:铜柱"自己找到"正确位置,无需逐根机械对准
  • 右下角局部放大视图(×40)清晰展示 0.13→0.12mm 缩径处的"架桥"风险
  • 45° 收敛角、Φ0.5→Φ0.13 漏斗扩大效果均有尺寸标注

交互控制

  • 上一步/下一步按钮逐步推进
  • 自动播放模式
  • 超声频率滑块(20–40 kHz)实时影响振动幅度与速度
  • 真空度滑块影响铜柱吸引力强度
积分规则:第一轮对话扣减6分,后续每轮扣4分