独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh">
<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&family=Noto+Sans+SC:wght@300;400;600;700&display=swap" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{
--bg:#060a12;--fg:#b8c8d8;--muted:#4a6078;
--accent:#00d4ff;--accent2:#ff9500;--accent3:#00ffa3;
--card:#0b1220;--border:#162535;
}
html,body{height:100%;overflow:hidden}
body{
background:var(--bg);color:var(--fg);
font-family:'Noto Sans SC',sans-serif;
display:flex;flex-direction:column;align-items:center;justify-content:center;
position:relative;
}
body::before{
content:'';position:fixed;inset:0;
background:
radial-gradient(ellipse 60% 50% at 30% 45%,rgba(0,212,255,.04),transparent),
radial-gradient(ellipse 50% 40% at 75% 55%,rgba(255,149,0,.03),transparent);
pointer-events:none;z-index:0;
}
#app{
position:relative;z-index:1;
display:flex;flex-direction:column;align-items:center;
width:100%;max-width:1200px;height:100vh;
padding:12px 16px 10px;
}
header{
text-align:center;margin-bottom:6px;flex-shrink:0;
}
header h1{
font-family:'Rajdhani',sans-serif;font-weight:700;
font-size:clamp(22px,3.2vw,36px);letter-spacing:3px;
color:#e0ecf4;
text-shadow:0 0 20px rgba(0,212,255,.25);
}
header p{
font-size:clamp(11px,1.4vw,14px);color:var(--muted);
font-weight:300;letter-spacing:1px;margin-top:2px;
}
header p span{color:var(--accent);font-weight:600}
#svg-wrap{
flex:1;width:100%;display:flex;align-items:center;justify-content:center;
min-height:0;
}
#main-svg{
width:100%;height:100%;max-height:calc(100vh - 140px);
display:block;
}
#bottom-bar{
flex-shrink:0;width:100%;
display:flex;align-items:center;justify-content:center;
gap:clamp(12px,2.5vw,32px);padding:8px 0 4px;
flex-wrap:wrap;
}
.phase-group{display:flex;gap:8px}
.phase-btn{
padding:6px 16px;border-radius:6px;
font-family:'Rajdhani',sans-serif;font-weight:600;font-size:13px;
letter-spacing:1px;
background:var(--card);border:1px solid var(--border);color:var(--muted);
transition:all .35s ease;cursor:default;white-space:nowrap;
}
.phase-btn.active{
background:rgba(0,212,255,.12);border-color:var(--accent);
color:var(--accent);box-shadow:0 0 12px rgba(0,212,255,.15);
}
.phase-btn .num{
display:inline-block;width:18px;height:18px;line-height:18px;
text-align:center;border-radius:50%;font-size:11px;
background:rgba(0,212,255,.15);color:var(--accent);margin-right:5px;
}
.ctrl-group{
display:flex;align-items:center;gap:8px;
font-size:12px;color:var(--muted);
}
.ctrl-group label{font-family:'Rajdhani',sans-serif;font-weight:600;letter-spacing:1px}
.ctrl-group input[type=range]{
-webkit-appearance:none;appearance:none;
width:90px;height:4px;background:var(--border);border-radius:2px;outline:none;
}
.ctrl-group input[type=range]::-webkit-slider-thumb{
-webkit-appearance:none;width:14px;height:14px;
background:var(--accent);border-radius:50%;cursor:pointer;
box-shadow:0 0 6px rgba(0,212,255,.4);
}
.ctrl-group .val{
font-family:'Rajdhani',sans-serif;color:var(--accent);
min-width:32px;text-align:right;font-weight:600;
}
@media(max-width:640px){
#app{padding:8px 8px 6px}
.phase-btn{padding:4px 10px;font-size:11px}
}
</style>
</head>
<body>
<div id="app">
<header>
<h1>CENTRIFUGAL VORTEX FEEDING</h1>
<p>TRIZ 最终理想解 — 以<span>气动场</span>替代机械接触,消除卡弹矛盾</p>
</header>
<div id="svg-wrap">
<svg id="main-svg" viewBox="0 0 1060 620" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 发光滤镜 -->
<filter id="glow-cyan" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="6" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glow-amber" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="5" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glow-soft" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur in="SourceGraphic" stdDeviation="14"/>
</filter>
<filter id="glow-wide" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur in="SourceGraphic" stdDeviation="28"/>
</filter>
<!-- 涡流罩渐变 -->
<radialGradient id="npGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#00d4ff" stop-opacity="0.35"/>
<stop offset="60%" stop-color="#00d4ff" stop-opacity="0.08"/>
<stop offset="100%" stop-color="#00d4ff" stop-opacity="0"/>
</radialGradient>
<!-- 枪管渐变 -->
<linearGradient id="barrelGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#1a2e44"/>
<stop offset="50%" stop-color="#243c56"/>
<stop offset="100%" stop-color="#1a2e44"/>
</linearGradient>
<!-- 叶轮渐变 -->
<linearGradient id="bladeGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#1e3650"/>
<stop offset="100%" stop-color="#2a4e6e"/>
</linearGradient>
</defs>
<!-- 背景网格 -->
<g opacity="0.18">
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M40 0 L0 0 0 40" fill="none" stroke="#1a3050" stroke-width="0.5"/>
</pattern>
<rect width="1060" height="620" fill="url(#grid)"/>
</g>
<!-- === 侧面剖视小图 === -->
<g id="side-view" transform="translate(25,430)">
<rect x="0" y="0" width="210" height="170" rx="6" fill="#0a1220" stroke="#162535" stroke-width="1"/>
<text x="105" y="16" text-anchor="middle" fill="#4a6078" font-size="9" font-family="Rajdhani" font-weight="600" letter-spacing="1">SIDE CROSS-SECTION</text>
<!-- 弹夹 -->
<rect x="90" y="24" width="30" height="62" rx="3" fill="none" stroke="#2a4a68" stroke-width="1.5"/>
<text x="75" y="50" text-anchor="end" fill="#4a6078" font-size="8" font-family="Noto Sans SC">弹夹</text>
<!-- 子弹(侧视) -->
<ellipse id="side-bullet" cx="105" cy="52" rx="6" ry="7" fill="#ff9500" opacity="0"/>
<!-- 涡流罩 -->
<rect x="60" y="86" width="100" height="40" rx="5" fill="none" stroke="#2a5a7a" stroke-width="1.5" stroke-dasharray="4,2"/>
<text x="48" y="108" text-anchor="end" fill="#4a6078" font-size="8" font-family="Noto Sans SC">涡流罩</text>
<!-- 叶轮(侧视截面) -->
<rect id="side-impeller" x="62" y="90" width="6" height="32" rx="2" fill="#2a5070"/>
<!-- 负压区 -->
<ellipse cx="110" cy="106" rx="22" ry="12" fill="url(#npGlow)" opacity="0.5" id="side-np"/>
<!-- 枪管 -->
<rect x="160" y="98" width="42" height="16" rx="2" fill="url(#barrelGrad)" stroke="#2a4a68" stroke-width="1"/>
<text x="181" y="93" text-anchor="middle" fill="#4a6078" font-size="8" font-family="Noto Sans SC">枪管</text>
<!-- 气流箭头 -->
<path d="M80,100 Q70,106 80,112" fill="none" stroke="#00d4ff" stroke-width="1" opacity="0.5" id="side-air1"/>
<path d="M100,96 L110,96" fill="none" stroke="#00d4ff" stroke-width="1" opacity="0.4" marker-end="none"/>
<path d="M140,100 L155,100" fill="none" stroke="#00d4ff" stroke-width="1.2" opacity="0.6"/>
<polygon points="155,97 162,100 155,103" fill="#00d4ff" opacity="0.6"/>
<!-- 旋转标记 -->
<path id="side-rot" d="M65,88 A3,3 0 1 1 65,87.5" fill="none" stroke="#00d4ff" stroke-width="0.8" opacity="0.6"/>
</g>
<!-- === 主视图:俯视涡流供弹机构 === -->
<g id="main-view">
<!-- 负压区大光晕 -->
<circle id="np-glow-wide" cx="290" cy="300" r="60" fill="#00d4ff" opacity="0" filter="url(#glow-wide)"/>
<circle id="np-glow-core" cx="290" cy="300" r="45" fill="url(#npGlow)" opacity="0"/>
<!-- 涡流罩(外环,有缺口) -->
<g id="vortex-cover">
<!-- 涡流罩外环 -->
<path id="vc-outer" fill="none" stroke="#1e3854" stroke-width="22" opacity="0.7"/>
<!-- 涡流罩内壁 -->
<path id="vc-inner" fill="none" stroke="#264a6a" stroke-width="1.5" opacity="0.5"/>
<!-- 涡流罩外壁 -->
<path id="vc-outer-edge" fill="none" stroke="#1a3050" stroke-width="1" opacity="0.4"/>
</g>
<!-- 气流粒子层 -->
<g id="particles-layer"></g>
<!-- 枪管 -->
<g id="barrel-group">
<rect id="barrel-body" x="453" y="287" width="460" height="26" rx="4" fill="url(#barrelGrad)" stroke="#1e3854" stroke-width="1.2"/>
<!-- 枪管内壁高光 -->
<line x1="458" y1="290" x2="908" y2="290" stroke="#2a4e6e" stroke-width="0.5" opacity="0.5"/>
<line x1="458" y1="310" x2="908" y2="310" stroke="#0d1a2a" stroke-width="0.5" opacity="0.5"/>
<!-- 枪口 -->
<rect x="908" y="284" width="8" height="32" rx="2" fill="#1a2e44" stroke="#2a4a68" stroke-width="1"/>
</g>
<!-- 叶轮组(旋转) -->
<g id="impeller-group">
<!-- 叶轮底盘 -->
<circle cx="290" cy="300" r="112" fill="#0e1a28" stroke="#1a3050" stroke-width="1" opacity="0.6"/>
<!-- 叶片由JS生成 -->
<g id="blades-group"></g>
<!-- 叶轮轮毂 -->
<circle cx="290" cy="300" r="26" fill="#162840" stroke="#2a4e6e" stroke-width="1.5"/>
<circle cx="290" cy="300" r="10" fill="#1e3854" stroke="#3a6a8e" stroke-width="1"/>
<!-- 旋转方向标记 -->
<path d="M290,270 A30,30 0 0 1 310,278" fill="none" stroke="#00d4ff" stroke-width="1" opacity="0.4" id="rot-indicator"/>
</g>
<!-- 弹夹入口标记(俯视为中央圆环) -->
<g id="mag-indicator">
<circle cx="290" cy="300" r="16" fill="none" stroke="#ff9500" stroke-width="1.2" stroke-dasharray="3,2" opacity="0.6"/>
<text x="290" y="260" text-anchor="middle" fill="#ff9500" font-size="9" font-family="Rajdhani" font-weight="600" opacity="0.7">MAGAZINE</text>
<line x1="290" y1="264" x2="290" y2="282" stroke="#ff9500" stroke-width="0.8" opacity="0.4"/>
</g>
<!-- 子弹 -->
<g id="bullet-group" opacity="0">
<ellipse id="bullet-body" cx="290" cy="300" rx="9" ry="10" fill="#ff9500" filter="url(#glow-amber)"/>
<ellipse cx="290" cy="296" rx="5" ry="4" fill="#ffb84d" opacity="0.6"/>
</g>
<!-- 子弹轨迹 -->
<path id="bullet-trail" fill="none" stroke="#ff9500" stroke-width="2.5" opacity="0" stroke-linecap="round"/>
<!-- 气流方向大箭头 -->
<g id="flow-arrows" opacity="0">
<path d="M230,240 Q260,250 270,280" fill="none" stroke="#00d4ff" stroke-width="1.2" opacity="0.35" stroke-dasharray="4,3"/>
<path d="M350,240 Q320,255 310,280" fill="none" stroke="#00d4ff" stroke-width="1.2" opacity="0.35" stroke-dasharray="4,3"/>
<path d="M370,330 Q350,360 310,370" fill="none" stroke="#00d4ff" stroke-width="1.2" opacity="0.35" stroke-dasharray="4,3"/>
</g>
<!-- 标注 -->
<g id="annotations" font-family="Noto Sans SC" font-size="10" fill="#5a7898">
<!-- 叶片后掠角标注 -->
<g id="anno-sweep" opacity="0">
<line x1="290" y1="300" x2="380" y2="300" stroke="#00ffa3" stroke-width="0.8" stroke-dasharray="3,2" opacity="0.5"/>
<path d="M360,300 A20,20 0 0 0 355,290" fill="none" stroke="#00ffa3" stroke-width="1" opacity="0.7"/>
<text x="385" y="295" fill="#00ffa3" font-size="9" font-family="Rajdhani" font-weight="600">30°</text>
<text x="385" y="308" fill="#4a7060" font-size="8">后掠角</text>
</g>
<!-- 间隙标注 -->
<g id="anno-gap" opacity="0">
<line x1="405" y1="280" x2="405" y2="320" stroke="#00ffa3" stroke-width="0.6" opacity="0.4"/>
<line x1="400" y1="282" x2="410" y2="282" stroke="#00ffa3" stroke-width="0.8" opacity="0.5"/>
<line x1="400" y1="318" x2="410" y2="318" stroke="#00ffa3" stroke-width="0.8" opacity="0.5"/>
<text x="415" y="303" fill="#00ffa3" font-size="9" font-family="Rajdhani" font-weight="600">0.5mm</text>
</g>
<!-- 负压腔深度标注 -->
<g id="anno-depth" opacity="0">
<line x1="250" y1="300" x2="250" y2="220" stroke="#00ffa3" stroke-width="0.6" opacity="0.4"/>
<text x="225" y="255" fill="#00ffa3" font-size="9" font-family="Rajdhani" font-weight="600" transform="rotate(-90,225,255)">15mm</text>
<text x="240" y="218" fill="#4a7060" font-size="8">负压腔</text>
</g>
</g>
<!-- 切向吸气口标注 -->
<g id="anno-port" opacity="0">
<rect x="440" y="292" width="16" height="16" fill="none" stroke="#00d4ff" stroke-width="1" stroke-dasharray="2,2" rx="2"/>
<text x="448" y="280" text-anchor="middle" fill="#00d4ff" font-size="8" font-family="Rajdhani" font-weight="600">TANGENTIAL PORT</text>
</g>
</g>
<!-- 动态文字:当前阶段 -->
<text id="phase-label" x="530" y="580" text-anchor="middle" fill="#00d4ff" font-size="13" font-family="Rajdhani" font-weight="700" letter-spacing="2" opacity="0.8"></text>
<!-- 关键创新点提示 -->
<g id="innovation-callout" opacity="0">
<rect x="650" y="140" width="280" height="80" rx="6" fill="#0a1220" stroke="#00ffa3" stroke-width="1" opacity="0.9"/>
<text x="665" y="160" fill="#00ffa3" font-size="10" font-family="Rajdhani" font-weight="700" letter-spacing="1">IFR CORE INSIGHT</text>
<text x="665" y="178" fill="#8aa8c0" font-size="10" font-family="Noto Sans SC">以旋转气流(已有资源)替代推弹杆</text>
<text x="665" y="195" fill="#8aa8c0" font-size="10" font-family="Noto Sans SC">气动力 = 零零件供弹 · 自清洁 · 免润滑</text>
<text x="665" y="212" fill="#5a7898" font-size="9" font-family="Noto Sans SC">资源: 电机旋转 → 离心风机 → 负压吸弹</text>
</g>
</svg>
</div>
<div id="bottom-bar">
<div class="phase-group">
<div class="phase-btn" id="ph0"><span class="num">1</span>负压生成</div>
<div class="phase-btn" id="ph1"><span class="num">2</span>子弹吸入</div>
<div class="phase-btn" id="ph2"><span class="num">3</span>离心供弹</div>
</div>
<div class="ctrl-group">
<label>转速</label>
<input type="range" id="speed-slider" min="0.3" max="2.5" step="0.1" value="1">
<span class="val" id="speed-val">1.0x</span>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const NS = 'http://www.w3.org/2000/svg';
const svg = document.getElementById('main-svg');
/* ========== 配置 ========== */
const C = {
cx: 290, cy: 300,
hubR: 26,
bladeInR: 32,
bladeOutR: 110,
bladeCount: 8,
sweepDeg: 30,
vortexInR: 120,
vortexOutR: 155,
portHalfAngle: 7,
barrelHW: 13,
barrelStartX: 453,
barrelEndX: 910,
particleCount: 75,
cycleTime: 9.5,
};
const sweepRad = C.sweepDeg * Math.PI / 180;
const portHalfRad = C.portHalfAngle * Math.PI / 180;
/* ========== 状态 ========== */
let time = 0, speed = 1, lastTs = 0;
let impAngle = 0;
let phase = -1;
let particles = [];
let bulletActive = false, bulletR = 0, bulletTheta = 0;
let bulletInBarrel = false, bulletBarrelX = 0;
let bulletTrailPts = [];
/* ========== DOM引用 ========== */
const $blades = document.getElementById('blades-group');
const $impGroup = document.getElementById('impeller-group');
const $bulletGroup = document.getElementById('bullet-group');
const $bulletBody = document.getElementById('bullet-body');
const $bulletTrail = document.getElementById('bullet-trail');
const $npGlowWide = document.getElementById('np-glow-wide');
const $npGlowCore = document.getElementById('np-glow-core');
const $particlesLayer = document.getElementById('particles-layer');
const $phaseLabel = document.getElementById('phase-label');
const $flowArrows = document.getElementById('flow-arrows');
const $annoSweep = document.getElementById('anno-sweep');
const $annoGap = document.getElementById('anno-gap');
const $annoDepth = document.getElementById('anno-depth');
const $annoPort = document.getElementById('anno-port');
const $innovationCallout = document.getElementById('innovation-callout');
const $sideBullet = document.getElementById('side-bullet');
const $sideNp = document.getElementById('side-np');
const $speedSlider = document.getElementById('speed-slider');
const $speedVal = document.getElementById('speed-val');
const $phaseBtns = [document.getElementById('ph0'), document.getElementById('ph1'), document.getElementById('ph2')];
/* ========== 绘制涡流罩弧线 ========== */
function arcPath(cx, cy, r, startDeg, endDeg) {
const s = startDeg * Math.PI / 180, e = endDeg * Math.PI / 180;
const x1 = cx + r * Math.cos(s), y1 = cy + r * Math.sin(s);
const x2 = cx + r * Math.cos(e), y2 = cy + r * Math.sin(e);
const large = (endDeg - startDeg) > 180 ? 1 : 0;
return `M${x1},${y1} A${r},${r} 0 ${large} 1 ${x2},${y2}`;
}
// 涡流罩弧线(留出切向口缺口)
const gapStart = 360 - C.portHalfAngle;
const gapEnd = C.portHalfAngle;
document.getElementById('vc-outer').setAttribute('d',
arcPath(C.cx, C.cy, (C.vortexInR + C.vortexOutR) / 2, gapEnd, gapStart)
);
document.getElementById('vc-inner').setAttribute('d',
arcPath(C.cx, C.cy, C.vortexInR, gapEnd, gapStart)
);
document.getElementById('vc-outer-edge').setAttribute('d',
arcPath(C.cx, C.cy, C.vortexOutR, gapEnd, gapStart)
);
/* ========== 绘制叶轮叶片 ========== */
function createBlades() {
$blades.innerHTML = '';
for (let i = 0; i < C.bladeCount; i++) {
const baseAngle = (i / C.bladeCount) * Math.PI * 2;
const inX = C.cx + C.bladeInR * Math.cos(baseAngle);
const inY = C.cy + C.bladeInR * Math.sin(baseAngle);
const tipAngle = baseAngle - sweepRad;
const outX = C.cx + C.bladeOutR * Math.cos(tipAngle);
const outY = C.cy + C.bladeOutR * Math.sin(tipAngle);
// 控制点
const midR = (C.bladeInR + C.bladeOutR) / 2;
const midAngle = baseAngle - sweepRad * 0.4;
const cpX = C.cx + midR * Math.cos(midAngle);
const cpY = C.cy + midR * Math.sin(midAngle);
// 叶片宽度方向
const perpIn = baseAngle + Math.PI / 2;
const perpOut = tipAngle + Math.PI / 2;
const wIn = 5, wOut = 3;
const d = [
`M${inX + wIn * Math.cos(perpIn)},${inY + wIn * Math.sin(perpIn)}`,
`Q${cpX + 4 * Math.cos(midAngle + Math.PI / 2)},${cpY + 4 * Math.sin(midAngle + Math.PI / 2)} ${outX + wOut * Math.cos(perpOut)},${outY + wOut * Math.sin(perpOut)}`,
`L${outX - wOut * Math.cos(perpOut)},${outY - wOut * Math.sin(perpOut)}`,
`Q${cpX - 4 * Math.cos(midAngle + Math.PI / 2)},${cpY - 4 * Math.sin(midAngle + Math.PI / 2)} ${inX - wIn * Math.cos(perpIn)},${inY - wIn * Math.sin(perpIn)}`,
'Z'
].join(' ');
const blade = document.createElementNS(NS, 'path');
blade.setAttribute('d', d);
blade.setAttribute('fill', '#1e3a58');
blade.setAttribute('stroke', '#2e5a7e');
blade.setAttribute('stroke-width', '0.8');
blade.setAttribute('opacity', '0.85');
$blades.appendChild(blade);
}
}
createBlades();
/* ========== 创建粒子 ========== */
function makeParticle() {
const el = document.createElementNS(NS, 'circle');
el.setAttribute('r', '2.2');
el.setAttribute('fill', '#00d4ff');
el.setAttribute('opacity', '0');
$particlesLayer.appendChild(el);
return {
el,
angle: Math.random() * Math.PI * 2,
radius: C.hubR + Math.random() * 10,
angVel: 0,
radVel: 0,
life: 0,
maxLife: 2.5 + Math.random() * 2,
size: 1.5 + Math.random() * 1.5,
inBarrel: false,
barrelX: 0,
active: true
};
}
for (let i = 0; i < C.particleCount; i++) {
const p = makeParticle();
p.radius = C.hubR + Math.random() * (C.vortexInR - C.hubR);
p.angle = Math.random() * Math.PI * 2;
p.life = Math.random() * p.maxLife;
particles.push(p);
}
/* ========== 速度控制 ========== */
$speedSlider.addEventListener('input', () => {
speed = parseFloat($speedSlider.value);
$speedVal.textContent = speed.toFixed(1) + 'x';
});
/* ========== 动画循环 ========== */
function setPhase(p) {
if (p === phase) return;
phase = p;
$phaseBtns.forEach((b, i) => b.classList.toggle('active', i === p));
const labels = ['负压生成 — 叶轮旋转 → 中心负压', '子弹吸入 — 负压吸引 → 子弹入腔', '离心供弹 — 气流裹挟 → 入管发射'];
$phaseLabel.textContent = labels[p] || '';
}
function resetBullet() {
bulletActive = false;
bulletR = 0;
bulletTheta = 0;
bulletInBarrel = false;
bulletBarrelX = 0;
bulletTrailPts = [];
$bulletGroup.setAttribute('opacity', '0');
$bulletTrail.setAttribute('opacity', '0');
$bulletTrail.setAttribute('d', '');
}
function update(dt) {
const sDt = dt * speed;
time += sDt;
const cycleT = time % C.cycleTime;
// 叶轮旋转
const rotSpeed = 3.5 * speed; // rad/s
impAngle += rotSpeed * dt;
$impGroup.setAttribute('transform', `rotate(${impAngle * 180 / Math.PI},${C.cx},${C.cy})`);
// 阶段判定
let newPhase;
if (cycleT < 2.5) newPhase = 0;
else if (cycleT < 5.0) newPhase = 1;
else newPhase = 2;
setPhase(newPhase);
// 负压光晕
const npPhase0 = Math.min(1, cycleT / 2.0); // 前2秒渐入
const npPulse = 0.7 + 0.3 * Math.sin(time * 4);
const npOpacity = (newPhase >= 0 ? npPhase0 : 0) * npPulse * 0.35;
$npGlowWide.setAttribute('opacity', npOpacity);
$npGlowCore.setAttribute('opacity', npOpacity * 1.6);
$sideNp.setAttribute('opacity', npOpacity * 1.2);
// 气流箭头
$flowArrows.setAttribute('opacity', npPhase0 * 0.7);
// 标注
const annoOpacity = Math.min(1, Math.max(0, (cycleT - 1.5) / 1.0));
$annoSweep.setAttribute('opacity', annoOpacity * 0.8);
$annoGap.setAttribute('opacity', annoOpacity * 0.8);
$annoDepth.setAttribute('opacity', annoOpacity * 0.7);
$annoPort.setAttribute('opacity', annoOpacity * 0.8);
// 创新点提示
const innovOp = Math.min(1, Math.max(0, (cycleT - 2.0) / 1.5)) * Math.min(1, Math.max(0, (C.cycleTime - 1.5 - cycleT) / 1.0));
$innovationCallout.setAttribute('opacity', innovOp);
// 侧视图子弹
if (newPhase === 1) {
const subT = (cycleT - 2.5) / 2.5;
const by = 52 - subT * 16;
$sideBullet.setAttribute('cy', by);
$sideBullet.setAttribute('opacity', 1 - subT * 0.5);
} else if (newPhase === 2) {
$sideBullet.setAttribute('opacity', 0.2);
$sideBullet.setAttribute('cx', 105 + ((cycleT - 5.0) / 4.5) * 55);
} else {
$sideBullet.setAttribute('opacity', 0);
$sideBullet.setAttribute('cx', 105);
$sideBullet.setAttribute('cy', 52);
}
// ========== 粒子更新 ==========
const impOmega = rotSpeed;
particles.forEach(p => {
p.life += sDt;
if (p.life > p.maxLife || p.radius > C.vortexOutR + 10) {
// 重生
p.radius = C.hubR + Math.random() * 8;
p.angle = Math.random() * Math.PI * 2;
p.life = 0;
p.maxLife = 2.5 + Math.random() * 2;
p.inBarrel = false;
p.active = true;
}
if (p.inBarrel) {
p.barrelX += 180 * sDt;
if (p.barrelX > C.barrelEndX + 20) {
p.life = p.maxLife + 1; // 触发重生
}
const opacity = Math.max(0, 0.6 - (p.barrelX - C.barrelStartX) / 600);
p.el.setAttribute('cx', p.barrelX);
p.el.setAttribute('cy', C.cy);
p.el.setAttribute('opacity', opacity * npPhase0);
p.el.setAttribute('r', p.size * 0.8);
return;
}
// 涡旋内部运动
const rFrac = (p.radius - C.hubR) / (C.vortexInR - C.hubR);
// 角速度:内部跟随叶轮,外部因减速
const dragFactor = Math.max(0.15, 1 - rFrac * 0.7);
p.angVel = impOmega * dragFactor;
p.angle += p.angVel * dt;
// 径向速度:离心效应
p.radVel = 15 + rFrac * 40;
p.radius += p.radVel * sDt;
// 检查是否到达切向口附近
const angleNorm = ((p.angle % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2);
const nearPort = angleNorm < portHalfRad * 2 || angleNorm > Math.PI * 2 - portHalfRad * 2;
if (p.radius > C.vortexInR && nearPort) {
// 进入枪管
p.inBarrel = true;
p.barrelX = C.barrelStartX;
}
const x = C.cx + p.radius * Math.cos(p.angle);
const y = C.cy + p.radius * Math.sin(p.angle);
const fadeIn = Math.min(1, p.life / 0.3);
const fadeOut = Math.max(0, 1 - (p.radius - C.vortexInR + 10) / 20);
const opacity = fadeIn * fadeOut * 0.65 * npPhase0;
p.el.setAttribute('cx', x);
p.el.setAttribute('cy', y);
p.el.setAttribute('opacity', Math.max(0, opacity));
p.el.setAttribute('r', p.size * (0.7 + rFrac * 0.5));
});
// ========== 子弹动画 ==========
if (newPhase === 1 && cycleT >= 3.0) {
// 子弹出现
if (!bulletActive) {
bulletActive = true;
bulletR = 0;
bulletTheta = Math.random() * Math.PI * 2;
bulletTrailPts = [];
}
$bulletGroup.setAttribute('opacity', '1');
// 子弹被吸向中心(实际在中心出现)
const subPhase = (cycleT - 3.0) / 2.0;
bulletR = 2 + subPhase * 8;
bulletTheta += rotSpeed * 0.3 * dt;
} else if (newPhase === 2) {
if (!bulletActive) {
bulletActive = true;
bulletR = 10;
bulletTheta = impAngle;
bulletTrailPts = [];
}
$bulletGroup.setAttribute('opacity', '1');
if (!bulletInBarrel) {
// 螺旋向外
const spiralSpeed = 55 * speed;
bulletR += spiralSpeed * dt;
bulletTheta += rotSpeed * 0.8 * dt;
// 记录轨迹
const bx = C.cx + bulletR * Math.cos(bulletTheta);
const by = C.cy + bulletR * Math.sin(bulletTheta);
bulletTrailPts.push({ x: bx, y: by });
if (bulletTrailPts.length > 60) bulletTrailPts.shift();
// 检查是否到达枪管入口
const bAngleNorm = ((bulletTheta % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2);
if (bulletR >= C.vortexInR - 5 && (bAngleNorm < 0.3 || bAngleNorm > Math.PI * 2 - 0.3)) {
bulletInBarrel = true;
bulletBarrelX = C.barrelStartX;
}
} else {
// 在枪管中
bulletBarrelX += 250 * speed * dt;
bulletTrailPts.push({ x: bulletBarrelX, y: C.cy });
if (bulletTrailPts.length > 60) bulletTrailPts.shift();
if (bulletBarrelX > C.barrelEndX + 30) {
resetBullet();
}
}
} else if (newPhase === 0 && cycleT < 0.5) {
resetBullet();
}
// 渲染子弹位置
if (bulletActive) {
let bx, by;
if (bulletInBarrel) {
bx = bulletBarrelX;
by = C.cy;
} else {
bx = C.cx + bulletR * Math.cos(bulletTheta);
by = C.cy + bulletR * Math.sin(bulletTheta);
}
$bulletBody.setAttribute('cx', bx);
$bulletBody.setAttribute('cy', by);
// 高光跟随
const hl = $bulletGroup.querySelector('ellipse:nth-child(2)');
if (hl) {
hl.setAttribute('cx', bx);
hl.setAttribute('cy', by - 4);
}
// 轨迹线
if (bulletTrailPts.length > 2) {
let d = `M${bulletTrailPts[0].x},${bulletTrailPts[0].y}`;
for (let i = 1; i < bulletTrailPts.length; i++) {
d += ` L${bulletTrailPts[i].x},${bulletTrailPts[i].y}`;
}
$bulletTrail.setAttribute('d', d);
$bulletTrail.setAttribute('opacity', '0.5');
}
}
}
function loop(ts) {
if (!lastTs) lastTs = ts;
const dt = Math.min((ts - lastTs) / 1000, 0.05); // 限制最大dt
lastTs = ts;
update(dt);
requestAnimationFrame(loop);
}
// 自动启动
requestAnimationFrame(loop);
});
</script>
</body>
</html>
这是一个完整的离心涡流气动供弹原理动画实现,以下是核心设计说明:
视觉设计
- 采用深色工程蓝图美学,以深海军蓝为底色,搭配青色(
#00d4ff)气流粒子和琥珀色(#ff9500)子弹,形成强烈对比 - 叶轮带有 30° 后掠角的弯曲叶片,旋转时清晰可见
- 涡流罩为半透明弧形环,右侧留有切向吸气口缺口连接枪管
- 负压区以脉冲呼吸光晕呈现,直观展示"吸力"来源
动画叙事(三阶段循环)
- 负压生成:叶轮高速旋转,气流粒子从中心螺旋向外扩散,中心负压光晕逐渐增强
- 子弹吸入:琥珀色子弹从弹夹入口出现,在负压吸引下进入涡流腔中心
- 离心供弹:子弹随旋转气流螺旋加速向外,经切向口进入枪管,最终高速射出
TRIZ 理想解呈现
- 右侧浮动面板直接点明核心创新:以旋转气流(电机旋转的衍生资源)替代推弹杆,实现零零件供弹
- 绿色标注(
#00ffa3)标出关键参数:30° 后掠角、0.5mm 间隙、15mm 负压腔深 - 左下角侧视剖图补充垂直维度的结构关系
交互性
- 底部转速滑块可实时调节叶轮转速和动画速度(0.3x ~ 2.5x)
- 三阶段指示器自动高亮当前阶段
- 动画页面加载后立即自动播放,重载即重播
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
