独立渲染引擎就绪引擎就绪
<!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>康达效应 & 引射原理 · 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.5–4mm):影响射流速度与缝隙可视间隙
- 射流压力滑块(30–100%):影响引射强度、粒子速度和低压区范围
视觉引导
- 青色=主流低速、橙色=高速射流、蓝色=低压区、绿色=关键创新点/混合加速
- 康达效应箭头沿中心体表面绘制,直观展示射流贴壁效应
- 出口速度剖面箭头展示最终加速效果
- 流线用动态虚线偏移模拟流动方向与速度
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
