<!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 · 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>
这段代码实现了一个完整的分步原理动画,包含以下核心特性:
视觉设计
- 深色工程蓝图风格背景,带微妙网格纹理
- 铜柱采用金属渐变填充,掩膜板用蓝色调区分,真空气流为青色,超声波为紫色
- 每个关键力/效应都有独立的颜色编码与辉光滤镜
六步动画时序
- 贴合掩膜板 — 从上方滑入并贴合,带缓动动画
- 开启真空 — 气流箭头出现并持续流动,显示真空度
- 撒布铜柱 — 30个铜柱受重力散落在掩膜板表面
- 超声波振动 — 核心阶段:目标铜柱在声流效应+漏斗引导+真空牵引下自组织入孔,两侧波纹可视化振动,掩膜板微振
- 吹气清理 — 未入孔铜柱被吹走,已入孔铜柱保留
- 移除掩膜板 — 掩膜板升起,露出精准就位的铜柱
IFR 思想体现
- 直接展示最终理想解:铜柱"自己找到"正确位置,无需逐根机械对准
- 右下角局部放大视图(×40)清晰展示 0.13→0.12mm 缩径处的"架桥"风险
- 45° 收敛角、Φ0.5→Φ0.13 漏斗扩大效果均有尺寸标注
交互控制
- 上一步/下一步按钮逐步推进
- 自动播放模式
- 超声频率滑块(20–40 kHz)实时影响振动幅度与速度
- 真空度滑块影响铜柱吸引力强度
积分规则:第一轮对话扣减6分,后续每轮扣4分
等待动画代码生成...
