分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>环形缝隙引射加速 — IFR原理动画</title>
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@600;700;800&family=IBM+Plex+Mono:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
:root{--bg:#050910;--fg:#c4d4e8;--muted:#3e5168;--accent:#00ffaa;--jet:#ff6b2b;--flow:#00c8ff;--lowp:#3355dd;--card:#0a1220;--border:#14253a}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--fg);font-family:'IBM Plex Mono',monospace;min-height:100vh;display:flex;flex-direction:column;align-items:center;padding:24px 16px;overflow-x:hidden}
.page-title{text-align:center;margin-bottom:18px}
.page-title h1{font-family:'Syne',sans-serif;font-weight:800;font-size:clamp(1.3rem,3vw,2.1rem);color:#e8f0fa;letter-spacing:-.02em;background:linear-gradient(135deg,#e8f0fa 40%,var(--accent));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.page-title p{font-size:.78rem;color:var(--muted);margin-top:6px;letter-spacing:.03em}
.canvas-wrap{width:100%;max-width:1120px;aspect-ratio:2/1;background:var(--card);border:1px solid var(--border);border-radius:14px;overflow:hidden;position:relative;box-shadow:0 0 60px rgba(0,200,255,.06),0 0 120px rgba(0,255,170,.03)}
canvas{width:100%;height:100%;display:block}
.controls{display:flex;gap:28px;margin-top:18px;flex-wrap:wrap;justify-content:center;align-items:flex-start}
.ctrl{display:flex;flex-direction:column;gap:5px;min-width:180px}
.ctrl label{font-size:.68rem;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;font-weight:500}
.ctrl input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:6px;border-radius:3px;background:#14253a;outline:none;cursor:pointer}
.ctrl input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;border-radius:50%;background:var(--accent);box-shadow:0 0 8px var(--accent);cursor:pointer}
.ctrl .val{font-size:.76rem;color:var(--accent);font-weight:500}
.phases{display:flex;gap:16px;margin-top:14px;flex-wrap:wrap;justify-content:center}
.ph{display:flex;align-items:center;gap:5px;font-size:.7rem;color:var(--muted);transition:color .4s}
.ph.on{color:var(--accent)}
.ph .dot{width:7px;height:7px;border-radius:50%;background:var(--muted);transition:all .4s}
.ph.on .dot{background:var(--accent);box-shadow:0 0 10px var(--accent)}
.info{max-width:1120px;width:100%;margin-top:16px;padding:14px 18px;background:var(--card);border:1px solid var(--border);border-radius:8px;font-size:.72rem;line-height:1.7;color:var(--muted)}
.info strong{color:var(--fg);font-weight:600}
.info em{color:var(--accent);font-style:normal;font-weight:500}
.legend{display:flex;gap:18px;margin-top:12px;flex-wrap:wrap;justify-content:center}
.legend-item{display:flex;align-items:center;gap:5px;font-size:.68rem;color:var(--muted)}
.legend-item .swatch{width:20px;height:4px;border-radius:2px}
</style>
</head>
<body>

<div class="page-title">
  <h1>环形缝隙引射加速原理</h1>
  <p>康达效应 &amp; 引射原理 · TRIZ 最终理想解 (IFR) 视角</p>
</div>

<div class="canvas-wrap">
  <canvas id="c"></canvas>
</div>

<div class="controls">
  <div class="ctrl">
    <label>缝隙出口宽度</label>
    <input type="range" id="slitW" min="1.5" max="4" step="0.1" value="2.5">
    <span class="val" id="slitWV">2.5 mm</span>
  </div>
  <div class="ctrl">
    <label>射流驱动压力</label>
    <input type="range" id="jetP" min="0.3" max="1.0" step="0.05" value="0.75">
    <span class="val" id="jetPV">75%</span>
  </div>
</div>

<div class="phases">
  <div class="ph on" id="ph0"><span class="dot"></span>建立射流</div>
  <div class="ph" id="ph1"><span class="dot"></span>形成负压</div>
  <div class="ph" id="ph2"><span class="dot"></span>卷吸加速</div>
  <div class="ph" id="ph3"><span class="dot"></span>稳态运行</div>
</div>

<div class="legend">
  <div class="legend-item"><span class="swatch" style="background:var(--flow)"></span>主流低速气流</div>
  <div class="legend-item"><span class="swatch" style="background:var(--jet)"></span>环形缝隙高速射流</div>
  <div class="legend-item"><span class="swatch" style="background:var(--lowp)"></span>低压引射区</div>
  <div class="legend-item"><span class="swatch" style="background:var(--accent)"></span>混合加速流</div>
</div>

<div class="info">
  <strong>IFR 核心思想:</strong>将 <em>"全截面节流加速"</em> 转化为 <em>"局部射流卷吸加速"</em>——不缩小流道截面,而是利用风机已有的压力储备驱动环形缝隙射流,通过康达效应与引射原理在中心形成负压区,卷吸大量低速空气实现整体加速。宏观上消除了截面突变,从根源上消除大面积涡流噪声,大通道同时保证抗堵塞能力。
</div>

<script>
/* ==============================
   环形缝隙引射加速 IFR 原理动画
   ============================== */

const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');

/* --- 虚拟坐标 --- */
const VW = 1200, VH = 600;
let dpr = 1, dispW, dispH, sx, sy;

function resize() {
  const r = canvas.parentElement.getBoundingClientRect();
  dpr = window.devicePixelRatio || 1;
  dispW = r.width; dispH = r.height;
  canvas.width = dispW * dpr;
  canvas.height = dispH * dpr;
  sx = dispW / VW; sy = dispH / VH;
}
window.addEventListener('resize', resize);
resize();

/* --- 参数 --- */
let slitW = 2.5, jetP = 0.75;
const slitSlider = document.getElementById('slitW');
const jetSlider = document.getElementById('jetP');
slitSlider.addEventListener('input', e => { slitW = +e.target.value; document.getElementById('slitWV').textContent = slitW.toFixed(1)+' mm'; });
jetSlider.addEventListener('input', e => { jetP = +e.target.value; document.getElementById('jetPV').textContent = Math.round(jetP*100)+'%'; });

/* --- 相位系统 --- */
const PHASE_DUR = [2800, 2200, 2800, Infinity];
let phase = 0, phaseT = 0, globalT = 0, lastTS = 0;
const phaseEls = [0,1,2,3].map(i => document.getElementById('ph'+i));

function setPhase(p) {
  phase = p;
  phaseEls.forEach((el,i) => el.classList.toggle('on', i <= p));
}

/* --- 几何定义 --- */
const DUCT = { l:70, r:1130, t:100, b:500, cy:300 };
const SLIT_X = 570;
const BODY_X0 = 280, BODY_X1 = 830;

/* 中心体表面函数(上半) */
function bodyTopY(x) {
  if (x < BODY_X0 || x > BODY_X1) return DUCT.cy;
  const t = (x - BODY_X0) / (BODY_X1 - BODY_X0);
  const st = (SLIT_X - BODY_X0) / (BODY_X1 - BODY_X0);
  const maxH = 70;
  let h;
  if (t < 0.12) h = maxH * Math.pow(t / 0.12, 0.55);
  else if (t < st - 0.015) h = maxH * (1 - 0.025 * Math.sin((t - 0.12) / (st - 0.135) * Math.PI));
  else if (t < st + 0.015) h = maxH * 0.92;
  else h = maxH * 0.92 * Math.pow(Math.max(0, 1 - (t - st - 0.015) / (1 - st - 0.015)), 0.65);
  return DUCT.cy - h;
}
function bodyBotY(x) { return 2 * DUCT.cy - bodyTopY(x); }

/* --- 速度场 --- */
function flowVel(x, y) {
  let vx = 1.0, vy = 0;
  const dy = y - DUCT.cy;
  const absDy = Math.abs(dy);
  const pFactor = jetP * (phase >= 2 ? 1 : phase === 1 ? 0.4 : phase === 0 ? 0.15 : 0);

  /* 引射:缝隙附近向中心吸引 */
  if (x > SLIT_X - 120 && x < SLIT_X + 280) {
    const prox = 1 - Math.min(1, Math.abs(x - SLIT_X - 40) / 200);
    vy += -dy * 0.0045 * prox * pFactor;
  }

  /* 射流加速:贴近中心体表面 */
  if (x > SLIT_X - 10 && x < SLIT_X + 320) {
    const bTy = bodyTopY(x), bBy = bodyBotY(x);
    if (dy < 0 && y > bTy - 35 && y < bTy + 5) {
      const d = Math.max(0, 1 - (bTy - y) / 35);
      vx += 3.5 * d * pFactor;
    }
    if (dy > 0 && y < bBy + 35 && y > bBy - 5) {
      const d = Math.max(0, 1 - (y - bBy) / 35);
      vx += 3.5 * d * pFactor;
    }
  }

  /* 混合区加速 */
  if (x > SLIT_X + 120) {
    const mp = Math.min(1, (x - SLIT_X - 120) / 350);
    vx += 0.7 * mp * pFactor;
  }

  /* 避开中心体 */
  if (x > BODY_X0 && x < BODY_X1) {
    const bTy = bodyTopY(x), bBy = bodyBotY(x);
    if (y > bTy - 2 && y < bBy + 2) {
      vy += (y < DUCT.cy ? -0.8 : 0.8);
      vx *= 0.4;
    }
  }

  /* 上下壁面排斥 */
  if (y < DUCT.t + 20) vy += (DUCT.t + 20 - y) * 0.02;
  if (y > DUCT.b - 20) vy += (DUCT.b - 20 - y) * 0.02;

  return { vx, vy };
}

/* --- 流线生成 --- */
function traceStreamline(sx0, sy0, steps, ds) {
  const pts = [{ x: sx0, y: sy0 }];
  let x = sx0, y = sy0;
  for (let i = 0; i < steps; i++) {
    const v = flowVel(x, y);
    const spd = Math.sqrt(v.vx * v.vx + v.vy * v.vy) || 0.01;
    x += v.vx / spd * ds;
    y += v.vy / spd * ds;
    pts.push({ x, y });
    if (x > DUCT.r + 10 || x < DUCT.l - 10 || y < DUCT.t - 10 || y > DUCT.b + 10) break;
  }
  return pts;
}

/* --- 粒子系统 --- */
const particles = [];
const MAX_P = 160;

class Particle {
  constructor() { this.alive = false; }
  spawn(type) {
    this.type = type;
    this.alive = true;
    this.age = 0;
    if (type === 'main') {
      this.x = DUCT.l + Math.random() * 10;
      const bTy = bodyTopY(this.x), bBy = bodyBotY(this.x);
      if (Math.random() < 0.5) this.y = DUCT.t + 15 + Math.random() * Math.max(5, bTy - DUCT.t - 30);
      else this.y = bBy + 15 + Math.random() * Math.max(5, DUCT.b - bBy - 30);
      this.speed = 0.7 + Math.random() * 0.5;
      this.size = 1.2 + Math.random() * 1.3;
      this.maxAge = 600 + Math.random() * 400;
      this.hue = 190 + Math.random() * 15;
    } else {
      /* 射流粒子 */
      const slitGap = 4 + (slitW - 1.5) * 2.5;
      this.x = SLIT_X + Math.random() * 6;
      if (Math.random() < 0.5) {
        this.y = bodyTopY(SLIT_X) - Math.random() * slitGap;
      } else {
        this.y = bodyBotY(SLIT_X) + Math.random() * slitGap;
      }
      this.speed = 2.8 + Math.random() * 1.8;
      this.size = 1.0 + Math.random() * 1.2;
      this.maxAge = 220 + Math.random() * 180;
      this.hue = 18 + Math.random() * 14;
    }
  }
  update(dt) {
    if (!this.alive) return;
    this.age += dt;
    if (this.age > this.maxAge || this.x > DUCT.r + 15 || this.x < DUCT.l - 15) { this.alive = false; return; }
    const v = flowVel(this.x, this.y);
    const spd = Math.sqrt(v.vx * v.vx + v.vy * v.vy) || 0.01;
    const factor = this.speed * (this.type === 'jet' ? jetP : 1) * dt * 0.065;
    this.x += v.vx / spd * factor * spd;
    this.y += v.vy / spd * factor * spd;
  }
  draw(ctx) {
    if (!this.alive) return;
    let alpha = 1;
    if (this.age < 120) alpha = this.age / 120;
    if (this.age > this.maxAge - 80) alpha = Math.max(0, (this.maxAge - this.age) / 80);
    const v = flowVel(this.x, this.y);
    const spd = Math.sqrt(v.vx * v.vx + v.vy * v.vy);
    /* 射流粒子逐渐混合变色 */
    let hue = this.hue;
    if (this.type === 'jet' && this.x > SLIT_X + 100) {
      const mix = Math.min(1, (this.x - SLIT_X - 100) / 250);
      hue = this.hue + mix * (170 - this.hue);
    }
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
    ctx.fillStyle = `hsla(${hue},100%,${this.type==='jet'?58:55}%,${alpha * 0.85})`;
    ctx.fill();
    /* 速度越高发光越强 */
    if (spd > 2) {
      ctx.beginPath();
      ctx.arc(this.x, this.y, this.size * 2.5, 0, Math.PI * 2);
      ctx.fillStyle = `hsla(${hue},100%,70%,${alpha * 0.12})`;
      ctx.fill();
    }
  }
}
for (let i = 0; i < MAX_P; i++) particles.push(new Particle());

function spawnParticles() {
  const mainRate = phase >= 2 ? 3 : phase >= 1 ? 2 : 1;
  const jetRate = phase >= 0 ? Math.round(2 * jetP) : 0;
  for (let i = 0; i < mainRate; i++) {
    const p = particles.find(p => !p.alive);
    if (p) p.spawn('main');
  }
  for (let i = 0; i < jetRate; i++) {
    const p = particles.find(p => !p.alive);
    if (p) p.spawn('jet');
  }
}

/* --- 绘图函数 --- */

function drawBg() {
  /* 深色渐变背景 */
  const g = ctx.createLinearGradient(0, 0, 0, VH);
  g.addColorStop(0, '#070c16');
  g.addColorStop(0.5, '#0a1020');
  g.addColorStop(1, '#070c16');
  ctx.fillStyle = g;
  ctx.fillRect(0, 0, VW, VH);

  /* 微弱网格 */
  ctx.strokeStyle = 'rgba(40,70,100,0.08)';
  ctx.lineWidth = 0.5;
  for (let x = 0; x < VW; x += 40) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, VH); ctx.stroke(); }
  for (let y = 0; y < VH; y += 40) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(VW, y); ctx.stroke(); }
}

function drawDuct() {
  /* 风道壁面 */
  const wallH = 18;
  const wg = ctx.createLinearGradient(0, DUCT.t - wallH, 0, DUCT.t);
  wg.addColorStop(0, '#1a2a3e');
  wg.addColorStop(1, '#253a52');
  ctx.fillStyle = wg;
  ctx.fillRect(DUCT.l, DUCT.t - wallH, DUCT.r - DUCT.l, wallH);
  const wg2 = ctx.createLinearGradient(0, DUCT.b, 0, DUCT.b + wallH);
  wg2.addColorStop(0, '#253a52');
  wg2.addColorStop(1, '#1a2a3e');
  ctx.fillStyle = wg2;
  ctx.fillRect(DUCT.l, DUCT.b, DUCT.r - DUCT.l, wallH);

  /* 内壁面高亮线 */
  ctx.strokeStyle = '#3a6080';
  ctx.lineWidth = 1.5;
  ctx.beginPath(); ctx.moveTo(DUCT.l, DUCT.t); ctx.lineTo(DUCT.r, DUCT.t); ctx.stroke();
  ctx.beginPath(); ctx.moveTo(DUCT.l, DUCT.b); ctx.lineTo(DUCT.r, DUCT.b); ctx.stroke();

  /* 入口/出口标识 */
  ctx.fillStyle = '#2a4a60';
  ctx.fillRect(DUCT.l - 8, DUCT.t - wallH, 8, DUCT.b - DUCT.t + wallH * 2);
  ctx.fillRect(DUCT.r, DUCT.t - wallH, 8, DUCT.b - DUCT.t + wallH * 2);
}

function drawBody() {
  /* 中心引射核心体 */
  ctx.beginPath();
  const step = 2;
  /* 上表面 - 从左到右 */
  for (let x = BODY_X0; x <= BODY_X1; x += step) {
    const y = bodyTopY(x);
    x === BODY_X0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
  }
  /* 下表面 - 从右到左 */
  for (let x = BODY_X1; x >= BODY_X0; x -= step) {
    ctx.lineTo(x, bodyBotY(x));
  }
  ctx.closePath();

  const bg = ctx.createLinearGradient(0, DUCT.cy - 70, 0, DUCT.cy + 70);
  bg.addColorStop(0, '#1c2e44');
  bg.addColorStop(0.3, '#243a54');
  bg.addColorStop(0.5, '#283f5a');
  bg.addColorStop(0.7, '#243a54');
  bg.addColorStop(1, '#1c2e44');
  ctx.fillStyle = bg;
  ctx.fill();
  ctx.strokeStyle = '#3e6888';
  ctx.lineWidth = 1.2;
  ctx.stroke();

  /* 内部高压通道(半透明显示) */
  ctx.beginPath();
  for (let x = BODY_X0 + 40; x <= SLIT_X - 5; x += step) {
    const yTop = bodyTopY(x) + 10;
    x === BODY_X0 + 40 ? ctx.moveTo(x, yTop) : ctx.lineTo(x, yTop);
  }
  for (let x = SLIT_X - 5; x >= BODY_X0 + 40; x -= step) {
    ctx.lineTo(x, bodyBotY(x) - 10);
  }
  ctx.closePath();
  ctx.fillStyle = 'rgba(255,107,43,0.06)';
  ctx.fill();

  /* HP 箭头 */
  const arrowY = DUCT.cy;
  ctx.strokeStyle = 'rgba(255,107,43,0.25)';
  ctx.lineWidth = 1.5;
  ctx.setLineDash([6, 4]);
  for (let ax = BODY_X0 + 80; ax < SLIT_X - 20; ax += 60) {
    ctx.beginPath();
    ctx.moveTo(ax, arrowY - 4); ctx.lineTo(ax + 20, arrowY - 4);
    ctx.moveTo(ax, arrowY + 4); ctx.lineTo(ax + 20, arrowY + 4);
    ctx.stroke();
    /* 箭头头 */
    ctx.beginPath();
    ctx.moveTo(ax + 20, arrowY - 8); ctx.lineTo(ax + 26, arrowY - 4); ctx.lineTo(ax + 20, arrowY);
    ctx.moveTo(ax + 20, arrowY); ctx.lineTo(ax + 26, arrowY + 4); ctx.lineTo(ax + 20, arrowY + 8);
    ctx.stroke();
  }
  ctx.setLineDash([]);

  /* 缝隙高亮 */
  const slitGlow = 0.5 + 0.3 * Math.sin(globalT * 0.003);
  const sg = ctx.createLinearGradient(SLIT_X - 6, 0, SLIT_X + 6, 0);
  sg.addColorStop(0, `rgba(0,255,170,0)`);
  sg.addColorStop(0.5, `rgba(0,255,170,${slitGlow * 0.7})`);
  sg.addColorStop(1, `rgba(0,255,170,0)`);
  ctx.fillStyle = sg;
  ctx.fillRect(SLIT_X - 6, bodyTopY(SLIT_X), 12, bodyBotY(SLIT_X) - bodyTopY(SLIT_X));

  /* 缝隙出口标记线 */
  const slitGap = 4 + (slitW - 1.5) * 2.5;
  ctx.strokeStyle = `rgba(0,255,170,${0.6 + slitGlow * 0.3})`;
  ctx.lineWidth = 2;
  /* 上缝隙 */
  ctx.beginPath();
  ctx.moveTo(SLIT_X, bodyTopY(SLIT_X));
  ctx.lineTo(SLIT_X, bodyTopY(SLIT_X) - slitGap);
  ctx.stroke();
  /* 下缝隙 */
  ctx.beginPath();
  ctx.moveTo(SLIT_X, bodyBotY(SLIT_X));
  ctx.lineTo(SLIT_X, bodyBotY(SLIT_X) + slitGap);
  ctx.stroke();
}

function drawPressureField() {
  if (phase < 1) return;
  const intensity = phase === 1 ? Math.min(1, phaseT / 1500) : 1;
  /* 缝隙后方的低压区 */
  const lpx = SLIT_X + 80, lpy = DUCT.cy;
  const rg = ctx.createRadialGradient(lpx, lpy, 10, lpx, lpy, 140);
  rg.addColorStop(0, `rgba(51,85,221,${0.22 * intensity * jetP})`);
  rg.addColorStop(0.5, `rgba(51,85,221,${0.08 * intensity * jetP})`);
  rg.addColorStop(1, 'rgba(51,85,221,0)');
  ctx.fillStyle = rg;
  ctx.fillRect(lpx - 160, lpy - 160, 320, 320);

  /* 脉动 */
  const pulse = 0.6 + 0.4 * Math.sin(globalT * 0.004);
  const rg2 = ctx.createRadialGradient(lpx, lpy, 5, lpx, lpy, 60);
  rg2.addColorStop(0, `rgba(80,120,255,${0.12 * intensity * pulse * jetP})`);
  rg2.addColorStop(1, 'rgba(80,120,255,0)');
  ctx.fillStyle = rg2;
  ctx.fillRect(lpx - 80, lpy - 80, 160, 160);
}

function drawStreamlines() {
  if (phase < 2) return;
  const alpha = Math.min(1, phaseT / 2000) * 0.35;

  /* 预计算流线 */
  const starts = [];
  for (let y = DUCT.t + 25; y < bodyTopY(DUCT.l) - 10; y += 28) starts.push({ x: DUCT.l + 5, y });
  for (let y = bodyBotY(DUCT.l) + 10; y < DUCT.b - 25; y += 28) starts.push({ x: DUCT.l + 5, y });

  starts.forEach((s, idx) => {
    const pts = traceStreamline(s.x, s.y, 350, 3.5);
    if (pts.length < 5) return;
    ctx.beginPath();
    ctx.moveTo(pts[0].x, pts[0].y);
    for (let i = 1; i < pts.length; i++) ctx.lineTo(pts[i].x, pts[i].y);
    ctx.strokeStyle = `rgba(0,200,255,${alpha})`;
    ctx.lineWidth = 0.8;
    ctx.setLineDash([8, 12]);
    ctx.lineDashOffset = -globalT * 0.04 * (1 + jetP * 0.5);
    ctx.stroke();
    ctx.setLineDash([]);
  });
}

function drawJetStreamlines() {
  if (phase < 0) return;
  const alpha = phase === 0 ? Math.min(0.5, phaseT / 2000) : phase === 1 ? 0.5 : 0.4;
  const slitGap = 4 + (slitW - 1.5) * 2.5;

  /* 上射流流线 */
  [bodyTopY(SLIT_X) - slitGap * 0.3, bodyTopY(SLIT_X) - slitGap * 0.7].forEach(sy => {
    const pts = traceStreamline(SLIT_X, sy, 200, 3);
    if (pts.length < 3) return;
    ctx.beginPath();
    ctx.moveTo(pts[0].x, pts[0].y);
    for (let i = 1; i < pts.length; i++) ctx.lineTo(pts[i].x, pts[i].y);
    ctx.strokeStyle = `rgba(255,107,43,${alpha})`;
    ctx.lineWidth = 1.2;
    ctx.setLineDash([6, 10]);
    ctx.lineDashOffset = -globalT * 0.08 * jetP;
    ctx.stroke();
    ctx.setLineDash([]);
  });

  /* 下射流流线 */
  [bodyBotY(SLIT_X) + slitGap * 0.3, bodyBotY(SLIT_X) + slitGap * 0.7].forEach(sy => {
    const pts = traceStreamline(SLIT_X, sy, 200, 3);
    if (pts.length < 3) return;
    ctx.beginPath();
    ctx.moveTo(pts[0].x, pts[0].y);
    for (let i = 1; i < pts.length; i++) ctx.lineTo(pts[i].x, pts[i].y);
    ctx.strokeStyle = `rgba(255,107,43,${alpha})`;
    ctx.lineWidth = 1.2;
    ctx.setLineDash([6, 10]);
    ctx.lineDashOffset = -globalT * 0.08 * jetP;
    ctx.stroke();
    ctx.setLineDash([]);
  });
}

function drawCoandaArrows() {
  if (phase < 1) return;
  const alpha = Math.min(1, phaseT / 1500) * 0.5;
  /* 沿中心体表面绘制康达效应弧线箭头 */
  ctx.strokeStyle = `rgba(0,255,170,${alpha})`;
  ctx.lineWidth = 1.5;
  ctx.setLineDash([]);

  /* 上表面 - 从缝隙沿表面绘制弧形箭头 */
  for (let offset = 30; offset < 180; offset += 55) {
    const ax = SLIT_X + offset;
    const ay = bodyTopY(ax) - 8;
    const nx = SLIT_X + offset + 25;
    const ny = bodyTopY(nx) - 8;
    ctx.beginPath();
    ctx.moveTo(ax, ay);
    ctx.lineTo(nx, ny);
    ctx.stroke();
    /* 箭头 */
    const angle = Math.atan2(ny - ay, nx - ax);
    ctx.beginPath();
    ctx.moveTo(nx, ny);
    ctx.lineTo(nx - 6 * Math.cos(angle - 0.5), ny - 6 * Math.sin(angle - 0.5));
    ctx.moveTo(nx, ny);
    ctx.lineTo(nx - 6 * Math.cos(angle + 0.5), ny - 6 * Math.sin(angle + 0.5));
    ctx.stroke();
  }
  /* 下表面 */
  for (let offset = 30; offset < 180; offset += 55) {
    const ax = SLIT_X + offset;
    const ay = bodyBotY(ax) + 8;
    const nx = SLIT_X + offset + 25;
    const ny = bodyBotY(nx) + 8;
    ctx.beginPath();
    ctx.moveTo(ax, ay);
    ctx.lineTo(nx, ny);
    ctx.stroke();
    const angle = Math.atan2(ny - ay, nx - ax);
    ctx.beginPath();
    ctx.moveTo(nx, ny);
    ctx.lineTo(nx - 6 * Math.cos(angle - 0.5), ny - 6 * Math.sin(angle - 0.5));
    ctx.moveTo(nx, ny);
    ctx.lineTo(nx - 6 * Math.cos(angle + 0.5), ny - 6 * Math.sin(angle + 0.5));
    ctx.stroke();
  }
}

function drawLabels() {
  ctx.font = '600 11px "IBM Plex Mono", monospace';
  ctx.textAlign = 'center';

  /* 外环宽通道标注 */
  ctx.fillStyle = 'rgba(0,200,255,0.55)';
  ctx.fillText('外环宽通道 (1.5x)', 420, DUCT.t + 40);
  ctx.fillText('外环宽通道 (1.5x)', 420, DUCT.b - 32);

  /* 中心引射核心 */
  ctx.fillStyle = 'rgba(200,220,240,0.45)';
  ctx.fillText('引射核心', 440, DUCT.cy + 4);

  /* 缝隙喷嘴 */
  const pulse = 0.7 + 0.3 * Math.sin(globalT * 0.003);
  ctx.fillStyle = `rgba(0,255,170,${0.6 * pulse})`;
  ctx.fillText('环形缝隙喷嘴', SLIT_X, DUCT.t - 30);
  /* 连接线 */
  ctx.strokeStyle = `rgba(0,255,170,${0.25 * pulse})`;
  ctx.lineWidth = 0.8;
  ctx.setLineDash([3, 3]);
  ctx.beginPath();
  ctx.moveTo(SLIT_X, DUCT.t - 22);
  ctx.lineTo(SLIT_X, bodyTopY(SLIT_X) - 15);
  ctx.stroke();
  ctx.setLineDash([]);

  /* 低压区标注 */
  if (phase >= 1) {
    const a = Math.min(1, phaseT / 1500) * 0.7;
    ctx.fillStyle = `rgba(80,120,255,${a})`;
    ctx.fillText('低压引射区', SLIT_X + 80, DUCT.cy - 50);
    ctx.fillText('低压引射区', SLIT_X + 80, DUCT.cy + 56);
  }

  /* 康达效应标注 */
  if (phase >= 1) {
    const a = Math.min(1, phaseT / 1500) * 0.55;
    ctx.fillStyle = `rgba(0,255,170,${a})`;
    ctx.font = '500 10px "IBM Plex Mono", monospace';
    ctx.fillText('康达效应', SLIT_X + 110, bodyTopY(SLIT_X + 110) - 22);
    ctx.fillText('康达效应', SLIT_X + 110, bodyBotY(SLIT_X + 110) + 30);
  }

  /* 混合加速出口 */
  if (phase >= 2) {
    const a = Math.min(1, phaseT / 2000) * 0.6;
    ctx.fillStyle = `rgba(0,255,170,${a})`;
    ctx.font = '600 11px "IBM Plex Mono", monospace';
    ctx.fillText('混合加速流', DUCT.r - 100, DUCT.cy - 60);
    ctx.fillText('混合加速流', DUCT.r - 100, DUCT.cy + 68);
  }

  /* HP入口标注 */
  ctx.fillStyle = 'rgba(255,107,43,0.4)';
  ctx.font = '500 9px "IBM Plex Mono", monospace';
  ctx.fillText('HP气源 →', BODY_X0 + 60, DUCT.cy + 20);

  /* 入口/出口 */
  ctx.fillStyle = 'rgba(200,220,240,0.3)';
  ctx.font = '500 10px "IBM Plex Mono", monospace';
  ctx.save();
  ctx.translate(DUCT.l - 20, DUCT.cy);
  ctx.rotate(-Math.PI / 2);
  ctx.fillText('入口', 0, 0);
  ctx.restore();
  ctx.save();
  ctx.translate(DUCT.r + 22, DUCT.cy);
  ctx.rotate(Math.PI / 2);
  ctx.fillText('出口', 0, 0);
  ctx.restore();
}

function drawPhaseOverlay() {
  /* 相位进度文字 */
  const phaseNames = ['建立环形射流', '形成中心负压', '卷吸加速主流', '稳态运行中'];
  ctx.font = '500 12px "IBM Plex Mono", monospace';
  ctx.textAlign = 'left';
  ctx.fillStyle = 'rgba(0,255,170,0.5)';
  ctx.fillText('▶ ' + phaseNames[phase], 20, 28);
}

function drawVelocityProfile() {
  /* 在出口处绘制速度剖面 */
  if (phase < 2) return;
  const alpha = Math.min(1, phaseT / 2000) * 0.6;
  const px = DUCT.r - 30;

  ctx.strokeStyle = `rgba(0,255,170,${alpha})`;
  ctx.lineWidth = 1.5;
  ctx.beginPath();

  for (let y = DUCT.t + 10; y < DUCT.b - 10; y += 3) {
    const v = flowVel(px, y);
    const spd = Math.sqrt(v.vx * v.vx + v.vy * v.vy);
    const barLen = spd * 12;
    const drawX = px + barLen;
    y === DUCT.t + 10 ? ctx.moveTo(px, y) : ctx.lineTo(px, y);
  }
  ctx.stroke();

  /* 速度矢量小箭头 */
  for (let y = DUCT.t + 30; y < DUCT.b - 30; y += 35) {
    const v = flowVel(px, y);
    const spd = Math.sqrt(v.vx * v.vx + v.vy * v.vy);
    const barLen = spd * 12;
    ctx.beginPath();
    ctx.moveTo(px, y);
    ctx.lineTo(px + barLen, y);
    ctx.strokeStyle = `rgba(0,255,170,${alpha * 0.8})`;
    ctx.lineWidth = 1.2;
    ctx.stroke();
    /* 箭头头 */
    ctx.beginPath();
    ctx.moveTo(px + barLen, y);
    ctx.lineTo(px + barLen - 4, y - 3);
    ctx.moveTo(px + barLen, y);
    ctx.lineTo(px + barLen - 4, y + 3);
    ctx.stroke();
  }

  ctx.font = '400 8px "IBM Plex Mono", monospace';
  ctx.fillStyle = `rgba(0,255,170,${alpha * 0.5})`;
  ctx.textAlign = 'center';
  ctx.fillText('速度剖面', px + 20, DUCT.b - 14);
}

/* --- 主动画循环 --- */
function frame(ts) {
  if (!lastTS) lastTS = ts;
  const dt = Math.min(ts - lastTS, 40);
  lastTS = ts;
  globalT += dt;
  phaseT += dt;

  /* 相位推进 */
  if (phase < 3 && phaseT > PHASE_DUR[phase]) {
    phaseT = 0;
    setPhase(phase + 1);
  }

  /* 生成粒子 */
  spawnParticles();

  /* 更新粒子 */
  particles.forEach(p => p.update(dt));

  /* 绘制 */
  ctx.save();
  ctx.setTransform(dpr * dispW / VW, 0, 0, dpr * dispH / VH, 0, 0);

  drawBg();
  drawPressureField();
  drawStreamlines();
  drawJetStreamlines();
  drawDuct();
  drawBody();
  drawCoandaArrows();
  particles.forEach(p => p.draw(ctx));
  drawVelocityProfile();
  drawLabels();
  drawPhaseOverlay();

  ctx.restore();
  requestAnimationFrame(frame);
}

/* --- 初始化 --- */
function init() {
  setPhase(0);
  phaseT = 0;
  globalT = 0;
  lastTS = 0;
  particles.forEach(p => p.alive = false);
  requestAnimationFrame(frame);
}

/* 页面加载和重载时自动开始 */
window.addEventListener('DOMContentLoaded', init);
/* 处理 iframe 重新加载等情况 */
document.addEventListener('visibilitychange', () => {
  if (!document.hidden) { lastTS = 0; }
});
</script>
</body>
</html>

这个动画实现了以下核心设计:

IFR 视角聚焦

  • 直接展示最终理想解的运行状态,无需前后对比
  • 突出"局部射流卷吸"替代"全截面节流"的核心创新——环形缝隙以绿色高亮脉动标识,是破除矛盾的关键视觉焦点

四阶段自动演进

  1. 建立射流:橙色粒子从缝隙喷出,建立高速环形射流
  2. 形成负压:缝隙后方出现蓝色脉动低压区
  3. 卷吸加速:青色主流粒子被明显吸引向中心并加速,虚线流线显现
  4. 稳态运行:全部流场稳定运行,出口速度剖面可视化

交互控制

  • 缝隙宽度滑块(1.5–4mm):影响射流速度与缝隙可视间隙
  • 射流压力滑块(30–100%):影响引射强度、粒子速度和低压区范围

视觉引导

  • 青色=主流低速、橙色=高速射流、蓝色=低压区、绿色=关键创新点/混合加速
  • 康达效应箭头沿中心体表面绘制,直观展示射流贴壁效应
  • 出口速度剖面箭头展示最终加速效果
  • 流线用动态虚线偏移模拟流动方向与速度
积分规则:第一轮对话扣减8分,后续每轮扣6分