独立渲染引擎就绪引擎就绪
<!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>
<style>
:root {
--bg-color: #07090F;
--grid-color: rgba(43, 62, 80, 0.2);
--text-main: #E2E8F0;
--text-dim: #94A3B8;
--accent-cyan: #00F0FF;
--accent-orange: #FF4500;
--accent-blue: #0066FF;
--plate-metal: #8B9BB4;
--brazing-copper: #C27845;
--font-mono: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;
--font-sans: 'Inter', -apple-system, 'PingFang SC', 'Noto Sans SC', sans-serif;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-color);
color: var(--text-main);
font-family: var(--font-sans);
overflow: hidden;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
#animation-container {
width: 100%;
height: 100%;
position: relative;
background-image:
linear-gradient(var(--grid-color) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
background-size: 40px 40px;
background-position: center center;
}
svg {
width: 100%;
height: 100%;
display: block;
}
/* 微型信息面板统一样式 - 绝对定位,极简极小,确保不遮挡主视图 */
.info-panel {
position: absolute;
background: rgba(7, 9, 15, 0.75);
border: 1px solid rgba(139, 155, 180, 0.15);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
padding: 16px;
border-radius: 4px;
pointer-events: none;
z-index: 10;
}
.panel-title {
font-family: var(--font-mono);
font-size: 11px;
color: var(--accent-cyan);
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 6px;
}
.panel-title::before {
content: '';
display: block;
width: 4px;
height: 12px;
background: var(--accent-cyan);
}
.panel-content {
font-size: 13px;
line-height: 1.6;
color: var(--text-dim);
}
.highlight { color: #FFF; font-weight: 500; }
.hl-cyan { color: var(--accent-cyan); }
.hl-orange { color: var(--accent-orange); }
/* 顶部标题区 */
.header {
position: absolute;
top: 40px;
left: 60px;
pointer-events: none;
}
.header h1 {
font-size: 20px;
font-weight: 600;
letter-spacing: 0.05em;
margin-bottom: 4px;
}
.header p {
font-family: var(--font-mono);
font-size: 12px;
color: var(--text-dim);
}
/* 各个角落的数据面板 */
.panel-ifr { top: 110px; left: 60px; width: 280px; }
.panel-params { bottom: 60px; left: 60px; width: 280px; }
.panel-limits { bottom: 60px; right: 60px; width: 300px; text-align: right; }
.panel-limits .panel-title { justify-content: flex-end; }
.panel-limits .panel-title::before { display: none; }
.panel-limits .panel-title::after {
content: ''; display: block; width: 4px; height: 12px; background: #FF3366;
}
/* 状态指示器与图例 */
.legend {
position: absolute;
top: 60px;
right: 60px;
font-size: 12px;
display: flex;
flex-direction: column;
gap: 8px;
font-family: var(--font-mono);
}
.legend-item { display: flex; align-items: center; gap: 8px; }
.dot { width: 8px; height: 8px; border-radius: 50%; }
.dot-r290 { background: var(--accent-cyan); box-shadow: 0 0 8px var(--accent-cyan); }
.dot-water-hot { background: var(--accent-orange); }
.dot-water-cold { background: var(--accent-blue); }
/* 脉冲动画 */
@keyframes pulse-opacity {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.8; }
}
.pulse { animation: pulse-opacity 3s ease-in-out infinite; }
</style>
</head>
<body>
<div id="animation-container">
<!-- 主交互与渲染视口 -->
<svg viewBox="0 0 1600 1000" preserveAspectRatio="xMidYMid meet" id="main-svg">
<defs>
<!-- 滤镜与渐变定义 -->
<filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<filter id="glow-orange" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<linearGradient id="grad-plate" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#475569" />
<stop offset="50%" stop-color="#94A3B8" />
<stop offset="100%" stop-color="#475569" />
</linearGradient>
<linearGradient id="grad-shell" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="rgba(148, 163, 184, 0.05)" />
<stop offset="50%" stop-color="rgba(148, 163, 184, 0.15)" />
<stop offset="100%" stop-color="rgba(148, 163, 184, 0.05)" />
</linearGradient>
<!-- 人字波纹微观背景图纹 -->
<pattern id="herringbone" x="0" y="0" width="40" height="40" patternUnits="userSpaceOnUse" patternTransform="rotate(15)">
<path d="M0 20 L20 0 L40 20 L20 40 Z" fill="none" stroke="rgba(194, 120, 69, 0.3)" stroke-width="1"/>
<path d="M0 0 L40 40 M40 0 L0 40" fill="none" stroke="rgba(148, 163, 184, 0.2)" stroke-width="1.5"/>
</pattern>
</defs>
<!-- 1. IFR 宏观对比:原壳管体积虚线轮廓 -->
<g class="pulse" transform="translate(150, 150)">
<rect x="0" y="0" width="1300" height="700" rx="40" fill="url(#grad-shell)" stroke="rgba(148,163,184,0.3)" stroke-width="2" stroke-dasharray="10 10" />
<text x="650" y="50" fill="rgba(148,163,184,0.5)" font-family="var(--font-mono)" font-size="16" text-anchor="middle" letter-spacing="4">ORIGINAL SHELL-AND-TUBE VOLUME (100%)</text>
</g>
<!-- 2. 局部放大镜:展示波纹与钎焊结构 -->
<g transform="translate(1300, 300)">
<circle cx="0" cy="0" r="140" fill="url(#herringbone)" stroke="#475569" stroke-width="2" />
<circle cx="0" cy="0" r="148" fill="none" stroke="var(--accent-cyan)" stroke-width="1" stroke-dasharray="4 8" class="pulse"/>
<line x1="-140" y1="0" x2="-220" y2="-50" stroke="#475569" stroke-width="1" stroke-dasharray="4 4"/>
<line x1="-140" y1="0" x2="-220" y2="150" stroke="#475569" stroke-width="1" stroke-dasharray="4 4"/>
<rect x="-100" y="-30" width="200" height="60" fill="rgba(7,9,15,0.8)" />
<text x="0" y="-5" fill="var(--text-main)" font-family="var(--font-mono)" font-size="12" text-anchor="middle">人字形 60°/30° 波纹叠加</text>
<text x="0" y="15" fill="var(--brazing-copper)" font-family="var(--font-mono)" font-size="10" text-anchor="middle">铜/镍 钎焊网格点</text>
</g>
<!-- 3. 核心换热器结构:板片与流道将被 JS 动态生成填充于此 -->
<!-- 中心区域界定: X: 600 - 1000, Y: 200 - 800 -->
<!-- 标注流向箭头 -->
<g stroke-width="2" font-family="var(--font-mono)" font-size="12">
<!-- R290 上进下出 -->
<path d="M 800 120 L 800 180" stroke="var(--accent-cyan)" marker-end="url(#arrow-cyan)" />
<text x="815" y="140" fill="var(--accent-cyan)">R290 液相 (IN)</text>
<path d="M 800 820 L 800 880" stroke="#FFF" opacity="0.8" marker-end="url(#arrow-white)" />
<text x="815" y="870" fill="#FFF">R290 气相 (OUT)</text>
<!-- 水 反向下进上出 -->
<path d="M 700 880 L 700 820" stroke="var(--accent-orange)" marker-end="url(#arrow-orange)" />
<text x="685" y="870" fill="var(--accent-orange)" text-anchor="end">冷却水 (IN)</text>
<path d="M 900 880 L 900 820" stroke="var(--accent-orange)" marker-end="url(#arrow-orange)" />
<text x="915" y="870" fill="var(--accent-orange)">冷却水 (IN)</text>
<path d="M 700 180 L 700 120" stroke="var(--accent-blue)" marker-end="url(#arrow-blue)" />
<text x="685" y="140" fill="var(--accent-blue)" text-anchor="end">冷水 (OUT)</text>
<path d="M 900 180 L 900 120" stroke="var(--accent-blue)" marker-end="url(#arrow-blue)" />
<text x="915" y="140" fill="var(--accent-blue)">冷水 (OUT)</text>
</g>
<defs>
<marker id="arrow-cyan" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--accent-cyan)" />
</marker>
<marker id="arrow-white" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#FFF" />
</marker>
<marker id="arrow-orange" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--accent-orange)" />
</marker>
<marker id="arrow-blue" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--accent-blue)" />
</marker>
</defs>
<!-- 动态渲染容器 -->
<g id="exchanger-core"></g>
<g id="fluid-particles"></g>
<g id="heat-transfer-arrows"></g>
</svg>
</div>
<!-- 浮动信息层 -->
<div class="header">
<h1>Brazed Plate Heat Exchanger</h1>
<p>R290 LOW-CHARGE SYSTEM DYNAMICS</p>
</div>
<div class="info-panel panel-ifr">
<div class="panel-title">最终理想解 (IFR)</div>
<div class="panel-content">
采用<span class="highlight">多层薄板窄流道</span>替代庞大管束。<br>
利用空间几何极值,在不牺牲换热面积前提下,使滞留液态冷媒<span class="hl-cyan">内容积骤减60%</span>,彻底消除 R290 充注量过大痛点。
</div>
</div>
<div class="info-panel panel-params">
<div class="panel-title">核心运作机理与参数</div>
<div class="panel-content">
• 逆流换热:水与 R290 完全反向流动<br>
• 强湍流态:波纹引发流体自扰动与沸腾<br>
• 物理微观:板间距 <span class="highlight">2.5mm</span> | 板厚 <span class="highlight">0.4mm</span><br>
• 压降控制:水侧压降 <span class="hl-orange">≤ 60kPa</span><br>
• 动态平衡:无储液器,膨胀阀 5K 过热度控流
</div>
</div>
<div class="info-panel panel-limits">
<div class="panel-title">适用边界与失效预警</div>
<div class="panel-content">
适用于中小型水冷/风冷系统。极窄流道导致对颗粒物高度敏感;<br>
<span class="highlight">结垢、水质差及频繁化学清洗环境慎用</span>。<br>
低温工况须配置严格防冻逻辑。
</div>
</div>
<div class="legend">
<div class="legend-item"><div class="dot dot-r290"></div>R290 两相沸腾流</div>
<div class="legend-item"><div class="dot dot-water-hot"></div>循环水 (放热区)</div>
<div class="legend-item"><div class="dot dot-water-cold"></div>循环水 (降温区)</div>
</div>
<script>
// 核心参数设定
const CONFIG = {
coreX: 650, // 换热器起始 X
coreY: 200, // 起始 Y
coreHeight: 600, // 换热器高度
plateCount: 4, // 4片板产生3个通道
channelWidth: 100, // 为了视觉展示放大的通道宽度 (实际2.5mm)
wavePeriod: 80, // 波纹周期
waveAmplitude: 20, // 波纹振幅
particleCountWater: 150,
particleCountR290: 250,
colors: {
waterIn: [255, 69, 0], // 橙红
waterOut: [0, 102, 255], // 蓝
r290In: [0, 240, 255], // 青
r290Out: [255, 255, 255] // 白
}
};
const elements = {
core: document.getElementById('exchanger-core'),
particles: document.getElementById('fluid-particles'),
heatArrows: document.getElementById('heat-transfer-arrows')
};
// 工具:生成波纹路径 (近似平滑折线模拟人字波纹剖面)
function generateWavePath(baseX, startY, endY, phaseOffset) {
let path = `M ${baseX + Math.sin(startY/CONFIG.wavePeriod + phaseOffset)*CONFIG.waveAmplitude} ${startY}`;
for (let y = startY + 5; y <= endY; y += 5) {
const x = baseX + Math.sin(y/CONFIG.wavePeriod + phaseOffset) * CONFIG.waveAmplitude;
path += ` L ${x} ${y}`;
}
return path;
}
// 颜色插值器
function interpolateColor(color1, color2, factor) {
const result = color1.slice();
for (let i = 0; i < 3; i++) {
result[i] = Math.round(result[i] + factor * (color2[i] - color1[i]));
}
return `rgb(${result[0]}, ${result[1]}, ${result[2]})`;
}
let particlesArray = [];
let time = 0;
// 初始化静态结构与粒子数据
function init() {
// 1. 绘制板片
const phases = [0, Math.PI, 0, Math.PI]; // 相邻板相位错开,形成网格交叉点
for (let i = 0; i < CONFIG.plateCount; i++) {
const baseX = CONFIG.coreX + i * CONFIG.channelWidth;
const pathStr = generateWavePath(baseX, CONFIG.coreY, CONFIG.coreY + CONFIG.coreHeight, phases[i]);
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("d", pathStr);
path.setAttribute("fill", "none");
path.setAttribute("stroke", "url(#grad-plate)");
path.setAttribute("stroke-width", "4");
path.setAttribute("stroke-linejoin", "round");
elements.core.appendChild(path);
// 绘制钎焊接触点 (板间的交叉相切处)
if (i > 0) {
for (let y = CONFIG.coreY + CONFIG.wavePeriod/4; y < CONFIG.coreY + CONFIG.coreHeight; y += Math.PI * CONFIG.wavePeriod) {
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.setAttribute("cx", baseX - CONFIG.channelWidth/2);
circle.setAttribute("cy", y + 10);
circle.setAttribute("r", "3");
circle.setAttribute("fill", "var(--brazing-copper)");
elements.core.appendChild(circle);
}
}
}
// 2. 初始化粒子系统
// 通道 1 & 3: 水 (向上流动)
[0, 2].forEach(chIndex => {
const baseX = CONFIG.coreX + chIndex * CONFIG.channelWidth + CONFIG.channelWidth/2;
for (let i = 0; i < CONFIG.particleCountWater; i++) {
particlesArray.push({
type: 'water',
channel: chIndex,
baseX: baseX,
y: CONFIG.coreY + Math.random() * CONFIG.coreHeight,
speed: - (1 + Math.random() * 1.5), // 向上
offsetScale: (Math.random() - 0.5) * (CONFIG.channelWidth - 20),
phase: phases[chIndex], // 跟随所在通道的波纹几何
element: createParticle(3, '#fff')
});
}
});
// 通道 2: R290 (向下流动, 沸腾膨胀)
const r290ChIndex = 1;
const r290BaseX = CONFIG.coreX + r290ChIndex * CONFIG.channelWidth + CONFIG.channelWidth/2;
for (let i = 0; i < CONFIG.particleCountR290; i++) {
particlesArray.push({
type: 'r290',
channel: r290ChIndex,
baseX: r290BaseX,
y: CONFIG.coreY + Math.random() * CONFIG.coreHeight,
speed: (1.5 + Math.random() * 2.5), // 向下较快
offsetScale: (Math.random() - 0.5) * (CONFIG.channelWidth - 20),
phase: phases[r290ChIndex],
turbulence: Math.random() * Math.PI * 2, // 沸腾随机横向扰动
element: createParticle(4, 'var(--accent-cyan)', 'url(#glow-cyan)')
});
}
// 3. 传热动画箭头预生成
for (let i = 0; i < 8; i++) {
createHeatWave(i * (CONFIG.coreHeight / 8) + CONFIG.coreY);
}
requestAnimationFrame(animate);
}
function createParticle(r, color, filter = '') {
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
circle.setAttribute("r", r);
circle.setAttribute("fill", color);
if(filter) circle.setAttribute("filter", filter);
elements.particles.appendChild(circle);
return circle;
}
function createHeatWave(yPos) {
// 从水通道(0, 2)指向 R290通道(1)
const leftWave = document.createElementNS("http://www.w3.org/2000/svg", "path");
leftWave.setAttribute("stroke", "var(--accent-orange)");
leftWave.setAttribute("stroke-width", "2");
leftWave.setAttribute("fill", "none");
leftWave.setAttribute("filter", "url(#glow-orange)");
elements.heatArrows.appendChild(leftWave);
const rightWave = leftWave.cloneNode();
elements.heatArrows.appendChild(rightWave);
particlesArray.push({
type: 'heat',
y: yPos,
el1: leftWave,
el2: rightWave,
progress: Math.random() * 100
});
}
// 主渲染循环
function animate() {
time += 0.05;
particlesArray.forEach(p => {
if (p.type === 'water') {
p.y += p.speed;
if (p.y < CONFIG.coreY) p.y = CONFIG.coreY + CONFIG.coreHeight; // 循环
// 计算由于波纹形状造成的X轴基础偏移
const wavePhase = (p.channel === 0 || p.channel === 2) ? 0 : Math.PI; // 简化近似
const waveX = Math.sin(p.y/CONFIG.wavePeriod + wavePhase) * (CONFIG.waveAmplitude * 0.8);
const x = p.baseX + waveX + p.offsetScale;
// 颜色依据进度渐变 (下红 上蓝)
const progress = (p.y - CONFIG.coreY) / CONFIG.coreHeight;
const color = interpolateColor(CONFIG.colors.waterOut, CONFIG.colors.waterIn, progress);
p.element.setAttribute("cx", x);
p.element.setAttribute("cy", p.y);
p.element.setAttribute("fill", color);
p.element.setAttribute("r", 2.5 + Math.random()*1); // 细微流体感
} else if (p.type === 'r290') {
p.y += p.speed;
if (p.y > CONFIG.coreY + CONFIG.coreHeight) {
p.y = CONFIG.coreY;
p.offsetScale = (Math.random() - 0.5) * (CONFIG.channelWidth - 20);
}
// 进度 (0 = 顶, 1 = 底)
const progress = (p.y - CONFIG.coreY) / CONFIG.coreHeight;
// 波纹偏移 + 剧烈沸腾引起的布朗运动
p.turbulence += 0.2;
const boilX = Math.sin(p.turbulence) * (progress * 15); // 越靠下沸腾越剧烈
const waveX = Math.sin(p.y/CONFIG.wavePeriod + Math.PI) * (CONFIG.waveAmplitude * 0.8);
// 约束在流道内
let xOffset = waveX + p.offsetScale + boilX;
const limit = CONFIG.channelWidth/2 - 5;
xOffset = Math.max(-limit, Math.min(limit, xOffset));
const x = p.baseX + xOffset;
// R290 吸热相变:青色液体 -> 白色气体,半径变大,透明度降低
const color = interpolateColor(CONFIG.colors.r290In, CONFIG.colors.r290Out, progress * 1.5);
const radius = 3 + progress * 6; // 气泡膨胀
const opacity = 1 - (progress * 0.5);
p.element.setAttribute("cx", x);
p.element.setAttribute("cy", p.y);
p.element.setAttribute("fill", color);
p.element.setAttribute("r", radius);
p.element.setAttribute("opacity", opacity);
} else if (p.type === 'heat') {
// 热传导波纹动画
p.progress += 2;
if(p.progress > 100) {
p.progress = 0;
p.y = CONFIG.coreY + Math.random() * CONFIG.coreHeight;
}
const xCenter = CONFIG.coreX + 1.5 * CONFIG.channelWidth;
const spread = (p.progress / 100) * (CONFIG.channelWidth - 20);
const opacity = 1 - (p.progress / 100);
// 左侧向右传热 (通道1 -> 通道2)
p.el1.setAttribute("d", `M ${CONFIG.coreX + 0.5*CONFIG.channelWidth + spread/2} ${p.y} L ${CONFIG.coreX + 1.5*CONFIG.channelWidth - spread} ${p.y}`);
p.el1.setAttribute("opacity", opacity * 0.6);
// 右侧向左传热 (通道3 -> 通道2)
p.el2.setAttribute("d", `M ${CONFIG.coreX + 2.5*CONFIG.channelWidth - spread/2} ${p.y} L ${CONFIG.coreX + 1.5*CONFIG.channelWidth + spread} ${p.y}`);
p.el2.setAttribute("opacity", opacity * 0.6);
}
});
requestAnimationFrame(animate);
}
// DOM 加载完成后严格自启
window.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>
积分规则:每轮对话扣10分
等待动画代码生成...
