独立渲染引擎就绪引擎就绪
<!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@300;500;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
<style>
:root{--bg:#060a14;--fg:#cfd8dc;--muted:#546e7a;--accent:#00e5ff;--accent2:#ff9100;--card:#0c1320;--border:#162030}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--fg);font-family:'Noto Sans SC',sans-serif;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;overflow:hidden;padding:10px}
.page-title{font-family:'Rajdhani',sans-serif;font-weight:700;font-size:clamp(1rem,2.2vw,1.6rem);letter-spacing:.15em;color:#d0d8e0;text-transform:uppercase;margin-bottom:10px;position:relative;padding:0 24px;white-space:nowrap}
.page-title::before,.page-title::after{content:'';position:absolute;top:50%;width:50px;height:1px;background:linear-gradient(90deg,transparent,var(--accent),transparent)}
.page-title::before{right:100%}.page-title::after{left:100%}
.canvas-wrap{position:relative;width:96vw;max-width:1440px;aspect-ratio:1400/790;border:1px solid var(--border);border-radius:8px;overflow:hidden;background:var(--card);box-shadow:0 0 80px rgba(0,229,255,.04),0 4px 60px rgba(0,0,0,.6)}
canvas{width:100%;height:100%;display:block}
.controls{display:flex;gap:20px;margin-top:10px;padding:10px 22px;background:var(--card);border:1px solid var(--border);border-radius:8px;align-items:center;flex-wrap:wrap;justify-content:center}
.cg{display:flex;align-items:center;gap:7px}
.cl{font-family:'Rajdhani',sans-serif;font-size:.82rem;color:var(--muted);white-space:nowrap}
.cv{font-family:'Rajdhani',sans-serif;font-weight:700;font-size:.88rem;min-width:38px;text-align:center}
.cv.cyan{color:var(--accent)}.cv.amber{color:var(--accent2)}
input[type=range]{-webkit-appearance:none;appearance:none;width:110px;height:4px;background:var(--border);border-radius:2px;outline:none}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;cursor:pointer;box-shadow:0 0 6px rgba(0,229,255,.35)}
#pressureSlider::-webkit-slider-thumb{background:var(--accent)}
#speedSlider::-webkit-slider-thumb{background:var(--accent2)}
.foot{max-width:940px;text-align:center;font-size:.75rem;color:var(--muted);margin-top:8px;line-height:1.6}
.foot .c1{color:#00e5ff}.foot .c2{color:#ff9100}.foot .c3{color:#00e676}.foot .c4{color:#d500f9}
@media(max-width:768px){.controls{gap:12px;padding:8px 14px}input[type=range]{width:80px}.foot{font-size:.65rem;padding:0 12px}}
</style>
</head>
<body>
<div class="page-title">射流卷吸效应 — 环形引射结构原理</div>
<div class="canvas-wrap"><canvas id="cv"></canvas></div>
<div class="controls">
<div class="cg"><span class="cl">主风压力</span><input type="range" id="pressureSlider" min="25" max="220" value="100"><span class="cv cyan" id="pVal">1.0</span></div>
<div class="cg"><span class="cl">动画速度</span><input type="range" id="speedSlider" min="20" max="250" value="100"><span class="cv amber" id="sVal">1.0x</span></div>
</div>
<div class="foot">
<span class="c1">高速主风</span>从中心喷管射出 → 出口形成<span class="c4">负压场</span> → 卷吸<span class="c2">二次风</span>大量涌入 → 混合段碰撞混合 → <span class="c3">高流量·中速度·低噪声</span>输出 | 核心转化:壁面摩擦 → 流体内部摩擦
</div>
<script>
/* ========== 初始化 Canvas ========== */
const canvas = document.getElementById('cv');
const ctx = canvas.getContext('2d');
const W = 1400, H = 790;
canvas.width = W; canvas.height = H;
/* ========== 几何常量 ========== */
const CY = 285; // 引射器中心 Y
const DX0 = 75; // 风道入口 X
const NX0 = 170; // 喷管入口 X
const NTH = 335; // 喷管喉部 X
const NX1 = 475; // 喷管出口 X
const MX1 = 720; // 混合段终点 X
const DF1 = 1060; // 扩压段终点 X
const DX1 = 1170; // 风道出口 X
const DR = 112; // 直段风道半径
const NIR = 30; // 喷管入口内半径
const NTR = 9; // 喷管喉部内半径
const NER = 20; // 喷管出口内半径
const DER = 145; // 扩压段出口半径
const WT = 6; // 喷管壁厚
const GY = 530, GH = 155; // 图表区域
/* ========== 颜色 ========== */
const C_P = [0,229,255]; // 主风 青
const C_S = [255,145,0]; // 二次风 琥珀
const C_M = [0,230,118]; // 混合 绿
const C_N = [213,0,249]; // 负压 紫
/* ========== 状态 ========== */
let pMul = 1.0, sMul = 1.0, aTime = 0, lastTS = 0;
/* ========== 工具 ========== */
const lerp = (a,b,t) => a+(b-a)*t;
const clamp = (v,lo,hi) => Math.max(lo,Math.min(hi,v));
const ss = t => { t=clamp(t,0,1); return t*t*(3-2*t); };
const lc = (c1,c2,t) => [Math.round(lerp(c1[0],c2[0],t)),Math.round(lerp(c1[1],c2[1],t)),Math.round(lerp(c1[2],c2[2],t))];
const cs = (c,a) => `rgba(${c[0]},${c[1]},${c[2]},${a})`;
/* ========== 几何函数 ========== */
function ductR(x){
if(x<=MX1) return DR;
if(x>=DF1) return DER;
return lerp(DR,DER,ss((x-MX1)/(DF1-MX1)));
}
function nozIR(x){
if(x<NX0||x>NX1) return null;
if(x<=NTH) return lerp(NIR,NTR,ss((x-NX0)/(NTH-NX0)));
return lerp(NTR,NER,ss((x-NTH)/(NX1-NTH)));
}
function nozOR(x){ const r=nozIR(x); return r===null?null:r+WT; }
/* 速度/压力分布 (归一化 0-1) */
function velProfile(t){
if(t<.11) return lerp(.22,.32,t/.11);
if(t<.29) return lerp(.32,1,(t-.11)/.18);
if(t<.40) return 1;
if(t<.64) return lerp(1,.42,(t-.40)/.24);
return lerp(.42,.32,(t-.64)/.36);
}
function prsProfile(t){
if(t<.11) return lerp(.72,.62,t/.11);
if(t<.29) return lerp(.62,.12,(t-.11)/.18);
if(t<.42) return lerp(.12,.06,(t-.29)/.13);
if(t<.64) return lerp(.06,.52,(t-.42)/.22);
return lerp(.52,.62,(t-.64)/.36);
}
/* ========== 粒子系统 ========== */
const parts = [];
const NP = 130, NS = 180;
function mkP(type){
const p = {type, pr:Math.random(), sp:0, sz:0, al:0, yn:0, sd:0, px:0, py:0};
if(type==='p'){
p.yn=(Math.random()-.5)*1.6;
p.sp=.0011+Math.random()*.0006;
p.sz=1.1+Math.random()*1.8;
p.al=.45+Math.random()*.55;
} else {
p.yn=.12+Math.random()*.82;
p.sd=Math.random()>.5?1:-1;
p.sp=.0005+Math.random()*.0004;
p.sz=1+Math.random()*1.5;
p.al=.35+Math.random()*.55;
}
return p;
}
function rstP(p){
p.pr=-Math.random()*.04;
if(p.type==='p') p.yn=(Math.random()-.5)*1.6;
else { p.yn=.12+Math.random()*.82; p.sd=Math.random()>.5?1:-1; }
}
function updP(p,dt){
let sm=1; const r=p.pr;
if(p.type==='p'){
if(r<.11) sm=.75;
else if(r<.29) sm=lerp(.75,2.6,(r-.11)/.18);
else if(r<.42) sm=2.6;
else if(r<.64) sm=lerp(2.6,.85,(r-.42)/.22);
else sm=.75;
} else {
if(r<.33) sm=.45;
else if(r<.55) sm=lerp(.45,1.4,(r-.33)/.22);
else if(r<.75) sm=lerp(1.4,.8,(r-.55)/.20);
else sm=.7;
}
p.pr += p.sp*sm*pMul*sMul*dt*60;
if(p.pr>1) rstP(p);
}
function posP(p){
if(p.pr<0) return null;
const x = lerp(DX0,DX1,p.pr);
if(p.type==='p'){
const nr=nozIR(x);
if(nr!==null) return {x, y:CY+p.yn*nr};
if(x<NX0){
const t=ss((x-DX0)/(NX0-DX0));
const r=lerp(NIR*1.6,NIR,t);
return {x, y:CY+p.yn*r};
}
// 喷管之后 - 射流扩散
const t=(x-NX1)/(DX1-NX1);
const jr=NER*(1+t*3.2);
const dr=ductR(x);
const er=Math.min(jr,dr*.82);
return {x, y:CY+p.yn*er};
}
// 二次风
const or=nozOR(x), dr=ductR(x);
if(or!==null && x>=NX0){
const gi=or+4, go=dr-4;
if(gi>=go) return null;
const r=gi+p.yn*(go-gi);
return {x, y:CY+p.sd*r};
}
if(x<NX0){
const t=ss((x-DX0)/(NX0-DX0));
const gi=NIR+WT+4, go=DR-4;
const sr=p.yn*DR;
const er2=gi+p.yn*(go-gi);
const r=lerp(sr,er2,t);
return {x, y:CY+p.sd*r};
}
// 喷管之后 - 卷吸向中心
const t=clamp((x-NX1)/(MX1-NX1),0,1);
const gi=NER+WT+4, go=DR-4;
const sr=gi+p.yn*(go-gi);
const tr=p.yn*dr*.72;
const r=lerp(sr,tr,ss(t));
return {x, y:CY+p.sd*Math.max(4,r)};
}
function colP(p){
if(p.type==='p'){
if(p.pr>.58) return lc(C_P,C_M,clamp((p.pr-.58)/.22,0,1));
return C_P;
}
if(p.pr>.48) return lc(C_S,C_M,clamp((p.pr-.48)/.25,0,1));
return C_S;
}
/* ========== 绘制函数 ========== */
function drawBg(){
const bg=ctx.createRadialGradient(W/2,CY,80,W/2,CY,750);
bg.addColorStop(0,'#0e1726'); bg.addColorStop(1,'#050910');
ctx.fillStyle=bg; ctx.fillRect(0,0,W,H);
// 网格
ctx.strokeStyle='rgba(25,45,65,.25)'; ctx.lineWidth=.5;
for(let x=0;x<W;x+=50){ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,H);ctx.stroke();}
for(let y=0;y<H;y+=50){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(W,y);ctx.stroke();}
}
function drawJetGlow(){
const g=ctx.createLinearGradient(NX1,CY,MX1,CY);
g.addColorStop(0,`rgba(0,229,255,${.1*pMul})`);
g.addColorStop(.6,`rgba(0,229,255,${.03*pMul})`);
g.addColorStop(1,'rgba(0,229,255,0)');
ctx.beginPath();
ctx.moveTo(NX1,CY-NER);
ctx.bezierCurveTo(NX1+80,CY-NER*1.8, MX1-60,CY-DR*.45, MX1,CY-DR*.5);
ctx.lineTo(MX1,CY+DR*.5);
ctx.bezierCurveTo(MX1-60,CY+DR*.45, NX1+80,CY+NER*1.8, NX1,CY+NER);
ctx.closePath();
ctx.fillStyle=g; ctx.fill();
}
function drawNegPress(t){
const cx=NX1+55, pulse=.65+.35*Math.sin(t*3.8);
const br=75*pMul*pulse;
// 径向光晕
const g=ctx.createRadialGradient(cx,CY,0,cx,CY,br);
g.addColorStop(0,`rgba(213,0,249,${.18*pulse})`);
g.addColorStop(.55,`rgba(213,0,249,${.06*pulse})`);
g.addColorStop(1,'rgba(213,0,249,0)');
ctx.fillStyle=g; ctx.beginPath(); ctx.arc(cx,CY,br,0,Math.PI*2); ctx.fill();
// 吸入箭头
const na=10, ap=t*2.2;
for(let i=0;i<na;i++){
const ang=(i/na)*Math.PI*2+ap;
const maxR=br*.75;
const prog=((t*1.6+i*.28)%1);
const r=maxR*(1-prog*.55);
const op=(1-prog)*.45*pulse;
const ax=cx+Math.cos(ang)*r, ay=CY+Math.sin(ang)*r;
const dx=-Math.cos(ang)*9, dy=-Math.sin(ang)*9;
ctx.beginPath(); ctx.moveTo(ax,ay); ctx.lineTo(ax+dx,ay+dy);
ctx.strokeStyle=`rgba(213,0,249,${op})`; ctx.lineWidth=1.5; ctx.stroke();
const ha=Math.atan2(dy,dx), hl=4;
ctx.beginPath();
ctx.moveTo(ax+dx,ay+dy);
ctx.lineTo(ax+dx-hl*Math.cos(ha-.5),ay+dy-hl*Math.sin(ha-.5));
ctx.moveTo(ax+dx,ay+dy);
ctx.lineTo(ax+dx-hl*Math.cos(ha+.5),ay+dy-hl*Math.sin(ha+.5));
ctx.stroke();
}
}
function drawParticles(){
for(const p of parts){
const pos=posP(p); if(!pos) continue;
if(Math.abs(pos.y-CY)>ductR(pos.x)-2) continue;
const col=colP(p);
// 尾迹
if(p.px&&p.py&&Math.abs(pos.x-p.px)<45){
ctx.beginPath(); ctx.moveTo(p.px,p.py); ctx.lineTo(pos.x,pos.y);
ctx.strokeStyle=cs(col,p.al*.25); ctx.lineWidth=p.sz*.7; ctx.stroke();
}
// 粒子
ctx.beginPath(); ctx.arc(pos.x,pos.y,p.sz,0,Math.PI*2);
ctx.fillStyle=cs(col,p.al); ctx.fill();
// 主风高速区发光
if(p.type==='p'&&p.pr>.14&&p.pr<.52){
ctx.beginPath(); ctx.arc(pos.x,pos.y,p.sz*3,0,Math.PI*2);
ctx.fillStyle=cs(col,p.al*.1); ctx.fill();
}
p.px=pos.x; p.py=pos.y;
}
}
function drawNozzleWall(side){
const s=side;
ctx.beginPath();
for(let i=0;i<=120;i++){
const t=i/120, x=lerp(NX0,NX1,t), r=nozOR(x), y=CY+s*r;
i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);
}
ctx.lineTo(NX1,CY+s*NER);
for(let i=120;i>=0;i--){
const t=i/120, x=lerp(NX0,NX1,t), r=nozIR(x), y=CY+s*r;
ctx.lineTo(x,y);
}
ctx.closePath();
const y1=CY+s*(NIR+WT), y2=CY+s*NTR;
const g=ctx.createLinearGradient(0,y1,0,y2);
g.addColorStop(0,'#2c3e47'); g.addColorStop(.35,'#5a7a88'); g.addColorStop(.7,'#4a6a78'); g.addColorStop(1,'#1e2e36');
ctx.fillStyle=g; ctx.fill();
ctx.strokeStyle='#7a9aaa'; ctx.lineWidth=.8; ctx.stroke();
// 剖面线
ctx.save(); ctx.clip();
ctx.strokeStyle='rgba(90,122,136,.25)'; ctx.lineWidth=.4;
for(let i=-30;i<50;i++){
const off=i*7;
ctx.beginPath();
ctx.moveTo(NX0+off, CY+s*(NIR+WT+6));
ctx.lineTo(NX0+off+55, CY+s*(NTR-4));
ctx.stroke();
}
ctx.restore();
}
function drawStruct(){
// 外风道壁
ctx.lineWidth=5; ctx.lineCap='round'; ctx.strokeStyle='#3e5565';
// 上壁
ctx.beginPath();
for(let i=0;i<=350;i++){const t=i/350,x=lerp(DX0,DX1,t),r=ductR(x),y=CY-r;i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);}
ctx.stroke();
// 下壁
ctx.beginPath();
for(let i=0;i<=350;i++){const t=i/350,x=lerp(DX0,DX1,t),r=ductR(x),y=CY+r;i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);}
ctx.stroke();
// 入口/出口端面
ctx.beginPath(); ctx.moveTo(DX0,CY-DR); ctx.lineTo(DX0,CY+DR); ctx.stroke();
ctx.beginPath(); ctx.moveTo(DX1,CY-ductR(DX1)); ctx.lineTo(DX1,CY+ductR(DX1)); ctx.stroke();
// 壁面高光
ctx.lineWidth=1; ctx.strokeStyle='rgba(120,160,180,.2)';
ctx.beginPath();
for(let i=0;i<=350;i++){const t=i/350,x=lerp(DX0,DX1,t),r=ductR(x),y=CY-r+3;i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);}
ctx.stroke();
// 喷管
drawNozzleWall(-1); drawNozzleWall(1);
// 分段虚线
ctx.setLineDash([4,7]); ctx.strokeStyle='rgba(70,100,120,.3)'; ctx.lineWidth=1;
[NX0,NX1,MX1,DF1].forEach(x=>{ctx.beginPath();ctx.moveTo(x,CY-DR-28);ctx.lineTo(x,CY+DR+28);ctx.stroke();});
ctx.setLineDash([]);
// 二次风与壁面接触高亮(关键视觉:二次风隔离壁面)
ctx.lineWidth=2.5; ctx.strokeStyle='rgba(255,145,0,.12)';
ctx.beginPath();
for(let i=0;i<=200;i++){const t=i/200,x=lerp(NX0,NX1,t),r=ductR(x),y=CY-r+1;i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);}
ctx.stroke();
ctx.beginPath();
for(let i=0;i<=200;i++){const t=i/200,x=lerp(NX0,NX1,t),r=ductR(x),y=CY+r-1;i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);}
ctx.stroke();
}
function drawLabels(){
// 分段名称
ctx.font='600 12px "Noto Sans SC",sans-serif'; ctx.textAlign='center'; ctx.fillStyle='#607888';
ctx.fillText('喷管区',(NX0+NX1)/2, CY-DR-38);
ctx.fillText('混合段',(NX1+MX1)/2, CY-DR-38);
ctx.fillText('扩压段',(MX1+DF1)/2, CY-DR-38);
// 组件标注
ctx.font='500 11px "Noto Sans SC",sans-serif'; ctx.textAlign='left';
ctx.fillStyle='#8aa0b0';
ctx.fillText('中心喷管 (Laval)', NTH+38, CY-52);
ctx.beginPath(); ctx.moveTo(NTH+36,CY-55); ctx.lineTo(NTH+8,CY-40);
ctx.strokeStyle='rgba(138,160,176,.4)'; ctx.lineWidth=.8; ctx.stroke();
// 环形次风道
ctx.fillStyle='#ff9100'; ctx.font='500 11px "Noto Sans SC",sans-serif';
ctx.fillText('环形次风道', NTH+25, CY-DR+22);
// 负压区
ctx.fillStyle='#d500f9'; ctx.textAlign='center';
ctx.fillText('负压卷吸区', NX1+55, CY+DR+42);
ctx.beginPath(); ctx.moveTo(NX1+55,CY+DR+30); ctx.lineTo(NX1+55,CY+DR+18);
ctx.strokeStyle='rgba(213,0,249,.35)'; ctx.lineWidth=.8; ctx.stroke();
// 入口标注
ctx.font='700 13px "Rajdhani",sans-serif'; ctx.textAlign='right';
ctx.fillStyle='#00e5ff'; ctx.fillText('主风 \u2192', NX0-8, CY+3);
ctx.fillStyle='#ff9100';
ctx.fillText('二次风 \u2192', NX0-8, CY-DR+22);
ctx.fillText('二次风 \u2192', NX0-8, CY+DR-14);
// 出口标注
ctx.textAlign='left'; ctx.fillStyle='#00e676'; ctx.font='700 13px "Rajdhani",sans-serif';
ctx.fillText('\u2192 输出', DX1+8, CY+3);
// 截面比标注
ctx.font='400 10px "Noto Sans SC",sans-serif'; ctx.textAlign='center'; ctx.fillStyle='#506878';
ctx.fillText('A\u2081:A\u2082 = 1:4', (NX0+NX1)/2, CY+DR+18);
// 扩压角
ctx.fillText('\u03b1 = 7\u00b0', (MX1+DF1)/2, CY+DER-8);
// 关键转化高亮框
const bx=(NX1+MX1)/2-70, by=CY+DR+52, bw=140, bh=22;
ctx.fillStyle='rgba(0,230,118,.06)'; ctx.fillRect(bx,by,bw,bh);
ctx.strokeStyle='rgba(0,230,118,.25)'; ctx.lineWidth=1; ctx.strokeRect(bx,by,bw,bh);
ctx.fillStyle='rgba(0,230,118,.7)'; ctx.font='500 10px "Noto Sans SC",sans-serif'; ctx.textAlign='center';
ctx.fillText('壁面摩擦 \u2192 流体内部摩擦', bx+bw/2, by+15);
}
function drawGraph(t){
const gx=DX0, gw=DX1-DX0;
// 背景
ctx.fillStyle='rgba(5,9,16,.55)'; ctx.fillRect(gx-8,GY-8,gw+16,GH+38);
ctx.strokeStyle='rgba(70,100,120,.2)'; ctx.lineWidth=1; ctx.strokeRect(gx,GY,gw,GH);
// 网格
ctx.strokeStyle='rgba(25,45,65,.3)'; ctx.lineWidth=.5;
for(let i=1;i<5;i++){const y=GY+(i/5)*GH; ctx.beginPath(); ctx.moveTo(gx,y); ctx.lineTo(gx+gw,y); ctx.stroke();}
// 分段虚线
ctx.setLineDash([3,5]); ctx.strokeStyle='rgba(70,100,120,.2)';
[NX0,NX1,MX1,DF1].forEach(x=>{ctx.beginPath();ctx.moveTo(x,GY);ctx.lineTo(x,GY+GH);ctx.stroke();});
ctx.setLineDash([]);
// 流速曲线
ctx.beginPath(); ctx.moveTo(gx,GY+GH);
for(let i=0;i<=250;i++){const tt=i/250,x=gx+tt*gw,v=velProfile(tt),y=GY+GH-v*GH; ctx.lineTo(x,y);}
ctx.lineTo(gx+gw,GY+GH); ctx.closePath();
ctx.fillStyle='rgba(0,229,255,.06)'; ctx.fill();
ctx.beginPath();
for(let i=0;i<=250;i++){const tt=i/250,x=gx+tt*gw,v=velProfile(tt),y=GY+GH-v*GH; i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);}
ctx.strokeStyle='rgba(0,229,255,.65)'; ctx.lineWidth=2; ctx.stroke();
// 压力曲线
ctx.beginPath(); ctx.moveTo(gx,GY+GH);
for(let i=0;i<=250;i++){const tt=i/250,x=gx+tt*gw,p=prsProfile(tt),y=GY+GH-p*GH; ctx.lineTo(x,y);}
ctx.lineTo(gx+gw,GY+GH); ctx.closePath();
ctx.fillStyle='rgba(255,145,0,.05)'; ctx.fill();
ctx.beginPath();
for(let i=0;i<=250;i++){const tt=i/250,x=gx+tt*gw,p=prsProfile(tt),y=GY+GH-p*GH; i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);}
ctx.strokeStyle='rgba(255,145,0,.65)'; ctx.lineWidth=2; ctx.stroke();
// 动态指示点
const it=((t*.07)%1), ix=gx+it*gw, iv=velProfile(it), iy=GY+GH-iv*GH;
ctx.beginPath(); ctx.arc(ix,iy,4,0,Math.PI*2); ctx.fillStyle='#00e5ff'; ctx.fill();
ctx.beginPath(); ctx.arc(ix,iy,9,0,Math.PI*2); ctx.fillStyle='rgba(0,229,255,.15)'; ctx.fill();
// 轴标签
ctx.font='500 10px "Rajdhani",sans-serif'; ctx.textAlign='center'; ctx.fillStyle='#4a6575';
ctx.fillText('沿轴向距离', gx+gw/2, GY+GH+16);
ctx.save(); ctx.translate(gx-12,GY+GH/2); ctx.rotate(-Math.PI/2); ctx.fillText('相对值',0,0); ctx.restore();
// 图例
ctx.textAlign='left';
const lx=gx+gw-110, ly=GY+18;
ctx.fillStyle='rgba(0,229,255,.85)'; ctx.fillRect(lx,ly,14,2); ctx.fillText('流速',lx+20,ly+4);
ctx.fillStyle='rgba(255,145,0,.85)'; ctx.fillRect(lx,ly+14,14,2); ctx.fillText('压力',lx+20,ly+18);
// 负压标注
const npT=.37, npX=gx+npT*gw, npP=prsProfile(npT), npY=GY+GH-npP*GH;
ctx.beginPath(); ctx.arc(npX,npY,3,0,Math.PI*2); ctx.fillStyle='#d500f9'; ctx.fill();
ctx.font='500 9px "Noto Sans SC",sans-serif'; ctx.fillStyle='#d500f9'; ctx.textAlign='left';
ctx.fillText('负压',npX+6,npY+3);
}
/* 动态流向小箭头 */
function drawFlowArrows(t){
const phase = t*1.5;
ctx.lineWidth=1.2;
// 环形次风道中的箭头(右向)
for(let i=0;i<5;i++){
const prog=((phase*.3+i*.2)%1);
const x=lerp(NX0+20,NX1-20,prog);
const or2=nozOR(x); if(!or2) continue;
const dr2=ductR(x);
const r=(or2+dr2)/2;
const al=(.5-Math.abs(prog-.5))*.5;
ctx.strokeStyle=`rgba(255,145,0,${al})`;
// 上侧
ctx.beginPath(); ctx.moveTo(x-6,CY-r); ctx.lineTo(x+6,CY-r);
ctx.moveTo(x+3,CY-r-3); ctx.lineTo(x+6,CY-r); ctx.lineTo(x+3,CY-r+3); ctx.stroke();
// 下侧
ctx.beginPath(); ctx.moveTo(x-6,CY+r); ctx.lineTo(x+6,CY+r);
ctx.moveTo(x+3,CY+r-3); ctx.lineTo(x+6,CY+r); ctx.lineTo(x+3,CY+r+3); ctx.stroke();
}
// 混合段箭头
for(let i=0;i<4;i++){
const prog=((phase*.25+i*.25)%1);
const x=lerp(NX1+30,MX1-30,prog);
const al=(.5-Math.abs(prog-.5))*.4;
ctx.strokeStyle=`rgba(0,230,118,${al})`;
ctx.beginPath(); ctx.moveTo(x-8,CY); ctx.lineTo(x+8,CY);
ctx.moveTo(x+5,CY-3); ctx.lineTo(x+8,CY); ctx.lineTo(x+5,CY+3); ctx.stroke();
}
}
/* ========== 主动画循环 ========== */
function animate(ts){
if(!lastTS) lastTS=ts;
const dt=Math.min(.05,(ts-lastTS)/1000);
lastTS=ts;
aTime+=dt*sMul;
// 更新粒子
for(const p of parts) updP(p,dt);
// 绘制
drawBg();
drawJetGlow();
drawNegPress(aTime);
drawFlowArrows(aTime);
drawParticles();
drawStruct();
drawLabels();
drawGraph(aTime);
requestAnimationFrame(animate);
}
/* ========== 事件 ========== */
document.getElementById('pressureSlider').addEventListener('input',e=>{
pMul=e.target.value/100;
document.getElementById('pVal').textContent=pMul.toFixed(1);
});
document.getElementById('speedSlider').addEventListener('input',e=>{
sMul=e.target.value/100;
document.getElementById('sVal').textContent=sMul.toFixed(1)+'x';
});
/* ========== 初始化 ========== */
(function init(){
for(let i=0;i<NP;i++) parts.push(mkP('p'));
for(let i=0;i<NS;i++) parts.push(mkP('s'));
requestAnimationFrame(animate);
})();
</script>
</body>
</html>
实现说明:
本动画聚焦展示环形引射结构(基于射流卷吸效应)的理想工作状态,核心视觉逻辑如下:
结构可视化:以剖面图精确呈现 Laval 喷管(中心缩放喷管)+ 环形次风道 + 混合段 + 扩压段的完整构型,喷管壁面带剖面线纹理,外风道壁带高光,7° 扩张角与 1:4 截面比均有标注。
粒子流场:130 个青色主风粒子沿喷管内壁加速(喉部处速度最快,带发光效果),180 个琥珀色二次风粒子经环形通道被卷吸涌入,两股粒子在混合段逐渐融合为绿色——直观呈现"壁面摩擦→流体内部摩擦"的核心转化。
负压卷吸可视化:喷管出口处的脉动紫色光晕 + 旋转吸入箭头群,动态展示高速射流产生的负压场如何驱动二次风卷吸。
关键资源利用暗示:次风道壁面内侧绘制了琥珀色薄高亮线,视觉强调"二次风包裹高速射流、隔离壁面"的降噪机理——这正是 IFR 思想中"以场(流体场)代物(壁面接触)"的体现。
流速/压力分布图:下方图表同步展示沿轴向的流速(青)与压力(琥珀)分布,负压极值点用紫色标注,动态指示点随时间移动。
交互控制:主风压力滑块实时改变粒子速度、射流扩散率和负压强度;动画速度滑块可慢放观察细节。
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
