分享图
动画工坊
引擎就绪
<!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>输出&nbsp;&nbsp;|&nbsp;&nbsp;核心转化:壁面摩擦 → 流体内部摩擦
</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>

实现说明:

本动画聚焦展示环形引射结构(基于射流卷吸效应)的理想工作状态,核心视觉逻辑如下:

  1. 结构可视化:以剖面图精确呈现 Laval 喷管(中心缩放喷管)+ 环形次风道 + 混合段 + 扩压段的完整构型,喷管壁面带剖面线纹理,外风道壁带高光,7° 扩张角与 1:4 截面比均有标注。

  2. 粒子流场:130 个青色主风粒子沿喷管内壁加速(喉部处速度最快,带发光效果),180 个琥珀色二次风粒子经环形通道被卷吸涌入,两股粒子在混合段逐渐融合为绿色——直观呈现"壁面摩擦→流体内部摩擦"的核心转化。

  3. 负压卷吸可视化:喷管出口处的脉动紫色光晕 + 旋转吸入箭头群,动态展示高速射流产生的负压场如何驱动二次风卷吸。

  4. 关键资源利用暗示:次风道壁面内侧绘制了琥珀色薄高亮线,视觉强调"二次风包裹高速射流、隔离壁面"的降噪机理——这正是 IFR 思想中"以场(流体场)代物(壁面接触)"的体现。

  5. 流速/压力分布图:下方图表同步展示沿轴向的流速(青)与压力(琥珀)分布,负压极值点用紫色标注,动态指示点随时间移动。

  6. 交互控制:主风压力滑块实时改变粒子速度、射流扩散率和负压强度;动画速度滑块可慢放观察细节。

积分规则:第一轮对话扣减8分,后续每轮扣6分