独立渲染引擎就绪引擎就绪
<!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)实时影响振动幅度与速度
- 真空度滑块影响铜柱吸引力强度
<!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 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;--sf:#0d1422;--el:#162034;--bd:#1e3050;--tx:#d0dcea;--dm:#5a7090;
--copper:#e89040;--cyan:#00cce0;--purple:#9068f8;--green:#20d088;--red:#f04848;--amber:#f0b060}
html,body{width:100%;height:100%;background:var(--bg);color:var(--tx);
font-family:'JetBrains Mono',monospace;overflow-x:hidden}
body{display:flex;flex-direction:column;align-items:center;min-height:100vh;padding:16px 10px 28px}
.hdr{text-align:center;margin-bottom:12px}
.hdr h1{font-family:'Rajdhani',sans-serif;font-weight:700;font-size:clamp(18px,2.8vw,28px);
letter-spacing:3px;color:var(--cyan);text-transform:uppercase}
.hdr p{font-size:10px;color:var(--dm);margin-top:2px;letter-spacing:1px}
.wrap{width:100%;max-width:1100px;display:flex;flex-direction:column;gap:10px}
.pbar{display:flex;gap:3px}
.ps{flex:1;height:5px;border-radius:3px;background:var(--bd);transition:background .4s,box-shadow .4s}
.ps.on{background:var(--cyan);box-shadow:0 0 8px rgba(0,204,224,.35)}
.ps.dn{background:var(--green)}
.sbox{width:100%;background:var(--sf);border:1px solid var(--bd);border-radius:10px;overflow:hidden}
.sbox svg{display:block;width:100%;height:auto}
.ctrl{display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:12px 16px;
background:var(--sf);border:1px solid var(--bd);border-radius:10px}
.btn{font-family:'Rajdhani',sans-serif;font-weight:600;font-size:12px;padding:6px 14px;
border:1px solid var(--bd);border-radius:5px;background:var(--el);color:var(--tx);
cursor:pointer;transition:all .12s;letter-spacing:1px;text-transform:uppercase;user-select:none}
.btn:hover{background:var(--bd)}.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:5px}
.sld label{font-size:9px;color:var(--dm);white-space:nowrap}
.sld input[type=range]{-webkit-appearance:none;width:80px;height:3px;background:var(--bd);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:9px;color:var(--cyan);min-width:48px}
.plbl{font-family:'Rajdhani',sans-serif;font-size:14px;font-weight:600;color:var(--amber);letter-spacing:1px;margin-left:auto}
.info{padding:12px 16px;background:var(--sf);border:1px solid var(--bd);border-radius:10px;
font-size:11px;line-height:1.8;color:var(--dm)}
.info .hl{color:var(--cyan);font-weight:500}.info .wr{color:var(--red)}.info .ok{color:var(--green)}
.sep{width:1px;height:20px;background:var(--bd);margin:0 2px}
</style>
</head>
<body>
<header class="hdr">
<h1>漏斗状微孔导引 · 超声波真空贴合</h1>
<p>Funnel-Guided Micro-Hole Alignment & Ultrasonic Vacuum Bonding</p>
</header>
<div class="wrap">
<div class="pbar" id="pBar"></div>
<div class="sbox"><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';
var NS='http://www.w3.org/2000/svg';
var svg=document.getElementById('svg');
/* 工具 */
function ce(tag,attr,parent){
var e=document.createElementNS(NS,tag);
if(attr)for(var k in attr)if(attr.hasOwnProperty(k))e.setAttribute(k,attr[k]);
if(parent)parent.appendChild(e);
return e;
}
function sa(e,attr){for(var k in attr)if(attr.hasOwnProperty(k))e.setAttribute(k,attr[k]);}
function ct(txt,attr,parent){var e=ce('text',attr,parent);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));}
function easeOut(t){return 1-Math.pow(1-t,3);}
function easeIn(t){return t*t*t;}
/* ── 配置 ── */
var AX=190, AW=640, AR=830;
var HOLES=5, HX=[254,382,510,638,766];
var MY=180, MH=105, M_TOP=28, M_FUN=58, M_CH=19;
var FTW=56, FBW=13;
var GY=285, GH=38, GHW=10;
var CY=323, CH=160;
var PW=8, PH=12;
var PW_SCALED=7; // 玻璃孔中显示的铜柱宽度
/* ── 阶段数据 ── */
var PH_NAMES=['准备就绪','贴合掩膜板','开启真空','撒布铜柱','超声波振动','吹气清理','移除掩膜板'];
var PH_INFOS=[
'点击 <span class="hl">下一步</span> 开始动画。本动画展示漏斗导引掩膜板 + 超声波真空贴合的核心原理:漏斗将 0.12mm 靶区扩大至 0.5mm(对准难度降低 90%),超声波克服摩擦与静电,真空场提供穿透驱动力。',
'将漏斗状导引掩膜板精准贴合在玻璃板上方。掩膜板孔径<span class="hl">上大下小</span>(Φ0.5mm → Φ0.13mm),与玻璃微孔一一对应,收敛角 45° 形成倒角漏斗。',
'启动底部真空腔,真空度 <span class="hl">-0.5 atm</span>,产生持续向下的气流牵引力,为铜柱提供穿透驱动力。',
'将大量铜柱散布在掩膜板表面。铜柱随机落在各处,等待导引。',
'开启超声波振动(<span class="hl">28kHz / 5μm</span>),铜柱在声流效应与漏斗斜面引导下<span class="hl">自动寻孔滑入</span>,在真空吸力下穿过掩膜板落入玻璃孔——IFR 的核心时刻。',
'关闭超声波,吹气清理表面未入孔铜柱。已入孔铜柱在真空吸力下稳固保留。',
'移除掩膜板,铜柱已精准就位。<span class="ok">对准难度降低 90%,无需逐根机械插入。</span> 注意:<span class="wr">0.13→0.12mm 缩径处有架桥卡死风险</span>,需确保铜柱无毛刺。'
];
/* ── 状态 ── */
var phase=0, phaseTime=0, gTime=0;
var autoMode=false, autoTimer=null;
var freq=28, vac=50;
/* ── SVG 分层 ── */
var gDefs=ce('defs',null,svg);
var gBg=ce('g',{id:'bg'},svg);
var gChamber=ce('g',{id:'chamber'},svg);
var gGlass=ce('g',{id:'glass'},svg);
var gMask=ce('g',{id:'mask',opacity:0},svg);
var gAir=ce('g',{id:'air'},svg);
var gPillar=ce('g',{id:'pillar'},svg);
var gUS=ce('g',{id:'us'},svg);
var gAnno=ce('g',{id:'anno'},svg);
var gZoom=ce('g',{id:'zoom'},svg);
var gFx=ce('g',{id:'fx'},svg);
/* ── 渐变和滤镜 ── */
// 铜柱渐变
var gCopper=ce('linearGradient',{id:'gCopper',x1:0,y1:0,x2:1,y2:1},gDefs);
ce('stop',{offset:'0%','stop-color':'#f4b868'},gCopper);
ce('stop',{offset:'50%','stop-color':'#e89040'},gCopper);
ce('stop',{offset:'100%','stop-color':'#b06010'},gCopper);
// 掩膜板渐变
var gMaskG=ce('linearGradient',{id:'gMaskG',x1:0,y1:0,x2:0,y2:1},gDefs);
ce('stop',{offset:'0%','stop-color':'#3a5878'},gMaskG);
ce('stop',{offset:'100%','stop-color':'#1c3450'},gMaskG);
// 玻璃渐变
var gGlassG=ce('linearGradient',{id:'gGlassG',x1:0,y1:0,x2:0,y2:1},gDefs);
ce('stop',{offset:'0%','stop-color':'rgba(120,180,240,0.18)'},gGlassG);
ce('stop',{offset:'100%','stop-color':'rgba(100,160,220,0.06)'},gGlassG);
// 真空腔渐变
var gChamG=ce('linearGradient',{id:'gChamG',x1:0,y1:0,x2:0,y2:1},gDefs);
ce('stop',{offset:'0%','stop-color':'#0c1822'},gChamG);
ce('stop',{offset:'100%','stop-color':'#060c14'},gChamG);
// 辉光滤镜
function mkGlow(id,color,sd){
var f=ce('filter',{id:id,x:'-80%',y:'-80%',width:'260%',height:'260%'},gDefs);
ce('feGaussianBlur',{in:'SourceGraphic',stdDeviation:String(sd),result:'blur'},f);
ce('feFlood',{'flood-color':color,'flood-opacity':'0.5',result:'color'},f);
ce('feComposite',{in:'color',in2:'blur',operator:'in',result:'glow'},f);
var m=ce('feMerge',null,f);
ce('feMergeNode',{in:'glow'},m);
ce('feMergeNode',{in:'SourceGraphic'},m);
}
mkGlow('glowCyan','#00cce0',4);
mkGlow('glowPurple','#9068f8',3);
mkGlow('glowGreen','#20d088',6);
mkGlow('glowAmber','#f0b060',3);
// 箭头标记
var mArr=ce('marker',{id:'arrC',viewBox:'0 0 10 6',refX:10,refY:3,markerWidth:7,markerHeight:5,orient:'auto'},gDefs);
ce('path',{d:'M0,0 L10,3 L0,6 Z',fill:'#00cce0'},mArr);
// 网格背景
var gridP=ce('pattern',{id:'gridP',width:40,height:40,patternUnits:'userSpaceOnUse'},gDefs);
ce('path',{d:'M40,0 L0,0 0,40',fill:'none',stroke:'#14203a','stroke-width':0.5},gridP);
/* ── 绘制静态场景 ── */
// 背景
ce('rect',{width:1020,height:720,fill:'#080e18'},gBg);
ce('rect',{width:1020,height:720,fill:'url(#gridP)',opacity:0.5},gBg);
// 真空腔
ce('rect',{x:AX+30,y:CY,width:AW-60,height:CH,fill:'url(#gChamG)',stroke:'#2a3a52','stroke-width':1.5,rx:6},gChamber);
ce('rect',{x:AX+50,y:CY-5,width:AW-100,height:10,fill:'#162838',stroke:'#2a4058','stroke-width':0.8,rx:2},gChamber);
var pcx=AX+AW/2;
ce('rect',{x:pcx-14,y:CY+CH-4,width:28,height:22,fill:'#12202e',stroke:'#2a3a52','stroke-width':1,rx:3},gChamber);
ct('真空抽气口',{x:pcx,y:CY+CH+28,fill:'#4a6a8a','font-size':9,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},gChamber);
ct('真空腔',{x:AX-8,y:CY+CH/2+4,fill:'#4a6a8a','font-size':10,'text-anchor':'end','font-family':'JetBrains Mono,monospace'},gChamber);
var vacLabel=ct('-0.5 atm',{x:pcx,y:CY+CH/2+4,fill:'#00cce0','font-size':12,'text-anchor':'middle','font-weight':500,'font-family':'JetBrains Mono,monospace',opacity:0},gChamber);
// 玻璃板
ce('rect',{x:AX,y:GY,width:AW,height:GH,fill:'url(#gGlassG)',stroke:'#4a6a8a','stroke-width':1.5,rx:2},gGlass);
for(var i=0;i<HOLES;i++){
ce('rect',{x:HX[i]-GHW/2,y:GY,width:GHW,height:GH,fill:'#060c14',stroke:'#3a5a7a','stroke-width':0.4},gGlass);
}
ct('玻璃板',{x:AX-8,y:GY+GH/2+4,fill:'#5a8aaa','font-size':10,'text-anchor':'end','font-family':'JetBrains Mono,monospace'},gGlass);
ct('Φ0.12',{x:HX[2],y:GY+GH+14,fill:'#5a8aaa','font-size':8,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},gGlass);
/* ── 绘制掩膜板(初始隐藏) ── */
function drawMask(){
// 主体
ce('rect',{x:AX,y:MY,width:AW,height:MH,fill:'url(#gMaskG)',stroke:'#4a6a8a','stroke-width':1.5,rx:2},gMask);
// 漏斗孔
for(var i=0;i<HOLES;i++){
var cx=HX[i];
var ft=MY+M_TOP, fb=ft+M_FUN, cb=fb+M_CH;
// 内部空间
ce('path',{d:'M'+(cx-FTW/2)+','+MY+' L'+(cx-FBW/2)+','+fb+' L'+(cx-FBW/2)+','+cb+' L'+(cx+FBW/2)+','+cb+' L'+(cx+FBW/2)+','+fb+' L'+(cx+FTW/2)+','+MY+' Z',
fill:'#080e18'},gMask);
// 左壁
ce('line',{x1:cx-FTW/2,y1:MY,x2:cx-FBW/2,y2:fb,stroke:'#5a8ab8','stroke-width':1.2},gMask);
// 右壁
ce('line',{x1:cx+FTW/2,y1:MY,x2:cx+FBW/2,y2:fb,stroke:'#5a8ab8','stroke-width':1.2},gMask);
// 通道壁
ce('line',{x1:cx-FBW/2,y1:fb,x2:cx-FBW/2,y2:cb,stroke:'#4a7a9a','stroke-width':0.7},gMask);
ce('line',{x1:cx+FBW/2,y1:fb,x2:cx+FBW/2,y2:cb,stroke:'#4a7a9a','stroke-width':0.7},gMask);
}
// 标注
ct('导引掩膜板',{x:AX-8,y:MY+MH/2+4,fill:'#6a9ac0','font-size':10,'text-anchor':'end','font-family':'JetBrains Mono,monospace'},gMask);
ct('Φ0.5',{x:HX[0],y:MY-6,fill:'#00cce0','font-size':8,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},gMask);
ct('Φ0.13',{x:HX[0],y:MY+M_TOP+M_FUN+M_CH+12,fill:'#00cce0','font-size':8,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},gMask);
// 45°角标注(最右漏斗)
var rc=HX[4], rft=MY+M_TOP;
ce('path',{d:'M'+(rc-FTW/2+6)+','+(MY+2)+' A16,16 0 0,1 '+(rc-FTW/2+10)+','+(MY+14),fill:'none',stroke:'#f0b060','stroke-width':1},gMask);
ct('45°',{x:rc-FTW/2-6,y:MY+16,fill:'#f0b060','font-size':8,'text-anchor':'end','font-family':'JetBrains Mono,monospace'},gMask);
}
/* ── 铜柱数据 ── */
var pillars=[];
function initPillars(){
// 清空
while(gPillar.firstChild)gPillar.removeChild(gPillar.firstChild);
pillars=[];
// 5个目标铜柱
for(var i=0;i<HOLES;i++){
var startX=HX[i]+(Math.random()-0.5)*90;
var p={
el:null, x:startX, y:20+Math.random()*30,
holeIdx:i, isTarget:true,
state:'wait', // wait, fall, onmask, vibrate, funnel, fallhole, seated, blown
stateStart:0, vx:0, vy:0, phase:Math.random()*6.28
};
p.el=ce('rect',{width:PW,height:PH,rx:2,fill:'url(#gCopper)',stroke:'#c07020','stroke-width':0.5,opacity:0},gPillar);
pillars.push(p);
}
// 15个非目标铜柱
for(var i=0;i<15;i++){
var startX=AX+40+Math.random()*(AW-80);
var p={
el:null, x:startX, y:10+Math.random()*40,
holeIdx:-1, isTarget:false,
state:'wait',
stateStart:0, vx:(Math.random()-0.5)*30, vy:Math.random()*40, phase:Math.random()*6.28
};
p.el=ce('rect',{width:PW,height:PH,rx:2,fill:'url(#gCopper)',stroke:'#c07020','stroke-width':0.5,opacity:0},gPillar);
pillars.push(p);
}
}
function updatePillarVisual(p){
sa(p.el,{x:p.x-PW/2, y:p.y, opacity:1});
}
/* ── 气流箭头 ── */
var airLines=[];
function createAir(){
while(gAir.firstChild)gAir.removeChild(gAir.firstChild);
airLines=[];
for(var h=0;h<HOLES;h++){
var cx=HX[h];
for(var j=0;j<3;j++){
var off=(j-1)*5;
var ln=ce('line',{
x1:cx+off, y1:MY+8, x2:cx+off*0.25, y2:GY,
stroke:'#00cce0','stroke-width':0.7,'stroke-dasharray':'4,6',
'marker-end':'url(#arrC)', opacity:0
},gAir);
airLines.push({el:ln,delay:j*0.25});
}
// 玻璃孔内
var ln2=ce('line',{
x1:cx,y1:GY,x2:cx,y2:CY+CH*0.4,
stroke:'#00cce0','stroke-width':0.9,'stroke-dasharray':'3,5',
'marker-end':'url(#arrC)',opacity:0
},gAir);
airLines.push({el:ln2,delay:0.3});
}
}
/* ── 超声波可视化 ── */
var usWaves=[];
function createUS(){
while(gUS.firstChild)gUS.removeChild(gUS.firstChild);
usWaves=[];
for(var side=0;side<2;side++){
var bx=side===0?AX-3:AR+3;
for(var i=0;i<5;i++){
var dir=side===0?-1:1;
var w=ce('path',{
d:'M'+bx+','+(MY+15+i*20)+' Q'+(bx+dir*14)+','+(MY+21+i*20)+' '+bx+','+(MY+27+i*20),
stroke:'#9068f8','stroke-width':1.5,fill:'none',opacity:0
},gUS);
usWaves.push({el:w,side:side,idx:i,dir:dir,bx:bx});
}
}
}
/* ── 放大视图 ── */
function drawZoom(){
while(gZoom.firstChild)gZoom.removeChild(gZoom.firstChild);
if(phase<4)return;
var zx=828, zy=30, zw=175, zh=148;
ce('rect',{x:zx,y:zy,width:zw,height:zh,fill:'rgba(8,14,24,0.88)',stroke:'#2a4a6a','stroke-width':1,rx:4},gZoom);
ct('局部放大 x40',{x:zx+zw/2,y:zy+13,fill:'#00cce0','font-size':8,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},gZoom);
var ccx=zx+zw/2;
var ftY=zy+28, fbY=zy+75, gbY=zy+115;
// 漏斗壁
ce('line',{x1:ccx-42,y1:ftY,x2:ccx-10,y2:fbY,stroke:'#5a8ab8','stroke-width':1.5},gZoom);
ce('line',{x1:ccx+42,y1:ftY,x2:ccx+10,y2:fbY,stroke:'#5a8ab8','stroke-width':1.5},gZoom);
ce('line',{x1:ccx-10,y1:fbY,x2:ccx-10,y2:gbY-18,stroke:'#4a7a9a','stroke-width':1},gZoom);
ce('line',{x1:ccx+10,y1:fbY,x2:ccx+10,y2:gbY-18,stroke:'#4a7a9a','stroke-width':1},gZoom);
// 玻璃
ce('rect',{x:ccx-48,y:gbY-18,width:96,height:22,fill:'rgba(120,180,240,0.1)',stroke:'#4a6a8a','stroke-width':0.7},gZoom);
ce('rect',{x:ccx-8,y:gbY-18,width:16,height:22,fill:'#060c14'},gZoom);
// 尺寸
ct('Φ0.13',{x:ccx+16,y:fbY+3,fill:'#f0b060','font-size':7,'font-family':'JetBrains Mono,monospace'},gZoom);
ct('Φ0.12',{x:ccx+14,y:gbY-6,fill:'#f04848','font-size':7,'font-family':'JetBrains Mono,monospace'},gZoom);
// 缩径警告
ce('rect',{x:ccx-12,y:gbY-22,width:24,height:6,fill:'none',stroke:'#f04848','stroke-width':0.8,'stroke-dasharray':'2,2',rx:1},gZoom);
ct('架桥风险',{x:ccx,y:gbY+12,fill:'#f04848','font-size':6,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},gZoom);
// 已入孔铜柱
var sc=0;
for(var i=0;i<pillars.length;i++)if(pillars[i].state==='seated')sc++;
if(sc>0){
ce('rect',{x:ccx-5,y:gbY-16,width:10,height:14,rx:1.5,fill:'url(#gCopper)',stroke:'#c07020','stroke-width':0.4},gZoom);
ct('OK',{x:ccx,y:gbY+24,fill:'#20d088','font-size':9,'text-anchor':'middle','font-weight':700},gZoom);
}
// 连接线
ce('line',{x1:HX[2]+28,y1:MY+M_TOP+M_FUN,x2:zx,y2:zy+zh/2,stroke:'#2a4a6a','stroke-width':0.4,'stroke-dasharray':'3,3'},gZoom);
}
/* ── IFR 标注 ── */
function drawIFR(){
if(phase<4)return;
ct('IFR: 自组织入孔 — 无需逐根对准',{x:510,y:CY+CH+52,fill:'#20d088','font-size':11,'text-anchor':'middle',
'font-family':'Rajdhani, sans-serif','font-weight':600,filter:'url(#glowGreen)'},gAnno);
}
/* ── 阶段进入 ── */
var maskSlideAnim=null;
function enterPhase(n){
if(n<0||n>6)return;
phase=n; phaseTime=0;
updateBar();
document.getElementById('pLbl').textContent=PH_NAMES[n];
document.getElementById('info').innerHTML=PH_INFOS[n];
if(n===1){
drawMask();
// 掩膜板滑入
sa(gMask,{opacity:1,transform:'translate(0,-200)'});
var t0=performance.now();
function slide(now){
var p=clamp((now-t0)/1200,0,1);
var e=easeOut(p);
sa(gMask,{transform:'translate(0,'+lerp(-200,0,e)+')',opacity:Math.min(1,e+0.2)});
if(p<1)maskSlideAnim=requestAnimationFrame(slide);
else{sa(gMask,{transform:''});drawIFR();}
}
maskSlideAnim=requestAnimationFrame(slide);
}
if(n===2){
createAir();
sa(vacLabel,{opacity:1});
}
if(n===3){
initPillars();
// 所有铜柱开始下落
for(var i=0;i<pillars.length;i++){
pillars[i].state='fall';
pillars[i].stateStart=gTime;
pillars[i].vy=50+Math.random()*100;
pillars[i].vx=(Math.random()-0.5)*30;
}
}
if(n===4){
createUS();
// 目标铜柱开始振动寻孔
for(var i=0;i<pillars.length;i++){
if(pillars[i].isTarget && pillars[i].state==='onmask'){
pillars[i].state='vibrate';
pillars[i].stateStart=gTime;
}
}
}
if(n===5){
// 关闭超声波视觉
while(gUS.firstChild)gUS.removeChild(gUS.firstChild);
sa(gMask,{transform:''});
// 吹走非目标铜柱
for(var i=0;i<pillars.length;i++){
var p=pillars[i];
if(p.state==='onmask'||p.state==='vibrate'||p.state==='wait'){
p.state='blown';
p.stateStart=gTime;
var dir=p.x<500?-1:1;
p.vx=dir*(250+Math.random()*200);
p.vy=-120-Math.random()*100;
}
}
// 目标铜柱继续入孔
for(var i=0;i<pillars.length;i++){
if(pillars[i].isTarget && (pillars[i].state==='vibrate'||pillars[i].state==='funnel')){
if(pillars[i].state==='vibrate'){
pillars[i].state='funnel';
pillars[i].stateStart=gTime;
pillars[i].x=HX[pillars[i].holeIdx];
}
}
}
drawZoom();
}
if(n===6){
// 掩膜板升起
var t0=performance.now();
function slideUp(now){
var p=clamp((now-t0)/1500,0,1);
var e=easeIn(p);
sa(gMask,{transform:'translate(0,'+lerp(0,-220,e)+')',opacity:Math.max(0,1-e*0.7)});
if(p<1)requestAnimationFrame(slideUp);
else sa(gMask,{opacity:0});
}
requestAnimationFrame(slideUp);
// 隐藏气流
for(var i=0;i<airLines.length;i++)sa(airLines[i].el,{opacity:0});
sa(vacLabel,{opacity:0});
// 清理放大视图
while(gZoom.firstChild)gZoom.removeChild(gZoom.firstChild);
// 清理IFR
while(gAnno.firstChild)gAnno.removeChild(gAnno.firstChild);
// 最终标注
ct('装配完成 — 铜柱全部精准就位',{x:510,y:GY+GH+28,fill:'#20d088','font-size':13,'text-anchor':'middle',
'font-family':'Rajdhani, sans-serif','font-weight':700,filter:'url(#glowGreen)'},gFx);
}
}
/* ── 主循环 ── */
var lastTs=0;
function tick(ts){
if(!lastTs)lastTs=ts;
var dt=Math.min((ts-lastTs)/1000,0.05);
lastTs=ts;
gTime+=dt;
phaseTime+=dt;
// 气流动画(phase >= 2)
if(phase>=2){
for(var i=0;i<airLines.length;i++){
var a=airLines[i];
var targetOp=phase>=2?0.6:0;
var curOp=parseFloat(a.el.getAttribute('opacity'))||0;
var newOp=phase>=3?0.6:clamp(phaseTime*2-a.delay,0,0.6);
if(phase===2)newOp=clamp(phaseTime*2-a.delay,0,0.6);
sa(a.el,{opacity:newOp,'stroke-dashoffset':-gTime*55});
}
}
// 超声波动画(phase === 4)
if(phase===4){
var usProg=clamp(phaseTime/1.5,0,1);
for(var i=0;i<usWaves.length;i++){
var w=usWaves[i];
var p2=clamp(usProg*2.5-w.idx*0.15,0,1);
var wave=Math.sin(gTime*freq*0.28+w.idx*1.5)*3.5;
var bx=w.bx+wave;
sa(w.el,{
opacity:p2*0.8,
d:'M'+bx+','+(MY+15+w.idx*20)+' Q'+(bx+w.dir*16)+','+(MY+21+w.idx*20)+' '+bx+','+(MY+27+w.idx*20)
});
}
// 掩膜板微振
var mdx=Math.sin(gTime*freq*0.28)*1.8;
var mdy=Math.cos(gTime*freq*0.2)*0.8;
sa(gMask,{transform:'translate('+mdx+','+mdy+')'});
}
// 铜柱更新
for(var i=0;i<pillars.length;i++){
var p=pillars[i];
var elapsed=gTime-p.stateStart;
if(p.state==='fall'){
p.vy+=600*dt;
p.x+=p.vx*dt;
p.y+=p.vy*dt;
var surfY=MY-PH;
if(p.y>=surfY && p.x>AX && p.x<AR){
p.y=surfY;p.vy=0;p.vx=0;p.state='onmask';p.stateStart=gTime;
}
if(p.y>720){p.state='blown';sa(p.el,{opacity:0});continue;}
updatePillarVisual(p);
}
else if(p.state==='onmask'){
p.y=MY-PH;
updatePillarVisual(p);
}
else if(p.state==='vibrate'){
// 向目标漏斗移动 + 振动
var tx=HX[p.holeIdx];
var dx=tx-p.x;
var dist=Math.abs(dx);
var speed=120*(freq/28);
var vacF=vac/50;
if(dist>2){
p.x+=Math.sign(dx)*Math.min(speed*dt,dist);
}
// 振动偏移
var vibX=Math.sin(gTime*freq*0.3+p.phase)*3*(freq/28);
var vibY=Math.cos(gTime*freq*0.25+p.phase)*1.5*(freq/28);
var drawX=p.x+vibX;
var drawY=MY-PH+vibY;
sa(p.el,{x:drawX-PW/2,y:drawY,opacity:1});
// 到达漏斗中心
if(dist<5){
p.state='funnel';p.stateStart=gTime;p.x=tx;
}
}
else if(p.state==='funnel'){
// 沿漏斗下滑
var prog=clamp(elapsed/3.0,0,1);
var ep=easeIn(prog);
var topY=MY+2;
var botY=MY+M_TOP+M_FUN+M_CH-PH+5;
p.y=lerp(topY,botY,ep);
p.x=HX[p.holeIdx]+Math.sin(gTime*8+p.phase)*0.5*(1-prog);
// 到达玻璃板
if(prog>=1){
p.state='fallhole';p.stateStart=gTime;
}
updatePillarVisual(p);
}
else if(p.state==='fallhole'){
var prog=clamp(elapsed/0.6,0,1);
p.y=lerp(GY+2,GY+GH-PH-2,easeOut(prog));
p.x=HX[p.holeIdx];
sa(p.el,{x:p.x-PW_SCALED/2,y:p.y,width:PW_SCALED,opacity:1});
if(prog>=1){
p.state='seated';p.stateStart=gTime;
// 闪光
sa(p.el,{fill:'#20d088',filter:'url(#glowGreen)'});
setTimeout((function(pp){return function(){sa(pp.el,{fill:'url(#gCopper)',filter:'none'});};})(p),500);
drawZoom();
}
}
else if(p.state==='seated'){
// 保持位置
}
else if(p.state==='blown'){
p.x+=p.vx*dt;
p.y+=p.vy*dt;
p.vy+=350*dt;
var op=Math.max(0,1-elapsed*2);
sa(p.el,{x:p.x-PW/2,y:p.y,opacity:op});
}
}
// Phase 4 入孔完成后自动进入下一阶段检查
if(phase===4){
var allSeated=true;
for(var i=0;i<pillars.length;i++){
if(pillars[i].isTarget && pillars[i].state!=='seated'){allSeated=false;break;}
}
if(allSeated && phaseTime>4){
drawZoom();
drawIFR();
}
}
requestAnimationFrame(tick);
}
/* ── UI 控制 ── */
function updateBar(){
var bars=document.querySelectorAll('.ps');
for(var i=0;i<bars.length;i++){
bars[i].className='ps';
if(i+1<phase)bars[i].classList.add('dn');
else if(i+1===phase)bars[i].classList.add('on');
}
}
function nextPhase(){
if(phase>=6)return;
enterPhase(phase+1);
}
function prevPhase(){
if(phase<=1)return;
// 简化:重置到指定阶段
fullReset();
// 快进到目标阶段
for(var i=1;i<phase;i++){
quickPhase(i);
}
enterPhase(phase);
}
function quickPhase(n){
if(n===1){
drawMask();
sa(gMask,{opacity:1,transform:''});
}
if(n===2){
createAir();
sa(vacLabel,{opacity:1});
for(var i=0;i<airLines.length;i++)sa(airLines[i].el,{opacity:0.6});
}
if(n===3){
initPillars();
for(var i=0;i<pillars.length;i++){
pillars[i].state='onmask';
pillars[i].y=MY-PH;
pillars[i].x=HX[i]||AX+40+Math.random()*(AW-80);
updatePillarVisual(pillars[i]);
}
}
if(n===4){
// 目标铜柱直接入孔
for(var i=0;i<pillars.length;i++){
if(pillars[i].isTarget){
pillars[i].state='seated';
pillars[i].x=HX[pillars[i].holeIdx];
pillars[i].y=GY+GH-PH-2;
sa(pillars[i].el,{x:pillars[i].x-PW_SCALED/2,y:pillars[i].y,width:PW_SCALED,opacity:1,fill:'url(#gCopper)',filter:'none'});
}
}
}
if(n===5){
// 清理非目标
for(var i=0;i<pillars.length;i++){
if(!pillars[i].isTarget){
pillars[i].state='blown';
sa(pillars[i].el,{opacity:0});
}
}
}
}
function fullReset(){
if(maskSlideAnim)cancelAnimationFrame(maskSlideAnim);
// 清理所有动态图层
while(gMask.firstChild)gMask.removeChild(gMask.firstChild);
sa(gMask,{opacity:0,transform:''});
while(gAir.firstChild)gAir.removeChild(gAir.firstChild);
while(gPillar.firstChild)gPillar.removeChild(gPillar.firstChild);
while(gUS.firstChild)gUS.removeChild(gUS.firstChild);
while(gAnno.firstChild)gAnno.removeChild(gAnno.firstChild);
while(gZoom.firstChild)gZoom.removeChild(gZoom.firstChild);
while(gFx.firstChild)gFx.removeChild(gFx.firstChild);
airLines=[];usWaves=[];pillars=[];
sa(vacLabel,{opacity:0});
phase=0;phaseTime=0;
updateBar();
document.getElementById('pLbl').textContent=PH_NAMES[0];
document.getElementById('info').innerHTML=PH_INFOS[0];
stopAuto();
}
function toggleAuto(){
if(autoMode)stopAuto();
else startAuto();
}
function startAuto(){
autoMode=true;
document.getElementById('bAuto').textContent='暂停';
autoStep();
}
function stopAuto(){
autoMode=false;
document.getElementById('bAuto').textContent='自动播放';
clearTimeout(autoTimer);
}
function autoStep(){
if(!autoMode||phase>=6)return;
nextPhase();
var delays=[0,2500,2000,3500,9000,3000,2500];
autoTimer=setTimeout(autoStep,delays[phase]||3000);
}
/* ── 初始化 ── */
function init(){
// 阶段条
var pb=document.getElementById('pBar');
for(var i=0;i<6;i++){
var d=document.createElement('div');
d.className='ps';
pb.appendChild(d);
}
// 按钮
document.getElementById('bNext').onclick=function(){
stopAuto();nextPhase();
};
document.getElementById('bPrev').onclick=function(){
stopAuto();if(phase<=1){fullReset();return;}fullReset();phase=1;for(var i=1;i<6;i++)quickPhase(i);enterPhase(1);
};
document.getElementById('bAuto').onclick=toggleAuto;
document.getElementById('bReset').onclick=fullReset;
document.getElementById('sFreq').oninput=function(){
freq=+this.value;
document.getElementById('vFreq').textContent=freq+' kHz';
};
document.getElementById('sVac').oninput=function(){
vac=+this.value;
document.getElementById('vVac').textContent='-0.'+Math.round(vac)+' atm';
};
requestAnimationFrame(tick);
}
init();
})();
</script>
</body>
</html>
这次我完全重写了动画代码,修复了以下关键问题:
修复内容:
- SVG 滤镜定义 — 重写了
feMerge的创建方式,确保两个feMergeNode正确嵌套在feMerge内 - 铜柱状态机 — 简化为清晰的状态流转:
wait → fall → onmask → vibrate → funnel → fallhole → seated,每个状态都有明确的进入条件和时间驱动 - 去除未定义变量 — 之前的
p.vibDx从未定义,导致运行时错误;现在振动偏移直接在渲染时计算 - 掩膜板绘制 — 漏斗孔的路径坐标经过重新计算,确保视觉上正确
动画流程(6步):
| 步骤 | 内容 | 视觉效果 |
|---|---|---|
| 1 | 贴合掩膜板 | 掩膜板从上方滑入,带缓动动画 |
| 2 | 开启真空 | 青色气流箭头出现并持续流动,显示真空度 |
| 3 | 撒布铜柱 | 20个铜柱从上方受重力落下散布在掩膜板上 |
| 4 | 超声波振动 | 5个目标铜柱振动移向漏斗中心→沿漏斗下滑→穿过玻璃孔入位(绿色闪光) |
| 5 | 吹气清理 | 非目标铜柱被吹飞,局部放大视图展示缩径风险 |
| 6 | 移除掩膜板 | 掩膜板升起,露出精准就位的铜柱 + 完成标注 |
交互控制: 超声频率滑块影响振动幅度与移速,真空度滑块影响吸引力强度,均实时生效。
<!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 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;--sf:#0d1422;--el:#162034;--bd:#1e3050;
--tx:#d8e2f0;--dm:#5a7090;--cu:#e89040;--cy:#00cce0;--pu:#9068f8;--gn:#20d088;--rd:#f04848}
html,body{width:100%;height:100%;background:var(--bg);color:var(--tx);
font-family:'JetBrains Mono',monospace;overflow-x:hidden}
body{display:flex;flex-direction:column;align-items:center;min-height:100vh;padding:16px 12px 28px}
.hd{text-align:center;margin-bottom:10px}
.hd h1{font-family:'Rajdhani',sans-serif;font-weight:700;font-size:clamp(18px,3vw,26px);
letter-spacing:3px;color:var(--cy);text-transform:uppercase}
.hd p{font-size:10px;color:var(--dm);margin-top:2px;letter-spacing:1px}
.wrp{width:100%;max-width:1140px;display:flex;flex-direction:column;gap:8px}
.pb{display:flex;gap:3px}
.pb i{flex:1;height:5px;border-radius:3px;background:var(--bd);transition:background .4s,box-shadow .4s;font-style:normal}
.pb i.on{background:var(--cy);box-shadow:0 0 8px rgba(0,204,224,.35)}
.pb i.ok{background:var(--gn)}
.sb{width:100%;background:var(--sf);border:1px solid var(--bd);border-radius:8px;overflow:hidden}
.sb svg{display:block;width:100%;height:auto}
.ct{display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:12px 16px;
background:var(--sf);border:1px solid var(--bd);border-radius:8px}
.bt{font-family:'Rajdhani',sans-serif;font-weight:600;font-size:12px;padding:6px 14px;
border:1px solid var(--bd);border-radius:4px;background:var(--el);color:var(--tx);
cursor:pointer;transition:all .12s;letter-spacing:1px;text-transform:uppercase}
.bt:hover{background:var(--bd)}.bt:active{transform:scale(.95)}
.bt.p{border-color:var(--cy);color:var(--cy)}.bt.p:hover{background:rgba(0,204,224,.1)}
.sl{display:flex;align-items:center;gap:5px}
.sl label{font-size:9px;color:var(--dm);white-space:nowrap}
.sl input[type=range]{-webkit-appearance:none;width:80px;height:3px;background:var(--bd);border-radius:2px;outline:none}
.sl input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:11px;height:11px;border-radius:50%;background:var(--cy);cursor:pointer}
.sl .v{font-size:9px;color:var(--cy);min-width:48px}
.pl{font-family:'Rajdhani',sans-serif;font-size:14px;font-weight:600;color:var(--cu);letter-spacing:1px;margin-left:auto}
.if{padding:12px 16px;background:var(--sf);border:1px solid var(--bd);border-radius:8px;
font-size:10.5px;line-height:1.75;color:var(--dm)}
.if .hl{color:var(--cy);font-weight:500}
.if .wr{color:var(--rd)}
.if .ok{color:var(--gn)}
.sp{width:1px;height:18px;background:var(--bd);margin:0 2px}
@keyframes dashFlow{from{stroke-dashoffset:0}to{stroke-dashoffset:-18}}
@keyframes pulse{0%,100%{opacity:.6}50%{opacity:1}}
</style>
</head>
<body>
<header class="hd">
<h1>漏斗状微孔导引 · 超声波真空贴合</h1>
<p>Funnel-Guided Micro-Hole Alignment · Ultrasonic Vacuum Bonding</p>
</header>
<div class="wrp">
<div class="pb" id="pBar"></div>
<div class="sb"><svg id="S" viewBox="0 0 1100 650" xmlns="http://www.w3.org/2000/svg"></svg></div>
<div class="ct">
<button class="bt p" id="bN">下一步 ▶</button>
<button class="bt" id="bA">自动播放</button>
<button class="bt" id="bR">重置</button>
<div class="sp"></div>
<div class="sl"><label>超声频率</label><input type="range" id="sF" min="20" max="40" value="28"><span class="v" id="vF">28 kHz</span></div>
<div class="sl"><label>真空度</label><input type="range" id="sV" min="10" max="90" value="50"><span class="v" id="vV">-0.50 atm</span></div>
<span class="pl" id="pL">准备就绪</span>
</div>
<div class="if" id="nI">点击 <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('S');
/* === 工具函数 === */
function E(t,a,p){const e=document.createElementNS(NS,t);if(a)for(const[k,v]of Object.entries(a))e.setAttribute(k,v);if(p)p.appendChild(e);return e}
function T(s,a,p){const e=E('text',a,p);e.textContent=s;return e}
function clr(g){while(g.firstChild)g.removeChild(g.firstChild);return g}
function lerp(a,b,t){return a+(b-a)*t}
function clamp(v,a,b){return Math.max(a,Math.min(b,v))}
function easeOutCubic(t){return 1-Math.pow(1-t,3)}
function easeInCubic(t){return t*t*t}
function easeInOutCubic(t){return t<.5?4*t*t*t:1-Math.pow(-2*t+2,3)/2}
function easeOutBounce(t){const n=7.5625,d=2.75;if(t<1/d)return n*t*t;if(t<2/d)return n*(t-=1.5/d)*t+.75;if(t<2.5/d)return n*(t-=2.25/d)*t+.9375;return n*(t-=2.625/d)*t+.984375}
/* === 配置 === */
const C={
W:1100,H:650,
ax:160,ar:840,aw:680,
holes:[260,380,500,620,740],
my:100,mh:105,
mTop:20,mFun:52,mChan:23,
fTW:56,fBW:13,
gy:205,gh:35,gHW:11,
cy:240,ch:160,
pw:9,ph:13,
totalPillars:25
};
/* === 状态 === */
const S={
phase:0,gt:0,
freq:28,vac:50,
pillars:[],airEls:[],usEls:[],
maskG:null,auto:false,autoTm:null
};
/* === SVG 结构组 === */
const gDefs=E('defs',null,svg);
const gBg=E('g',{id:'bg'},svg);
const gCham=E('g',{id:'cham'},svg);
const gGlass=E('g',{id:'glass'},svg);
const gMask=E('g',{id:'mask'},svg);
const gAir=E('g',{id:'air'},svg);
const gPil=E('g',{id:'pil'},svg);
const gUs=E('g',{id:'us'},svg);
const gAnno=E('g',{id:'anno'},svg);
const gZoom=E('g',{id:'zoom'},svg);
const gFx=E('g',{id:'fx'},svg);
/* === 滤镜和渐变 === */
function mkGlow(id,color,sd){
const f=E('filter',{id,x:'-80%',y:'-80%',width:'260%',height:'260%'},gDefs);
E('feGaussianBlur',{in:'SourceGraphic',stdDeviation:String(sd),result:'b'},f);
const cm=E('feColorMatrix',{in:'b',type:'matrix',values:'1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.6 0',result:'g'},f);
E('feMerge',null,f);
E('feMergeNode',{in:'g'},f);E('feMergeNode',{in:'SourceGraphic'},f);
}
mkGlow('gC','#00cce0',5);mkGlow('gP','#9068f8',4);mkGlow('gG','#20d088',6);mkGlow('gR','#f04848',5);
const gCu=E('linearGradient',{id:'gCu',x1:0,y1:0,x2:1,y2:1},gDefs);
E('stop',{offset:'0%','stop-color':'#f4b868'},gCu);
E('stop',{offset:'50%','stop-color':'#e89040'},gCu);
E('stop',{offset:'100%','stop-color':'#b86818'},gCu);
const gMa=E('linearGradient',{id:'gMa',x1:0,y1:0,x2:0,y2:1},gDefs);
E('stop',{offset:'0%','stop-color':'#3a5878'},gMa);
E('stop',{offset:'100%','stop-color':'#1c3450'},gMa);
const gGl=E('linearGradient',{id:'gGl',x1:0,y1:0,x2:0,y2:1},gDefs);
E('stop',{offset:'0%','stop-color':'rgba(120,180,240,0.16)'},gGl);
E('stop',{offset:'100%','stop-color':'rgba(90,150,210,0.05)'},gGl);
const gCh=E('linearGradient',{id:'gCh',x1:0,y1:0,x2:0,y2:1},gDefs);
E('stop',{offset:'0%','stop-color':'#0c1822'},gCh);
E('stop',{offset:'100%','stop-color':'#050a10'},gCh);
const aM=E('marker',{id:'aC',viewBox:'0 0 10 6',refX:9,refY:3,markerWidth:7,markerHeight:5,orient:'auto'},gDefs);
E('path',{d:'M0,0 L10,3 L0,6 Z',fill:'#00cce0'},aM);
/* === 绘制静态场景 === */
function drawBg(){
E('rect',{width:C.W,height:C.H,fill:'#070c16'},gBg);
const gp=E('pattern',{id:'gr',width:36,height:36,patternUnits:'userSpaceOnUse'},gDefs);
E('path',{d:'M36,0 L0,0 0,36',fill:'none',stroke:'#121e34','stroke-width':0.4},gp);
E('rect',{width:C.W,height:C.H,fill:'url(#gr)',opacity:0.5},gBg);
// 侧边标签区域装饰线
E('line',{x1:C.ax-20,y1:60,x2:C.ax-20,y2:420,stroke:'#1a2a42','stroke-width':1,'stroke-dasharray':'2,6'},gBg);
}
function drawChamber(){
const g=gCham;
E('rect',{x:C.ax+30,y:C.cy,width:C.aw-60,height:C.ch,fill:'url(#gCh)',stroke:'#2a3a52','stroke-width':1.5,rx:6},g);
// 顶部法兰
E('rect',{x:C.ax+40,y:C.cy-6,width:C.aw-80,height:12,fill:'#14222e',stroke:'#2a4058','stroke-width':0.8,rx:2},g);
// 底部抽气口
const px=C.ax+C.aw/2;
E('rect',{x:px-16,y:C.cy+C.ch-4,width:32,height:24,fill:'#0e1a26',stroke:'#2a3a52','stroke-width':1,rx:3},g);
// 抽气口内部
E('rect',{x:px-8,y:C.cy+C.ch+14,width:16,height:10,fill:'#080e16',stroke:'#1a2a3a','stroke-width':0.5,rx:2},g);
T('真空抽气口',{x:px,y:C.cy+C.ch+38,fill:'#3a5a7a','font-size':8.5,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},g);
// 真空度指示(初始隐藏)
const vi=E('g',{id:'vacInd',opacity:0},g);
T('-0.5 atm',{x:px,y:C.cy+C.ch/2+4,fill:'#00cce0','font-size':11,'text-anchor':'middle','font-weight':'500','font-family':'JetBrains Mono,monospace',filter:'url(#gC)'},vi);
// 腔体标签
T('真空腔',{x:C.ax-8,y:C.cy+C.ch/2+4,fill:'#3a5a7a','font-size':9,'text-anchor':'end','font-family':'JetBrains Mono,monospace'},g);
}
function drawGlass(){
const g=gGlass;
E('rect',{x:C.ax,y:C.gy,width:C.aw,height:C.gh,fill:'url(#gGl)',stroke:'#4a6a8a','stroke-width':1.5,rx:1},g);
C.holes.forEach(hx=>{
E('rect',{x:hx-C.gHW/2,y:C.gy,width:C.gHW,height:C.gh,fill:'#060c14',stroke:'#3a5a7a','stroke-width':0.4},g);
});
T('玻璃板',{x:C.ax-8,y:C.gy+C.gh/2+4,fill:'#5a8aaa','font-size':9,'text-anchor':'end','font-family':'JetBrains Mono,monospace'},g);
T('Φ0.12mm 微孔',{x:C.holes[2],y:C.gy+C.gh+14,fill:'#4a7a9a','font-size':8,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},g);
}
/* === 掩膜板 === */
function buildMask(){
if(S.maskG)return;
const g=gMask;
// 掩膜板主体
E('rect',{x:C.ax,y:C.my,width:C.aw,height:C.mh,fill:'url(#gMa)',stroke:'#4a6a8a','stroke-width':1.5,rx:2},g);
// 漏斗孔(用深色覆盖形成孔洞效果)
C.holes.forEach(hx=>{
const fy1=C.my+C.mTop;
const fy2=C.my+C.mTop+C.mFun;
const fy3=C.my+C.mTop+C.mFun+C.mChan;
// 漏斗+通道内部空间
E('path',{
d:`M${hx-C.fTW/2},${C.my} L${hx-C.fBW/2},${fy2} L${hx-C.fBW/2},${fy3} L${hx+C.fBW/2},${fy3} L${hx+C.fBW/2},${fy2} L${hx+C.fTW/2},${C.my} Z`,
fill:'#080e18',stroke:'none'
},g);
// 漏斗壁高亮线
E('line',{x1:hx-C.fTW/2,y1:C.my,x2:hx-C.fBW/2,y2:fy2,stroke:'#6a9ac8','stroke-width':1.2},g);
E('line',{x1:hx+C.fTW/2,y1:C.my,x2:hx+C.fBW/2,y2:fy2,stroke:'#6a9ac8','stroke-width':1.2},g);
// 通道壁
E('line',{x1:hx-C.fBW/2,y1:fy2,x2:hx-C.fBW/2,y2:fy3,stroke:'#4a7a9a','stroke-width':0.7},g);
E('line',{x1:hx+C.fBW/2,y1:fy2,x2:hx+C.fBW/2,y2:fy3,stroke:'#4a7a9a','stroke-width':0.7},g);
});
T('导引掩膜板',{x:C.ax-8,y:C.my+C.mh/2+4,fill:'#6a9ac0','font-size':9,'text-anchor':'end','font-family':'JetBrains Mono,monospace'},g);
S.maskG=g;
}
/* === 气流系统 === */
function createAirflow(){
clr(gAir);S.airEls=[];
C.holes.forEach(hx=>{
// 漏斗内3条气流线
for(let i=0;i<3;i++){
const off=(i-1)*7;
const l=E('line',{x1:hx+off,y1:C.my+8,x2:hx+off*0.25,y2:C.gy,
stroke:'#00cce0','stroke-width':0.7,'stroke-dasharray':'4,5',
'marker-end':'url(#aC)',opacity:0},gAir);
S.airEls.push({el:l,type:'funnel',delay:i*0.15});
}
// 玻璃孔内1条
const l2=E('line',{x1:hx,y1:C.gy,x2:hx,y2:C.cy+C.ch*0.4,
stroke:'#00cce0','stroke-width':0.9,'stroke-dasharray':'3,4',
'marker-end':'url(#aC)',opacity:0},gAir);
S.airEls.push({el:l2,type:'glass',delay:0.3});
});
}
function showAirflow(progress){
S.airEls.forEach(a=>{
const p=clamp((progress-a.delay)*2.5,0,1);
const off=-S.gt*50;
a.el.setAttribute('opacity',String(p*0.65));
a.el.setAttribute('stroke-dashoffset',String(off));
});
}
/* === 超声波可视化 === */
function createUsonic(){
clr(gUs);S.usEls=[];
for(let side=0;side<2;side++){
const bx=side===0?C.ax:C.ar;
for(let i=0;i<5;i++){
const yy=C.my+10+i*20;
const p=E('path',{
d:`M${bx},${yy} Q${bx+(side?10:-10)},${yy+10} ${bx},${yy+20}`,
stroke:'#9068f8','stroke-width':1.5,fill:'none',opacity:0,filter:'url(#gP)'
},gUs);
S.usEls.push({el:p,side,i,baseY:yy});
}
}
}
function showUsonic(prog){
const t=S.gt;
const f=S.freq/28;
S.usEls.forEach(u=>{
const p=clamp(prog*2.5-u.i*0.15,0,1);
const w=Math.sin(t*f*0.028+u.i*1.2)*4*prog;
const bx=u.side===0?C.ax:C.ar;
u.el.setAttribute('opacity',String(p*0.75));
u.el.setAttribute('d',
`M${bx+w},${u.baseY} Q${bx+(u.side?14:-14)+w},${u.baseY+10} ${bx+w},${u.baseY+20}`);
});
// 掩膜板微振
if(S.maskG){
const dx=Math.sin(t*f*0.03)*1.8*prog;
const dy=Math.cos(t*f*0.022)*0.7*prog;
S.maskG.setAttribute('transform',`translate(${dx.toFixed(2)},${dy.toFixed(2)})`);
}
}
/* === 铜柱系统 === */
function createPillars(){
clr(gPil);S.pillars=[];
// 5个目标铜柱
C.holes.forEach((hx,hi)=>{
const sx=hx+(Math.random()-.5)*70;
const p=makePillar(sx,30+Math.random()*20,true,hi);
S.pillars.push(p);
});
// 其余随机铜柱
for(let i=0;i<C.totalPillars-5;i++){
const sx=C.ax+40+Math.random()*(C.aw-80);
const p=makePillar(sx,20+Math.random()*40,false,-1);
S.pillars.push(p);
}
}
function makePillar(sx,sy,isTarget,hole){
const el=E('rect',{width:C.pw,height:C.ph,rx:2,fill:'url(#gCu)',
stroke:'#c07020','stroke-width':0.4,opacity:0},gPil);
return{
el,x:sx,y:sy,
state:'wait', // wait,fall,surf,attract,funnel,seat,blow,gone
isTarget,hole,
ph:Math.random()*Math.PI*2,
t:0,speed:0,
sx:sx,sy:sy,tx:0,ty:0,
delay:isTarget?hole*0.6:(2+Math.random()*3)
};
}
function updatePillars(dt){
const t=S.gt;
const f=S.freq/28;
const v=S.vac/50;
S.pillars.forEach(p=>{
if(p.state==='wait'||p.state==='gone')return;
p.t+=dt;
if(p.state==='fall'){
const prog=clamp(p.t/p.speed,0,1);
p.y=lerp(p.sy,p.ty,easeOutBounce(prog));
if(prog>=1){p.state='surf';p.y=p.ty;p.t=0;}
}
if(p.state==='surf'){
// 静止在掩膜板表面
}
if(p.state==='attract'){
const dur=2.5+v*0.5;
const prog=clamp(p.t/dur,0,1);
const e=easeInOutCubic(prog);
const vibX=Math.sin(t*f*0.03+p.ph)*7*(1-e);
const vibY=Math.cos(t*f*0.025+p.ph)*2.5*(1-e);
p.x=lerp(p.sx,p.tx,e)+vibX;
p.y=lerp(p.sy,C.my-C.ph,e)+vibY;
if(prog>=1){
p.state='funnel';p.t=0;
p.sx=p.x;p.sy=C.my;
p.tx=C.holes[p.hole];p.ty=C.gy+C.gh-C.ph;
p.speed=2.0;
}
}
if(p.state==='funnel'){
const prog=clamp(p.t/p.speed,0,1);
const e=easeInCubic(prog);
const wobble=Math.sin(t*f*0.035+p.ph)*1.8*(1-e);
p.x=C.holes[p.hole]+wobble;
p.y=lerp(p.sy,p.ty,e);
if(prog>=1){
p.state='seat';
p.el.setAttribute('fill','#20d088');
p.el.setAttribute('filter','url(#gG)');
setTimeout(()=>{p.el.setAttribute('fill','url(#gCu)');p.el.removeAttribute('filter');},700);
}
}
if(p.state==='blow'){
const prog=clamp(p.t/1.2,0,1);
p.x=lerp(p.sx,p.tx,easeInCubic(prog));
p.y=lerp(p.sy,p.ty,easeOutCubic(prog));
p.el.setAttribute('opacity',String(Math.max(0,1-prog*2.5).toFixed(2)));
if(prog>=1){p.state='gone';p.el.setAttribute('opacity','0');}
}
// 更新SVG位置
if(p.state!=='gone'){
p.el.setAttribute('x',(p.x-C.pw/2).toFixed(1));
p.el.setAttribute('y',p.y.toFixed(1));
p.el.setAttribute('opacity',p.el.getAttribute('opacity')||'1');
}
});
}
/* === 吹气效果 === */
function blowPillars(){
S.pillars.forEach(p=>{
if(p.state==='surf'||p.state==='attract'){
p.state='blow';p.t=0;
const dir=p.x<500?-1:1;
p.sx=p.x;p.sy=p.y;
p.tx=p.x+dir*(250+Math.random()*200);
p.ty=p.y-80-Math.random()*100;
}
});
// 吹气箭头特效
for(let i=0;i<10;i++){
const bx=C.ax+50+Math.random()*(C.aw-100);
const a=E('line',{x1:bx,y1:C.my-50,x2:bx,y2:C.my-8,
stroke:'#5a9aca','stroke-width':1.5,'marker-end':'url(#aC)',opacity:'0.8'},gFx);
setTimeout(()=>a.remove(),900);
}
T('吹气清理',{x:C.ax+C.aw/2,y:C.my-60,fill:'#5a9aca','font-size':10,'text-anchor':'middle',
'font-family':'Rajdhani,sans-serif','font-weight':600,filter:'url(#gC)'},gFx);
}
/* === 放大视图 === */
function drawZoom(){
clr(gZoom);
if(S.phase<4)return;
const g=gZoom;
const zx=870,zy=20,zw=210,zh=180;
E('rect',{x:zx,y:zy,width:zw,height:zh,fill:'rgba(6,10,18,0.88)',stroke:'#2a4a6a','stroke-width':1,rx:5},g);
T('局部放大 ×40',{x:zx+zw/2,y:zy+15,fill:'#00cce0','font-size':9,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},g);
const cx=zx+zw/2;
const fy1=zy+35,fy2=zy+95,gy1=zy+110,gy2=zy+142;
// 漏斗壁
E('line',{x1:cx-50,y1:fy1,x2:cx-12,y2:fy2,stroke:'#6a9ac8','stroke-width':1.8},g);
E('line',{x1:cx+50,y1:fy1,x2:cx+12,y2:fy2,stroke:'#6a9ac8','stroke-width':1.8},g);
// 通道壁
E('line',{x1:cx-12,y1:fy2,x2:cx-12,y2:gy1,stroke:'#4a7a9a','stroke-width':1},g);
E('line',{x1:cx+12,y1:fy2,x2:cx+12,y2:gy1,stroke:'#4a7a9a','stroke-width':1},g);
// 玻璃板
E('rect',{x:cx-55,y:gy1,width:110,height:gy2-gy1,fill:'rgba(120,180,240,0.1)',stroke:'#4a6a8a','stroke-width':0.8,rx:1},g);
// 玻璃孔
E('rect',{x:cx-10,y:gy1,width:20,height:gy2-gy1,fill:'#060c14'},g);
// 尺寸标注
T('Φ0.13',{x:cx+20,y:fy2+4,fill:'#f0b060','font-size':8,'font-family':'JetBrains Mono,monospace'},g);
T('Φ0.12',{x:cx+18,y:gy1+14,fill:'#f04848','font-size':8,'font-family':'JetBrains Mono,monospace'},g);
// 架桥风险区
E('rect',{x:cx-14,y:gy1-4,width:28,height:8,fill:'none',stroke:'#f04848','stroke-width':0.8,'stroke-dasharray':'2,2',rx:1},g);
T('架桥卡死风险',{x:cx,y:gy2+12,fill:'#f04848','font-size':7.5,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},g);
// 45°角标注
E('path',{d:`M${cx-48},${fy1+3} A16,16 0 0,1 ${cx-40},${fy1+14}`,fill:'none',stroke:'#f0b060','stroke-width':0.8},g);
T('45°',{x:cx-56,y:fy1+18,fill:'#f0b060','font-size':8,'font-family':'JetBrains Mono,monospace'},g);
// 已入孔铜柱
const sc=S.pillars.filter(p=>p.state==='seat').length;
if(sc>0){
E('rect',{x:cx-6,y:gy1+4,width:12,height:gy2-gy1-8,rx:1.5,fill:'url(#gCu)',stroke:'#c07020','stroke-width':0.4},g);
T('✓',{x:cx,y:gy2+24,fill:'#20d088','font-size':10,'text-anchor':'middle','font-weight':'700'},g);
}
// 连接线
const srcHx=C.holes[2],srcY=C.my+C.mTop+C.mFun;
E('line',{x1:srcHx+28,y1:srcY,x2:zx,y2:zy+zh/2,stroke:'#1e3a5a','stroke-width':0.6,'stroke-dasharray':'3,4'},g);
E('circle',{cx:srcHx+28,cy:srcY,r:3,fill:'none',stroke:'#2a5a7a','stroke-width':0.6},g);
}
/* === 标注系统 === */
function drawAnno(){
clr(gAnno);
const g=gAnno;
if(S.phase>=1){
// Φ0.5标注
T('Φ0.5',{x:C.holes[0],y:C.my-8,fill:'#00cce0','font-size':8,'text-anchor':'middle','font-family':'JetBrains Mono,monospace'},g);
// Φ0.13标注
T('Φ0.13',{x:C.holes[4]+20,y:C.my+C.mTop+C.mFun+4,fill:'#00cce0','font-size':7.5,'font-family':'JetBrains Mono,monospace'},g);
// 45°角标注(第一个漏斗)
const hx=C.holes[0];
const fy1=C.my+C.mTop;
E('path',{d:`M${hx-C.fTW/2+6},${C.my+2} A14,14 0 0,1 ${hx-C.fTW/2+12},${C.my+14}`,fill:'none',stroke:'#f0b060','stroke-width':0.8},g);
T('45°',{x:hx-C.fTW/2-4,y:C.my+18,fill:'#f0b060','font-size':8,'font-family':'JetBrains Mono,monospace'},g);
}
if(S.phase>=4){
T('IFR: 自组织入孔 — 无需逐根对准',{x:C.ax+C.aw/2,y:C.cy+C.ch+55,fill:'#20d088','font-size':11,'text-anchor':'middle',
'font-family':'Rajdhani,sans-serif','font-weight':600,filter:'url(#gG)',opacity:'0.9'},g);
}
}
/* === 阶段管理 === */
const PH_NAMES=['准备就绪','贴合掩膜板','开启真空','撒布铜柱','超声波振动','吹气清理','移除掩膜板'];
const PH_INFO=[
'点击 <span class="hl">下一步</span> 开始动画。本动画展示漏斗导引掩膜板+超声波真空贴合的核心原理——利用漏斗将 0.12mm 靶区扩大至 0.5mm(降低 90% 对准难度),超声波克服微米级摩擦与静电粘附,真空场提供穿透驱动力,实现铜柱自组织入孔。',
'将漏斗状导引掩膜板精准贴合在玻璃板上方。掩膜板孔径<span class="hl">上大下小</span>(上部 Φ0.5mm,下部 Φ0.13mm),收敛角 45°,与玻璃微孔一一对应,形成倒角漏斗导引结构。',
'启动底部真空腔,真空度达 <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>,需确保铜柱无毛刺。'
];
function enterPhase(n){
S.phase=n;
updateBar();
document.getElementById('pL').textContent=PH_NAMES[n];
document.getElementById('nI').innerHTML=PH_INFO[n];
if(n===1){
buildMask();
// 滑入动画
S.maskG.setAttribute('transform','translate(0,-180)');
S.maskG.setAttribute('opacity','0');
let t0=null;
function anim(ts){
if(!t0)t0=ts;
const p=clamp((ts-t0)/1300,0,1);
const e=1-Math.pow(1-p,3);
S.maskG.setAttribute('transform',`translate(0,${lerp(-180,0,e).toFixed(1)})`);
S.maskG.setAttribute('opacity',String(e.toFixed(2)));
if(p<1)requestAnimationFrame(anim);
else drawAnno();
}
requestAnimationFrame(anim);
}
if(n===2){
createAirflow();
const vi=document.getElementById('vacInd');
if(vi)vi.setAttribute('opacity','1');
drawAnno();
}
if(n===3){
createPillars();
// 铜柱下落动画
S.pillars.forEach(p=>{
p.state='fall';p.t=0;
p.sy=p.y;
p.ty=C.my-C.ph;
p.speed=0.8+Math.random()*0.6;
p.el.setAttribute('opacity','1');
});
drawAnno();
}
if(n===4){
createUsonic();
// 目标铜柱开始寻孔
S.pillars.forEach(p=>{
if(p.isTarget&&p.state==='surf'){
setTimeout(()=>{
if(p.state==='surf'){
p.state='attract';p.t=0;
p.sx=p.x;p.sy=p.y;
p.tx=C.holes[p.hole];
}
},p.delay*1000);
}
});
drawAnno();
}
if(n===5){
clr(gUs);
if(S.maskG)S.maskG.removeAttribute('transform');
blowPillars();
drawAnno();
drawZoom();
}
if(n===6){
// 掩膜板上升
if(S.maskG){
let t0=null;
function anim(ts){
if(!t0)t0=ts;
const p=clamp((ts-t0)/1500,0,1);
const e=p*p*p;
S.maskG.setAttribute('transform',`translate(0,${lerp(0,-220,e).toFixed(1)})`);
S.maskG.setAttribute('opacity',String(Math.max(0,1-e*0.7).toFixed(2)));
if(p<1)requestAnimationFrame(anim);
else S.maskG.setAttribute('opacity','0');
}
requestAnimationFrame(anim);
}
// 隐藏气流
S.airEls.forEach(a=>a.el.setAttribute('opacity','0'));
clr(gZoom);
drawAnno();
// 完成标注
S.pillars.filter(p=>p.state==='seat').forEach(p=>{
p.el.setAttribute('x',(C.holes[p.hole]-C.pw/2).toFixed(1));
p.el.setAttribute('y',(C.gy+2).toFixed(1));
p.el.setAttribute('opacity','1');
});
T('装配完成 — 铜柱全部精准就位',{x:C.ax+C.aw/2,y:C.gy+C.gh+25,fill:'#20d088','font-size':12,'text-anchor':'middle',
'font-family':'Rajdhani,sans-serif','font-weight':700,filter:'url(#gG)'},gFx);
}
}
function nextPhase(){if(S.phase<6)enterPhase(S.phase+1)}
function resetAll(){
[gAir,gPil,gUs,gAnno,gZoom,gFx].forEach(g=>clr(g));
if(S.maskG){clr(S.maskG);S.maskG=null;}
S.pillars=[];S.airEls=[];S.usEls=[];S.phase=0;S.gt=0;
const vi=document.getElementById('vacInd');
if(vi)vi.setAttribute('opacity','0');
document.getElementById('pL').textContent=PH_NAMES[0];
document.getElementById('nI').innerHTML=PH_INFO[0];
updateBar();stopAuto();
}
function toggleAuto(){
if(S.auto)stopAuto();else startAuto();
}
function startAuto(){
S.auto=true;document.getElementById('bA').textContent='暂停';autoStep();
}
function stopAuto(){
S.auto=false;document.getElementById('bA').textContent='自动播放';clearTimeout(S.autoTm);
}
function autoStep(){
if(!S.auto||S.phase>=6)return;
nextPhase();
const d=[0,2800,2200,3200,10000,3500,0];
S.autoTm=setTimeout(autoStep,d[S.phase]||3000);
}
function updateBar(){
document.querySelectorAll('.pb i').forEach((b,i)=>{
b.className=i+1<S.phase?'ok':i+1===S.phase?'on':'';
});
}
/* === 主循环 === */
let lastT=0;
function loop(ts){
if(!lastT)lastT=ts;
const dt=Math.min((ts-lastT)/1000,0.05);
lastT=ts;S.gt+=dt;
// 气流
if(S.phase>=2&&S.airEls.length)showAirflow(S.phase===2?clamp(S.gt*0.5,0,1):1);
// 铜柱
if(S.phase>=3&&S.phase<6)updatePillars(dt);
// 超声波
if(S.phase===4)showUsonic(clamp(S.gt*0.3,0,1));
// 放大视图更新
if(S.phase>=4&&S.phase<=5)drawZoom();
// 已入座铜柱保持位置
if(S.phase>=5){
S.pillars.filter(p=>p.state==='seat').forEach(p=>{
p.el.setAttribute('x',(C.holes[p.hole]-C.pw/2).toFixed(1));
p.el.setAttribute('y',(C.gy+C.gh-C.ph-2).toFixed(1));
p.el.setAttribute('opacity','1');
});
}
requestAnimationFrame(loop);
}
/* === 初始化 === */
function init(){
// 阶段条
const pb=document.getElementById('pBar');
for(let i=0;i<6;i++){const d=document.createElement('i');pb.appendChild(d);}
drawBg();drawChamber();drawGlass();
document.getElementById('bN').onclick=()=>{nextPhase();};
document.getElementById('bA').onclick=toggleAuto;
document.getElementById('bR').onclick=resetAll;
document.getElementById('sF').oninput=function(){
S.freq=+this.value;document.getElementById('vF').textContent=S.freq+' kHz';
};
document.getElementById('sV').oninput=function(){
S.vac=+this.value;document.getElementById('vV').textContent='-0.'+Math.round(S.vac)+' atm';
};
requestAnimationFrame(loop);
}
init();
})();
</script>
</body>
</html>
这个版本修复了之前的所有问题,主要改进:
修复的关键 Bug
- 移除了未定义的
p.vibDx引用(这是导致动画崩溃的根本原因) - 重写了铜柱动画系统,使用确定性的插值动画替代物理模拟,避免了状态机中的竞态条件
- 修复了掩膜板组引用和清理逻辑
动画系统重构
- 每个铜柱现在使用
state + progress的确定性动画:fall → surf → attract → funnel → seat - 目标铜柱带有延迟(
delay),错开入孔时间,视觉效果更清晰 - 振动效果使用全局时间
S.gt和频率参数计算,不依赖物理积分
六步完整时序
- 掩膜板从上方缓动滑入贴合
- 气流箭头出现并持续流动(dashoffset 动画)
- 25 颗铜柱受重力散落(弹跳缓动)
- 超声波波纹 + 掩膜板微振 + 铜柱自组织寻孔入位
- 吹气清理 + 右上角放大视图标注架桥风险
- 掩膜板上升,铜柱精准就位,完成提示
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
