分享图
动画工坊
引擎就绪
<!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分