独立渲染引擎就绪引擎就绪
<!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;900&family=Noto+Sans+SC:wght@300;400;600;700&display=swap" rel="stylesheet">
<style>
:root{--bg:#04080f;--fg:#d8e2f0;--muted:#4a6080;--accent:#00d4ff;--accent2:#00ff88;--warn:#ff8800;--card:rgba(6,14,30,0.92);--border:rgba(0,212,255,0.18)}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--fg);font-family:'Noto Sans SC',sans-serif;height:100vh;overflow:hidden;display:flex;flex-direction:column}
canvas{display:block;flex:1;width:100%}
.hud-top{position:fixed;top:0;left:0;right:0;padding:14px 28px;background:linear-gradient(180deg,rgba(4,8,15,.96) 0%,rgba(4,8,15,0) 100%);z-index:10;display:flex;align-items:baseline;gap:14px;pointer-events:none}
.hud-top h1{font-family:'Rajdhani',sans-serif;font-weight:900;font-size:20px;letter-spacing:3px;color:var(--accent);text-transform:uppercase}
.hud-top .sub{font-size:12px;color:var(--muted);font-weight:300;letter-spacing:.5px}
.legend{position:fixed;top:56px;right:20px;background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px 18px;z-index:10;backdrop-filter:blur(16px);pointer-events:none}
.legend-row{display:flex;align-items:center;gap:9px;margin-bottom:7px;font-size:11px;color:var(--fg);letter-spacing:.3px}
.legend-row:last-child{margin-bottom:0}
.ldot{width:9px;height:9px;border-radius:50%;flex-shrink:0}
.controls{position:fixed;bottom:18px;left:50%;transform:translateX(-50%);background:var(--card);border:1px solid var(--border);border-radius:14px;padding:16px 26px;display:flex;gap:28px;align-items:flex-end;backdrop-filter:blur(18px);z-index:10}
.cg{display:flex;flex-direction:column;gap:5px}
.cg label{font-size:10px;color:var(--muted);font-weight:700;letter-spacing:1.2px;text-transform:uppercase;white-space:nowrap}
.cg input[type=range]{-webkit-appearance:none;width:130px;height:3px;background:rgba(0,212,255,.15);border-radius:2px;outline:none}
.cg input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:13px;height:13px;background:var(--accent);border-radius:50%;cursor:pointer;box-shadow:0 0 8px rgba(0,212,255,.5)}
.cg .val{font-family:'Rajdhani',sans-serif;font-size:15px;font-weight:700;color:var(--accent);text-align:center;min-width:36px}
.tbtn{background:transparent;border:1px solid var(--border);color:var(--muted);padding:7px 14px;border-radius:7px;cursor:pointer;font-family:'Noto Sans SC',sans-serif;font-size:11px;transition:all .25s;white-space:nowrap}
.tbtn.on{border-color:var(--accent);color:var(--accent);background:rgba(0,212,255,.07);box-shadow:0 0 10px rgba(0,212,255,.12)}
.tbtn:hover{border-color:var(--accent)}
.inset{position:fixed;bottom:90px;right:20px;width:200px;height:150px;background:var(--card);border:1px solid var(--border);border-radius:10px;z-index:10;overflow:hidden;backdrop-filter:blur(16px)}
.inset canvas{width:100%;height:100%}
.inset-label{position:absolute;bottom:6px;left:10px;font-size:9px;color:var(--muted);letter-spacing:.8px;text-transform:uppercase;pointer-events:none}
</style>
</head>
<body>
<div class="hud-top">
<h1>IFR Micro-Vortex Air Bearing</h1>
<span class="sub">最终理想解:微涡流气膜轴承 — 降噪防粘原理</span>
</div>
<div class="legend">
<div class="legend-row"><div class="ldot" style="background:#00d4ff;box-shadow:0 0 5px #00d4ff"></div>主流场</div>
<div class="legend-row"><div class="ldot" style="background:#00ff88;box-shadow:0 0 5px #00ff88"></div>微涡旋 / 气膜轴承</div>
<div class="legend-row"><div class="ldot" style="background:#ff8800;box-shadow:0 0 5px #ff8800"></div>粉尘颗粒</div>
<div class="legend-row"><div class="ldot" style="background:rgba(0,200,255,.25);border:1px solid #00c8ff"></div>超疏水微纳壁面</div>
</div>
<canvas id="mainCanvas"></canvas>
<div class="inset">
<canvas id="insetCanvas"></canvas>
<div class="inset-label">超疏水壁面 · 接触角 >150°</div>
</div>
<div class="controls">
<div class="cg">
<label>涡流发生器迎角</label>
<input type="range" id="sAngle" min="5" max="30" value="15" step="1">
<span class="val" id="vAngle">15°</span>
</div>
<div class="cg">
<label>流速倍率</label>
<input type="range" id="sSpeed" min="0.3" max="2.5" value="1.0" step="0.1">
<span class="val" id="vSpeed">1.0x</span>
</div>
<div class="cg">
<label>粉尘密度</label>
<input type="range" id="sDust" min="0" max="60" value="25" step="1">
<span class="val" id="vDust">25</span>
</div>
<button class="tbtn on" id="btnFilm">气膜可视化</button>
<button class="tbtn on" id="btnLabel">标注层</button>
</div>
<script>
/* ========== 全局状态 ========== */
const mc = document.getElementById('mainCanvas');
const cx = mc.getContext('2d');
const ic = document.getElementById('insetCanvas');
const ix = ic.getContext('2d');
let W, H;
let time = 0;
let vortexAngle = 15;
let flowMul = 1.0;
let dustMax = 25;
let showFilm = true;
let showLabel = true;
/* ========== 通道几何 ========== */
let CH = {};
function calcGeom() {
W = mc.width = window.innerWidth;
H = mc.height = window.innerHeight;
ic.width = 200; ic.height = 150;
const cy = H * 0.48;
CH = {
wL: W * 0.04, // 宽段左
wR: W * 0.27, // 宽段右 / 收缩起点
wT: cy - H * 0.23, // 宽段顶壁
wB: cy + H * 0.23, // 宽段底壁
nL: W * 0.45, // 窄段左 / 收缩终点
nR: W * 0.96, // 窄段右
nT: cy - H * 0.13, // 窄段顶壁
nB: cy + H * 0.13, // 窄段底壁
vgX: W * 0.255, // 涡流发生器 X
cy: cy
};
}
/* 壁面 Y 坐标(smoothstep 插值)*/
function topY(x) {
if (x <= CH.wR) return CH.wT;
if (x >= CH.nL) return CH.nT;
const t = (x - CH.wR) / (CH.nL - CH.wR);
const s = t * t * (3 - 2 * t);
return CH.wT + s * (CH.nT - CH.wT);
}
function botY(x) {
if (x <= CH.wR) return CH.wB;
if (x >= CH.nL) return CH.nB;
const t = (x - CH.wR) / (CH.nL - CH.wR);
const s = t * t * (3 - 2 * t);
return CH.wB + s * (CH.nB - CH.wB);
}
function chanH(x) { return botY(x) - topY(x); }
function chanCY(x) { return (topY(x) + botY(x)) / 2; }
/* ========== 粒子系统 ========== */
/* 流线粒子 */
const FLOW_N = 260;
let flowP = [];
function mkFlow() {
const x = CH.wL + Math.random() * (CH.nR - CH.wL) * 0.2;
const ty = topY(x), by = botY(x);
const margin = (by - ty) * 0.06;
return {
x, y: ty + margin + Math.random() * (by - ty - margin * 2),
speed: 0.6 + Math.random() * 0.4,
size: 1 + Math.random() * 1.2,
alpha: 0.3 + Math.random() * 0.5
};
}
function initFlow() { flowP = []; for (let i = 0; i < FLOW_N; i++) flowP.push(mkFlow()); }
function updateFlow(dt) {
const base = 120 * flowMul;
const wideH = CH.wB - CH.wT;
for (let p of flowP) {
const h = chanH(p.x);
const vBase = base * (wideH / Math.max(h, 1));
const relY = (p.y - topY(p.x)) / h;
/* 壁面附近减速 */
const dWall = Math.min(relY, 1 - relY);
let blF = 1;
if (dWall < 0.12) blF = 0.35 + 0.65 * (dWall / 0.12);
/* 微涡旋扰动 */
let vortY = 0;
if (p.x > CH.vgX + 30 && dWall < 0.18 && dWall > 0) {
const str = (1 - dWall / 0.18) * 0.45 * (vortexAngle / 15) * flowMul;
vortY = Math.sin(time * 4 + p.x * 0.04 + relY * 6) * str * vBase * 0.15;
}
p.x += vBase * blF * p.speed * dt;
p.y += vortY * dt;
/* 流线约束:保持在通道内 */
const ty = topY(p.x) + h * 0.02;
const by = botY(p.x) - h * 0.02;
if (p.y < ty) p.y = ty;
if (p.y > by) p.y = by;
/* 超出右端则重生 */
if (p.x > CH.nR + 10) Object.assign(p, mkFlow());
}
}
/* 微涡旋指示器 */
let vortices = [];
function initVortices() {
vortices = [];
const startX = CH.nL + 30;
const endX = CH.nR - 60;
const count = 14;
for (let i = 0; i < count; i++) {
const x = startX + (endX - startX) * (i / (count - 1));
vortices.push({ x, phase: Math.random() * Math.PI * 2, r: 8 + Math.random() * 4 });
vortices.push({ x, phase: Math.random() * Math.PI * 2, r: 8 + Math.random() * 4 }); // 底壁
}
}
function drawVortices() {
const speed = 3 + flowMul * 2;
for (let i = 0; i < vortices.length; i++) {
const v = vortices[i];
const isTop = i % 2 === 0;
const wallY = isTop ? topY(v.x) : botY(v.x);
const cy = isTop ? wallY + chanH(v.x) * 0.07 : wallY - chanH(v.x) * 0.07;
const rot = time * speed + v.phase;
/* 绘制旋转螺旋 */
cx.save();
cx.translate(v.x, cy);
cx.rotate(rot * (isTop ? 1 : -1));
cx.globalAlpha = 0.7;
cx.strokeStyle = '#00ff88';
cx.lineWidth = 1.5;
cx.shadowColor = '#00ff88';
cx.shadowBlur = 8;
cx.beginPath();
for (let a = 0; a < Math.PI * 4; a += 0.15) {
const rr = v.r * (a / (Math.PI * 4));
const px = Math.cos(a) * rr;
const py = Math.sin(a) * rr;
a === 0 ? cx.moveTo(px, py) : cx.lineTo(px, py);
}
cx.stroke();
/* 中心亮点 */
cx.beginPath();
cx.arc(0, 0, 2, 0, Math.PI * 2);
cx.fillStyle = '#00ff88';
cx.shadowBlur = 14;
cx.fill();
cx.restore();
}
}
/* 粉尘粒子 */
let dustP = [];
function mkDust() {
const x = CH.wL + Math.random() * (CH.nR - CH.wL) * 0.5;
const ty = topY(x), by = botY(x);
const m = (by - ty) * 0.08;
return {
x, y: ty + m + Math.random() * (by - ty - m * 2),
vx: 0, vy: 0,
size: 2.5 + Math.random() * 2,
alpha: 0.6 + Math.random() * 0.4,
bounced: 0,
trail: []
};
}
function initDust() { dustP = []; for (let i = 0; i < dustMax; i++) dustP.push(mkDust()); }
function updateDust(dt) {
const base = 80 * flowMul;
const wideH = CH.wB - CH.wT;
while (dustP.length < dustMax) dustP.push(mkDust());
while (dustP.length > dustMax) dustP.pop();
for (let p of dustP) {
const h = chanH(p.x);
const relY = (p.y - topY(p.x)) / h;
const dWallTop = relY;
const dWallBot = 1 - relY;
const dWall = Math.min(dWallTop, dWallBot);
/* 基础水平速度 */
const vBase = base * (wideH / Math.max(h, 1));
p.vx = vBase * 0.7;
/* 涡旋离心力:在窄段近壁处,将颗粒推向中心 */
if (p.x > CH.vgX + 40 && dWall < 0.22) {
const str = (1 - dWall / 0.22) * 180 * (vortexAngle / 15) * flowMul;
const dir = dWallTop < dWallBot ? 1 : -1; // 指向中心
p.vy += dir * str * dt;
}
/* 微弱随机游走 */
p.vy += (Math.random() - 0.5) * 30 * dt;
p.vy *= 0.96; // 阻尼
p.x += p.vx * dt;
p.y += p.vy * dt;
/* 超疏水壁面反弹 */
const ty = topY(p.x) + 4;
const by = botY(p.x) - 4;
if (p.y < ty) {
p.y = ty + 1;
p.vy = Math.abs(p.vy) * 0.75;
p.bounced = 8;
}
if (p.y > by) {
p.y = by - 1;
p.vy = -Math.abs(p.vy) * 0.75;
p.bounced = 8;
}
if (p.bounced > 0) p.bounced -= dt * 30;
/* 记录轨迹 */
p.trail.push({ x: p.x, y: p.y });
if (p.trail.length > 18) p.trail.shift();
if (p.x > CH.nR + 20) Object.assign(p, mkDust());
}
}
/* ========== 绘制函数 ========== */
function drawBg() {
/* 深色渐变背景 */
const g = cx.createRadialGradient(W * 0.45, H * 0.48, H * 0.1, W * 0.45, H * 0.48, H * 0.9);
g.addColorStop(0, '#0b1528');
g.addColorStop(1, '#04080f');
cx.fillStyle = g;
cx.fillRect(0, 0, W, H);
/* 点阵网格 */
cx.fillStyle = 'rgba(0,180,255,0.04)';
const sp = 40;
for (let gx = sp; gx < W; gx += sp) {
for (let gy = sp; gy < H; gy += sp) {
cx.fillRect(gx - 0.5, gy - 0.5, 1, 1);
}
}
}
function drawChannel() {
/* 通道内部填充 */
cx.save();
cx.beginPath();
cx.moveTo(CH.wL, CH.wT);
for (let x = CH.wR; x <= CH.nL; x += 2) cx.lineTo(x, topY(x));
cx.lineTo(CH.nR, CH.nT);
cx.lineTo(CH.nR, CH.nB);
for (let x = CH.nL; x >= CH.wR; x -= 2) cx.lineTo(x, botY(x));
cx.lineTo(CH.wL, CH.wB);
cx.closePath();
const ig = cx.createLinearGradient(0, CH.nT, 0, CH.nB);
ig.addColorStop(0, '#060e1e');
ig.addColorStop(0.5, '#0a1830');
ig.addColorStop(1, '#060e1e');
cx.fillStyle = ig;
cx.fill();
cx.restore();
/* 壁面线 */
cx.save();
cx.lineWidth = 3;
cx.strokeStyle = '#1a2e50';
cx.shadowColor = 'rgba(0,180,255,0.15)';
cx.shadowBlur = 6;
/* 顶壁 */
cx.beginPath();
cx.moveTo(CH.wL, CH.wT);
for (let x = CH.wR; x <= CH.nL; x += 2) cx.lineTo(x, topY(x));
cx.lineTo(CH.nR, CH.nT);
cx.stroke();
/* 底壁 */
cx.beginPath();
cx.moveTo(CH.wL, CH.wB);
for (let x = CH.wR; x <= CH.nL; x += 2) cx.lineTo(x, botY(x));
cx.lineTo(CH.nR, CH.nB);
cx.stroke();
cx.restore();
/* 超疏水壁面纹理(窄段) */
cx.save();
cx.globalAlpha = 0.12 + 0.04 * Math.sin(time * 1.2);
const shimmer = cx.createLinearGradient(CH.nL, 0, CH.nR, 0);
shimmer.addColorStop(0, '#00c8ff');
shimmer.addColorStop(0.5, '#00e8ff');
shimmer.addColorStop(1, '#00a0e0');
cx.strokeStyle = shimmer;
cx.lineWidth = 5;
/* 顶壁内表面 */
cx.beginPath();
cx.moveTo(CH.nL, CH.nT + 3);
cx.lineTo(CH.nR, CH.nT + 3);
cx.stroke();
/* 底壁内表面 */
cx.beginPath();
cx.moveTo(CH.nL, CH.nB - 3);
cx.lineTo(CH.nR, CH.nB - 3);
cx.stroke();
cx.restore();
/* 壁面微纳结构小齿(装饰) */
cx.save();
cx.globalAlpha = 0.08;
cx.strokeStyle = '#00c8ff';
cx.lineWidth = 0.8;
for (let x = CH.nL + 10; x < CH.nR - 10; x += 8) {
/* 顶壁 */
cx.beginPath(); cx.moveTo(x, CH.nT); cx.lineTo(x, CH.nT + 6); cx.stroke();
/* 底壁 */
cx.beginPath(); cx.moveTo(x, CH.nB); cx.lineTo(x, CH.nB - 6); cx.stroke();
}
cx.restore();
}
function drawVortexGenerators() {
const vgX = CH.vgX;
const vgH = H * 0.03;
const angleRad = vortexAngle * Math.PI / 180;
const count = 6;
cx.save();
for (let i = 0; i < count; i++) {
const frac = (i + 0.5) / count;
const yTop = CH.wT + (CH.wB - CH.wT) * frac * 0.15 + (CH.wB - CH.wT) * 0.02;
const yBot = CH.wB - (CH.wB - CH.wT) * frac * 0.15 - (CH.wB - CH.wT) * 0.02;
/* 顶壁发生器 */
const tx = vgX - i * 2;
const ty = topY(tx);
cx.save();
cx.translate(tx, ty);
cx.rotate(angleRad);
cx.fillStyle = '#1e3a60';
cx.strokeStyle = '#00d4ff';
cx.lineWidth = 1.2;
cx.shadowColor = '#00d4ff';
cx.shadowBlur = 6;
cx.beginPath();
cx.moveTo(0, 0);
cx.lineTo(vgH * 0.5, -vgH);
cx.lineTo(-vgH * 0.15, -vgH * 0.6);
cx.closePath();
cx.fill(); cx.stroke();
cx.restore();
/* 底壁发生器 */
cx.save();
cx.translate(tx, botY(tx));
cx.rotate(-angleRad);
cx.fillStyle = '#1e3a60';
cx.strokeStyle = '#00d4ff';
cx.lineWidth = 1.2;
cx.shadowColor = '#00d4ff';
cx.shadowBlur = 6;
cx.beginPath();
cx.moveTo(0, 0);
cx.lineTo(vgH * 0.5, vgH);
cx.lineTo(-vgH * 0.15, vgH * 0.6);
cx.closePath();
cx.fill(); cx.stroke();
cx.restore();
}
cx.restore();
}
function drawAirFilm() {
if (!showFilm) return;
cx.save();
const filmThick = (CH.nB - CH.nT) * 0.06;
const pulse = 0.55 + 0.15 * Math.sin(time * 2.5);
/* 顶壁气膜 */
for (let x = CH.nL + 10; x < CH.nR - 10; x += 3) {
const wy = topY(x);
const g = cx.createLinearGradient(x, wy, x, wy + filmThick);
g.addColorStop(0, `rgba(0,255,136,${0.02 * pulse})`);
g.addColorStop(0.5, `rgba(0,255,200,${0.12 * pulse})`);
g.addColorStop(1, `rgba(0,255,136,0)`);
cx.fillStyle = g;
cx.fillRect(x, wy, 3, filmThick);
}
/* 底壁气膜 */
for (let x = CH.nL + 10; x < CH.nR - 10; x += 3) {
const wy = botY(x);
const g = cx.createLinearGradient(x, wy, x, wy - filmThick);
g.addColorStop(0, `rgba(0,255,136,${0.02 * pulse})`);
g.addColorStop(0.5, `rgba(0,255,200,${0.12 * pulse})`);
g.addColorStop(1, `rgba(0,255,136,0)`);
cx.fillStyle = g;
cx.fillRect(x, wy - filmThick, 3, filmThick);
}
cx.restore();
}
function drawFlowParticles() {
cx.save();
for (let p of flowP) {
cx.globalAlpha = p.alpha * 0.7;
cx.fillStyle = '#00d4ff';
cx.shadowColor = '#00d4ff';
cx.shadowBlur = 4;
cx.beginPath();
cx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
cx.fill();
}
cx.restore();
}
function drawDustParticles() {
cx.save();
for (let p of dustP) {
/* 轨迹 */
if (p.trail.length > 2) {
cx.beginPath();
cx.moveTo(p.trail[0].x, p.trail[0].y);
for (let i = 1; i < p.trail.length; i++) cx.lineTo(p.trail[i].x, p.trail[i].y);
cx.strokeStyle = `rgba(255,136,0,${0.15})`;
cx.lineWidth = 1;
cx.stroke();
}
/* 粒子本体 */
const b = Math.max(0, p.bounced);
cx.globalAlpha = p.alpha;
cx.fillStyle = b > 0 ? '#ffcc00' : '#ff8800';
cx.shadowColor = b > 0 ? '#ffcc00' : '#ff8800';
cx.shadowBlur = b > 0 ? 12 : 6;
cx.beginPath();
cx.arc(p.x, p.y, p.size * (1 + b * 0.06), 0, Math.PI * 2);
cx.fill();
}
cx.restore();
}
/* 噪声抑制可视化:在窄段壁面画被抑制的声波弧线 */
function drawNoiseSuppression() {
cx.save();
const waveX = CH.nL + (CH.nR - CH.nL) * 0.35;
const waveCount = 3;
for (let i = 0; i < waveCount; i++) {
const ox = waveX + i * 35;
/* 顶壁声波 */
const wy = topY(ox);
const alpha = 0.08 * (1 - i / waveCount);
cx.strokeStyle = `rgba(0,200,255,${alpha})`;
cx.lineWidth = 1;
cx.beginPath();
cx.arc(ox, wy, 12 + i * 8, Math.PI * 0.15, Math.PI * 0.85);
cx.stroke();
/* 底壁声波 */
const wy2 = botY(ox);
cx.beginPath();
cx.arc(ox, wy2, 12 + i * 8, -Math.PI * 0.85, -Math.PI * 0.15);
cx.stroke();
}
cx.restore();
}
/* 流向箭头 */
function drawFlowArrows() {
cx.save();
cx.globalAlpha = 0.25;
cx.strokeStyle = '#00d4ff';
cx.fillStyle = '#00d4ff';
cx.lineWidth = 1.5;
const positions = [
{ x: CH.wL + 60, y: CH.cy },
{ x: (CH.wR + CH.nL) / 2, y: CH.cy },
{ x: CH.nL + (CH.nR - CH.nL) * 0.3, y: CH.cy },
{ x: CH.nL + (CH.nR - CH.nL) * 0.65, y: CH.cy }
];
for (let p of positions) {
const len = 18 + 10 * flowMul;
cx.beginPath();
cx.moveTo(p.x - len, p.y);
cx.lineTo(p.x + len, p.y);
cx.stroke();
cx.beginPath();
cx.moveTo(p.x + len, p.y);
cx.lineTo(p.x + len - 6, p.y - 4);
cx.lineTo(p.x + len - 6, p.y + 4);
cx.closePath();
cx.fill();
}
cx.restore();
}
/* 标注层 */
function drawLabels() {
if (!showLabel) return;
cx.save();
cx.font = '600 12px "Noto Sans SC", sans-serif';
cx.textAlign = 'left';
const labels = [
{ text: '微涡流发生器阵列', tx: CH.vgX - 40, ty: CH.wT - 28, lx: CH.vgX, ly: CH.wT + 5, color: '#00d4ff' },
{ text: '迎角 ' + vortexAngle + '°', tx: CH.vgX + 40, ty: CH.wB + 30, lx: CH.vgX + 10, ly: CH.wB - 5, color: '#00d4ff' },
{ text: '有序微涡旋', tx: CH.nL + 60, ty: CH.nT - 32, lx: CH.nL + 90, ly: CH.nT + chanH(CH.nL + 90) * 0.07, color: '#00ff88' },
{ text: '气膜轴承', tx: CH.nL + (CH.nR - CH.nL) * 0.4, ty: CH.nT - 32, lx: CH.nL + (CH.nR - CH.nL) * 0.4 + 10, ly: CH.nT + 8, color: '#00ff88' },
{ text: '超疏水壁面 θ>150°', tx: CH.nL + (CH.nR - CH.nL) * 0.6, ty: CH.nB + 22, lx: CH.nL + (CH.nR - CH.nL) * 0.6 + 10, ly: CH.nB - 4, color: '#00c8ff' },
{ text: '粉尘离心甩出', tx: CH.nL + (CH.nR - CH.nL) * 0.25 - 20, ty: CH.nB + 22, lx: CH.nL + (CH.nR - CH.nL) * 0.25, ly: CH.cy + 10, color: '#ff8800' },
];
for (let lb of labels) {
/* 引线 */
cx.strokeStyle = lb.color;
cx.globalAlpha = 0.4;
cx.lineWidth = 0.8;
cx.setLineDash([3, 3]);
cx.beginPath();
cx.moveTo(lb.tx + 10, lb.ty + 5);
cx.lineTo(lb.lx, lb.ly);
cx.stroke();
cx.setLineDash([]);
/* 文字 */
cx.globalAlpha = 0.85;
cx.fillStyle = lb.color;
cx.shadowColor = lb.color;
cx.shadowBlur = 6;
cx.fillText(lb.text, lb.tx, lb.ty);
cx.shadowBlur = 0;
}
/* 收缩段标注 */
cx.globalAlpha = 0.3;
cx.font = '700 11px "Rajdhani", sans-serif';
cx.fillStyle = '#4a6080';
cx.textAlign = 'center';
cx.fillText('CONVERGING SECTION', (CH.wR + CH.nL) / 2, CH.wB + 50);
cx.fillText('NARROW CHANNEL', (CH.nL + CH.nR) / 2, CH.nB + 50);
cx.restore();
}
/* ========== 超疏水壁面放大插图 ========== */
function drawInset() {
const w = 200, h = 150;
ix.clearRect(0, 0, w, h);
/* 背景 */
ix.fillStyle = '#060e1e';
ix.fillRect(0, 0, w, h);
/* 微纳结构柱状阵列 */
ix.save();
ix.strokeStyle = 'rgba(0,200,255,0.35)';
ix.lineWidth = 1;
const baseY = h * 0.75;
const pillarW = 3, pillarH = 18, gap = 9;
for (let x = 15; x < w - 10; x += gap) {
ix.fillStyle = 'rgba(0,180,255,0.15)';
ix.fillRect(x, baseY - pillarH, pillarW, pillarH);
ix.strokeRect(x, baseY - pillarH, pillarW, pillarH);
/* 纳米级二级结构 */
ix.fillStyle = 'rgba(0,220,255,0.08)';
ix.fillRect(x + 0.5, baseY - pillarH - 5, pillarW - 1, 5);
}
/* 基底 */
ix.fillStyle = '#1a2e50';
ix.fillRect(0, baseY, w, h - baseY);
ix.restore();
/* 液滴/粉尘颗粒 — 接触角 >150° */
const dropX = w * 0.5;
const dropBaseY = baseY - pillarH - 4;
const dropR = 16;
/* 极小接触面 */
ix.save();
ix.globalAlpha = 0.8;
const dg = ix.createRadialGradient(dropX - 3, dropBaseY - dropR * 0.9, 2, dropX, dropBaseY - dropR * 0.5, dropR);
dg.addColorStop(0, 'rgba(255,160,50,0.9)');
dg.addColorStop(1, 'rgba(255,100,0,0.4)');
ix.fillStyle = dg;
ix.beginPath();
/* 近球形液滴,底部极小接触面 */
const contactW = 4; // 接触面半宽
ix.moveTo(dropX - contactW, dropBaseY);
ix.bezierCurveTo(
dropX - contactW - 2, dropBaseY - dropR * 0.5,
dropX - dropR * 0.9, dropBaseY - dropR * 1.1,
dropX, dropBaseY - dropR * 1.2
);
ix.bezierCurveTo(
dropX + dropR * 0.9, dropBaseY - dropR * 1.1,
dropX + contactW + 2, dropBaseY - dropR * 0.5,
dropX + contactW, dropBaseY
);
ix.closePath();
ix.fill();
/* 接触角标注线 */
ix.strokeStyle = 'rgba(0,255,136,0.6)';
ix.lineWidth = 0.8;
ix.setLineDash([2, 2]);
/* 水平线 */
ix.beginPath();
ix.moveTo(dropX - 25, dropBaseY);
ix.lineTo(dropX + 25, dropBaseY);
ix.stroke();
/* 切线 */
const tangentAngle = 155 * Math.PI / 180;
const tLen = 22;
ix.beginPath();
ix.moveTo(dropX + contactW, dropBaseY);
ix.lineTo(dropX + contactW + Math.cos(tangentAngle) * tLen, dropBaseY - Math.sin(tangentAngle) * tLen);
ix.stroke();
ix.setLineDash([]);
/* 角度弧 */
ix.strokeStyle = '#00ff88';
ix.lineWidth = 1;
ix.beginPath();
ix.arc(dropX + contactW, dropBaseY, 12, -tangentAngle, 0);
ix.stroke();
/* 角度值 */
ix.fillStyle = '#00ff88';
ix.font = '700 10px "Rajdhani", sans-serif';
ix.textAlign = 'left';
ix.fillText('>150°', dropX + contactW + 14, dropBaseY - 4);
/* 反弹箭头 */
const bounceT = (time * 1.5) % 1;
if (bounceT < 0.5) {
const bt = bounceT * 2;
ix.globalAlpha = 1 - bt;
ix.strokeStyle = '#ffcc00';
ix.lineWidth = 1.5;
const arrowY = dropBaseY - dropR * 1.2 - 6 - bt * 20;
ix.beginPath();
ix.moveTo(dropX, dropBaseY - dropR * 1.2 - 6);
ix.lineTo(dropX, arrowY);
ix.stroke();
ix.beginPath();
ix.moveTo(dropX, arrowY);
ix.lineTo(dropX - 4, arrowY + 5);
ix.moveTo(dropX, arrowY);
ix.lineTo(dropX + 4, arrowY + 5);
cx.stroke();
}
ix.restore();
}
/* ========== 主动画循环 ========== */
let lastTs = 0;
function animate(ts) {
const dt = Math.min((ts - lastTs) / 1000, 0.05);
lastTs = ts;
time += dt;
updateFlow(dt);
updateDust(dt);
/* 清屏 & 绘制 */
cx.clearRect(0, 0, W, H);
drawBg();
drawChannel();
drawFlowArrows();
drawAirFilm();
drawNoiseSuppression();
drawFlowParticles();
drawVortices();
drawDustParticles();
drawVortexGenerators();
drawLabels();
drawInset();
requestAnimationFrame(animate);
}
/* ========== 控件 ========== */
document.getElementById('sAngle').addEventListener('input', function() {
vortexAngle = +this.value;
document.getElementById('vAngle').textContent = vortexAngle + '°';
});
document.getElementById('sSpeed').addEventListener('input', function() {
flowMul = +this.value;
document.getElementById('vSpeed').textContent = flowMul.toFixed(1) + 'x';
});
document.getElementById('sDust').addEventListener('input', function() {
dustMax = +this.value;
document.getElementById('vDust').textContent = dustMax;
});
document.getElementById('btnFilm').addEventListener('click', function() {
showFilm = !showFilm;
this.classList.toggle('on', showFilm);
});
document.getElementById('btnLabel').addEventListener('click', function() {
showLabel = !showLabel;
this.classList.toggle('on', showLabel);
});
/* ========== 初始化 ========== */
function init() {
calcGeom();
initFlow();
initVortices();
initDust();
lastTs = performance.now();
requestAnimationFrame(animate);
}
window.addEventListener('resize', () => {
calcGeom();
initVortices();
});
window.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>
这个动画实现了以下核心要素:
视觉设计方向:采用深空科技美学——深色背景搭配青色(主流场)、霓虹绿(微涡旋/气膜轴承)、琥珀橙(粉尘颗粒)三色体系,营造高端 CFD 可视化质感。
IFR 理想解呈现:
- 微涡流发生器:在收缩段上游以 15° 迎角安装,用发光三角形标识,滑块可调角度
- 有序微涡旋:窄流道壁面绘制旋转螺旋动画,直观展示"气膜轴承"的形成
- 气膜轴承:绿色渐变脉冲光层铺在壁面与主流之间,是破题关键——用"气膜"替代实壁面
- 粉尘离心甩出:橙色颗粒靠近壁面时被涡旋离心力推向中心,接触超疏水壁面时反弹闪黄光
- 超疏水壁面:窄段壁面以微纳齿纹 + 闪烁纹理呈现,右下角插图放大展示 >150° 接触角原理
交互控制:底部面板可调涡流发生器迎角、流速倍率、粉尘密度,以及切换气膜可视化和标注层显示。所有动画页面加载即自动播放。
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
