分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TRIZ IFR: 仿生锯齿文丘里管降噪系统</title>
    <style>
        :root {
            --bg-base: #030610;
            --grid-color: rgba(30, 58, 138, 0.2);
            --cyan-glow: #00F0FF;
            --magenta-glow: #FF007A;
            --warning-red: #FF3333;
            --text-main: #8892B0;
            --text-light: #E2E8F0;
            --panel-bg: rgba(5, 10, 20, 0.7);
            --panel-border: rgba(0, 240, 255, 0.2);
        }

        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }

        body {
            background-color: var(--bg-base);
            color: var(--text-main);
            font-family: 'Inter', system-ui, -apple-system, sans-serif;
            overflow: hidden;
            width: 100vw;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        #animation-container {
            position: relative;
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: center;
            align-items: center;
            background: radial-gradient(circle at center, #0B1120 0%, #030610 100%);
        }

        /* SVG Stying */
        svg {
            width: 100%;
            height: 100%;
            max-width: 1600px;
            max-height: 900px;
            display: block;
        }

        .neon-cyan { stroke: var(--cyan-glow); filter: drop-shadow(0 0 4px var(--cyan-glow)); }
        .neon-magenta { stroke: var(--magenta-glow); filter: drop-shadow(0 0 4px var(--magenta-glow)); }
        
        .solid-body { fill: #0F172A; stroke: #1E293B; stroke-width: 2; }
        .tech-line { stroke: rgba(0, 240, 255, 0.3); stroke-width: 1; stroke-dasharray: 4 4; }
        
        text {
            font-family: 'Courier New', monospace;
            font-size: 12px;
            fill: var(--text-main);
            user-select: none;
            letter-spacing: 1px;
        }
        
        .text-highlight { fill: var(--cyan-glow); font-weight: bold; }
        .text-warning { fill: var(--warning-red); opacity: 0; transition: opacity 0.3s ease; }

        /* UI Overlays (Kept to edges, small sizes) */
        .ui-panel {
            position: absolute;
            background: var(--panel-bg);
            border: 1px solid var(--panel-border);
            border-radius: 8px;
            padding: 16px;
            backdrop-filter: blur(8px);
            z-index: 10;
        }

        .top-left-panel { top: 24px; left: 24px; max-width: 300px; }
        .bottom-left-panel { bottom: 24px; left: 24px; width: 280px; }

        h1 {
            font-size: 14px;
            color: var(--text-light);
            margin-bottom: 6px;
            text-transform: uppercase;
            letter-spacing: 1.5px;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        h1::before {
            content: '';
            display: inline-block;
            width: 8px;
            height: 8px;
            background-color: var(--cyan-glow);
            box-shadow: 0 0 8px var(--cyan-glow);
            border-radius: 50%;
        }

        p.desc { font-size: 11px; line-height: 1.6; color: #64748B; }

        /* Slider Controls */
        .control-group { margin-top: 12px; }
        .control-label {
            display: flex;
            justify-content: space-between;
            font-size: 10px;
            color: var(--cyan-glow);
            margin-bottom: 6px;
        }
        
        input[type=range] {
            -webkit-appearance: none;
            width: 100%;
            background: rgba(255,255,255,0.05);
            height: 4px;
            border-radius: 2px;
            outline: none;
        }
        input[type=range]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: var(--cyan-glow);
            box-shadow: 0 0 10px var(--cyan-glow);
            cursor: pointer;
            transition: transform 0.1s;
        }
        input[type=range]::-webkit-slider-thumb:hover { transform: scale(1.2); }

        /* Status Indicator */
        .status-dot {
            display: inline-block;
            width: 6px;
            height: 6px;
            border-radius: 50%;
            margin-right: 4px;
            background-color: var(--cyan-glow);
            box-shadow: 0 0 6px var(--cyan-glow);
        }
        .status-dot.danger {
            background-color: var(--warning-red);
            box-shadow: 0 0 6px var(--warning-red);
        }
    </style>
</head>
<body>

<div id="animation-container">
    <!-- UI Panels -->
    <div class="ui-panel top-left-panel">
        <h1>TRIZ IFR 最终理想解</h1>
        <p class="desc">
            方案聚焦:引入流线型文丘里收缩管与仿生猫头鹰翼缘锯齿。<br>
            理想状态:消除台阶涡流,打散边界层剪切,实现高频白噪音转化。
        </p>
    </div>

    <div class="ui-panel bottom-left-panel">
        <div class="control-label">
            <span><i class="status-dot" id="status-indicator"></i> 运行状态监测</span>
            <span id="speed-value">标准流速</span>
        </div>
        <p class="desc" style="margin-bottom:8px;" id="status-desc">系统稳定运行,湍流碰撞完全消除。</p>
        <div class="control-group">
            <div class="control-label"><span>风机负载系数 (Mach)</span></div>
            <input type="range" id="speed-slider" min="1" max="10" value="4" step="0.1">
        </div>
    </div>

    <!-- Main SVG Animation -->
    <svg viewBox="0 0 1400 800" preserveAspectRatio="xMidYMid meet" id="main-scene">
        <defs>
            <!-- Grid Pattern -->
            <pattern id="hex-grid" width="40" height="40" patternUnits="userSpaceOnUse">
                <path d="M 40 0 L 0 0 0 40" fill="none" stroke="var(--grid-color)" stroke-width="0.5"/>
                <circle cx="0" cy="0" r="1" fill="var(--grid-color)"/>
            </pattern>

            <!-- Glow Filters -->
            <filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
                <feGaussianBlur stdDeviation="3" result="blur" />
                <feComposite in="SourceGraphic" in2="blur" operator="over" />
            </filter>
            <filter id="glow-magenta" x="-50%" y="-50%" width="200%" height="200%">
                <feGaussianBlur stdDeviation="4" result="blur" />
                <feComposite in="SourceGraphic" in2="blur" operator="over" />
            </filter>
            
            <!-- Metallic Body Gradient -->
            <linearGradient id="body-grad" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0%" stop-color="#1E293B" stop-opacity="0.8"/>
                <stop offset="50%" stop-color="#0B1120" stop-opacity="0.9"/>
                <stop offset="100%" stop-color="#1E293B" stop-opacity="0.8"/>
            </linearGradient>

            <linearGradient id="edge-grad" x1="0" y1="0" x2="1" y2="0">
                <stop offset="0%" stop-color="#0B1120"/>
                <stop offset="100%" stop-color="#1E293B"/>
            </linearGradient>

            <!-- Clip Path for Magnifier -->
            <clipPath id="magnify-clip">
                <circle cx="1150" cy="250" r="140" />
            </clipPath>
        </defs>

        <!-- Background -->
        <rect width="100%" height="100%" fill="url(#hex-grid)" />

        <!-- ============================== -->
        <!-- MAIN CROSS SECTION VIEW        -->
        <!-- ============================== -->
        <g id="main-duct" transform="translate(50, 0)">
            
            <!-- Dimension Lines & Annotations -->
            <g opacity="0.6">
                <!-- Inlet -->
                <line x1="50" y1="200" x2="50" y2="600" stroke="#475569" stroke-width="1" stroke-dasharray="2 2"/>
                <text x="60" y="405" fill="#94A3B8">INTAKE / 广口进气段</text>
                
                <!-- Transition Angle -->
                <path d="M 450,200 A 100,100 0 0,1 550,230" fill="none" stroke="#475569" stroke-width="1" stroke-dasharray="2 2"/>
                <text x="460" y="190" fill="#94A3B8">θ &lt; 15° 平滑过渡控制</text>
                
                <!-- Bernoulli Equation decorative -->
                <text x="450" y="650" fill="#475569" font-style="italic">P₁ + ½ρv₁² = P₂ + ½ρv₂² (伯努利原理)</text>
            </g>

            <!-- Solid Wall Background / Hardware -->
            <!-- Top Wall Profile: Inlet y=200, smooth curve down to y=320 at x=800 -->
            <path d="M -50,150 L 400,150 L 400,200 C 550,200 650,320 800,320 L 800,150 Z" fill="url(#body-grad)"/>
            <path d="M -50,200 L 400,200 C 550,200 650,320 800,320" fill="none" class="neon-cyan" stroke-width="2"/>
            
            <!-- Bottom Wall Profile: Inlet y=600, smooth curve up to y=480 at x=800 -->
            <path d="M -50,650 L 400,650 L 400,600 C 550,600 650,480 800,480 L 800,650 Z" fill="url(#body-grad)"/>
            <path d="M -50,600 L 400,600 C 550,600 650,480 800,480" fill="none" class="neon-cyan" stroke-width="2"/>

            <!-- Trailing Edge Hardware block (Right side before exit) -->
            <rect x="800" y="320" width="20" height="160" fill="#334155" />
            <line x1="820" y1="320" x2="820" y2="480" stroke="#00F0FF" stroke-width="2" stroke-dasharray="4 2"/>

            <!-- Invisible Flow Paths for JS Particles -->
            <g id="flow-paths" opacity="0">
                <path class="flow-path" d="M -20,230 L 400,230 C 550,230 650,335 820,335" />
                <path class="flow-path" d="M -20,280 L 400,280 C 550,280 650,360 820,360" />
                <path class="flow-path" d="M -20,340 L 400,340 C 550,340 650,385 820,385" />
                <path class="flow-path" d="M -20,400 L 820,400" /> <!-- Centerline -->
                <path class="flow-path" d="M -20,460 L 400,460 C 550,460 650,415 820,415" />
                <path class="flow-path" d="M -20,520 L 400,520 C 550,520 650,440 820,440" />
                <path class="flow-path" d="M -20,570 L 400,570 C 550,570 650,465 820,465" />
            </g>

            <!-- Dynamic Particle Groups -->
            <g id="particles-group" filter="url(#glow-cyan)"></g>
            <g id="main-vortices-group" filter="url(#glow-magenta)"></g>

            <!-- Annotations Pointing to Structure -->
            <circle cx="600" cy="270" r="4" fill="#00F0FF" />
            <line x1="600" y1="270" x2="520" y2="160" class="tech-line"/>
            <rect x="360" y="130" width="160" height="24" fill="#050B14" stroke="#1E3A8A" stroke-width="1"/>
            <text x="370" y="146" class="text-highlight">曲率连续 / 边界层防剥离</text>

            <circle cx="820" cy="330" r="4" fill="#FF007A" />
            <line x1="820" y1="330" x2="880" y2="280" class="tech-line"/>
            <text x="840" y="270" fill="#E2E8F0">边缘激扰区</text>

        </g>


        <!-- ============================== -->
        <!-- MAGNIFIER CALLOUT (TOP DOWN)   -->
        <!-- ============================== -->
        <!-- Connection lines -->
        <path d="M 870, 400 L 980, 250 L 1010, 250" fill="none" stroke="#475569" stroke-width="1" stroke-dasharray="4 4"/>
        <path d="M 870, 320 L 980, 150 L 1010, 150" fill="none" stroke="#475569" stroke-width="1" stroke-dasharray="4 4"/>

        <!-- Magnifier Frame -->
        <circle cx="1150" cy="250" r="145" fill="none" stroke="var(--cyan-glow)" stroke-width="2" opacity="0.3"/>
        <circle cx="1150" cy="250" r="140" fill="#050912" stroke="#1E293B" stroke-width="4"/>
        
        <!-- Inside Magnifier -->
        <g clip-path="url(#magnify-clip)">
            <!-- Grid inside -->
            <rect x="1000" y="100" width="300" height="300" fill="url(#hex-grid)" opacity="0.5"/>
            
            <!-- Solid hardware (Trailing edge from top-down) -->
            <path d="M 1000,100 L 1150,100 
                     L 1150,130 L 1180,145 L 1150,160 
                     L 1150,190 L 1180,205 L 1150,220 
                     L 1150,250 L 1180,265 L 1150,280 
                     L 1150,310 L 1180,325 L 1150,340 
                     L 1150,370 L 1180,385 L 1150,400 
                     L 1000,400 Z" 
                  fill="url(#edge-grad)" stroke="#38BDF8" stroke-width="1.5"/>

            <!-- JS Spawner area for Callout -->
            <g id="callout-particles" filter="url(#glow-cyan)"></g>
            <g id="callout-vortices" filter="url(#glow-magenta)"></g>
        </g>

        <!-- Magnifier UI Overlay Elements -->
        <path d="M 1010,250 L 1030,250" stroke="#00F0FF" stroke-width="2"/>
        <circle cx="1150" cy="250" r="140" fill="none" stroke="url(#body-grad)" stroke-width="8"/>
        <circle cx="1150" cy="250" r="140" fill="none" stroke="#00F0FF" stroke-width="1" stroke-dasharray="2 6"/>
        
        <!-- Callout Text -->
        <rect x="1200" y="100" width="160" height="50" fill="rgba(5, 11, 20, 0.8)" rx="4"/>
        <text x="1210" y="120" class="text-highlight">仿生微小锯齿构型</text>
        <text x="1210" y="140" fill="#94A3B8">深度: 2-3mm / 切割大尺度涡</text>

        <!-- Overspeed Warning Label -->
        <g id="warning-label" class="text-warning" transform="translate(1100, 420)">
            <rect x="0" y="0" width="180" height="24" fill="rgba(255, 51, 51, 0.1)" stroke="#FF3333" stroke-width="1" rx="2"/>
            <text x="10" y="16" fill="#FF3333" font-weight="bold">⚠️ 边界突破:微结构哨音共振</text>
        </g>
    </svg>
</div>

<script>
document.addEventListener("DOMContentLoaded", () => {
    // DOM Elements
    const flowPaths = document.querySelectorAll('.flow-path');
    const mainParticlesGroup = document.getElementById('particles-group');
    const mainVorticesGroup = document.getElementById('main-vortices-group');
    
    const calloutParticlesGroup = document.getElementById('callout-particles');
    const calloutVorticesGroup = document.getElementById('callout-vortices');
    
    const speedSlider = document.getElementById('speed-slider');
    const statusIndicator = document.getElementById('status-indicator');
    const speedValueText = document.getElementById('speed-value');
    const statusDescText = document.getElementById('status-desc');
    const warningLabel = document.getElementById('warning-label');

    // Data Structures
    const particles = [];
    const vortices = [];
    const calloutParticles = [];
    const calloutVortices = [];

    // Configuration
    const OVERSPEED_THRESHOLD = 7.5;
    const NUM_PARTICLES_PER_PATH = 12;
    const BASE_SPEED_MULT = 0.0008;

    // Helper: Create SVG Element
    function createSvgEl(type, attrs, parent) {
        const el = document.createElementNS('http://www.w3.org/2000/svg', type);
        for (let key in attrs) el.setAttribute(key, attrs[key]);
        parent.appendChild(el);
        return el;
    }

    // Initialize Main Flow Particles
    flowPaths.forEach(path => {
        const len = path.getTotalLength();
        for (let i = 0; i < NUM_PARTICLES_PER_PATH; i++) {
            particles.push({
                path: path,
                len: len,
                progress: i / NUM_PARTICLES_PER_PATH,
                // Using lines for a "streaking" high-speed look instead of dots
                el: createSvgEl('line', {
                    stroke: '#00F0FF',
                    'stroke-width': 1.5,
                    'stroke-linecap': 'round',
                    opacity: 0.8
                }, mainParticlesGroup)
            });
        }
    });

    // Initialize Callout Flow Particles (Horizontal straight lines hitting the edge)
    // The edge is at x=1150-1180, let's spawn them at x=1000
    const calloutYPositions = [120, 150, 180, 210, 240, 270, 300, 330, 360, 390];
    calloutYPositions.forEach(y => {
        for(let i=0; i<3; i++) {
            calloutParticles.push({
                x: 1000 + i * 50,
                y: y + (Math.random()*10 - 5),
                el: createSvgEl('line', {
                    stroke: '#00F0FF',
                    'stroke-width': 1.5,
                    opacity: 0.6
                }, calloutParticlesGroup)
            });
        }
    });


    // Animation Loop
    function animate() {
        const rawSpeed = parseFloat(speedSlider.value);
        const baseSpeed = rawSpeed * BASE_SPEED_MULT;
        const isOverspeed = rawSpeed >= OVERSPEED_THRESHOLD;

        // Update UI state
        if (isOverspeed) {
            statusIndicator.classList.add('danger');
            speedValueText.textContent = `流速异常 (Mach ${rawSpeed.toFixed(1)})`;
            speedValueText.style.color = 'var(--warning-red)';
            statusDescText.textContent = "流速过高,锯齿结构产生高频共振啸叫失效。";
            warningLabel.style.opacity = '1';
        } else {
            statusIndicator.classList.remove('danger');
            speedValueText.textContent = `最优工况 (Mach ${rawSpeed.toFixed(1)})`;
            speedValueText.style.color = 'var(--cyan-glow)';
            statusDescText.textContent = "文丘里效应平滑加速,锯齿边缘转化剪切涡流为高频白噪。";
            warningLabel.style.opacity = '0';
        }

        const vortexColor = isOverspeed ? '#FF3333' : '#FF007A';

        // 1. Process Main Duct Particles
        particles.forEach(p => {
            // Acceleration logic: Bernoulli principle in Venturi tube
            // Throat is approximately progress 0.5 to 0.9. Speed increases significantly here.
            let speedFactor = 1;
            if (p.progress > 0.4) {
                // Ramp up speed smoothly up to 3.5x in the throat
                speedFactor = 1 + Math.sin((p.progress - 0.4) * Math.PI) * 2.5; 
            }
            
            p.progress += baseSpeed * speedFactor;

            if (p.progress >= 0.98) {
                // Reach the exit trailing edge -> Shatter into micro-vortices
                p.progress = 0;
                spawnMainVortex(p.path.getPointAtLength(p.len), vortexColor, isOverspeed, rawSpeed);
            }

            const pt1 = p.path.getPointAtLength(p.progress * p.len);
            // Trail calculation based on speed
            const trailLen = 5 + (speedFactor * rawSpeed * 1.5);
            const backProgress = Math.max(0, p.progress - (trailLen / p.len));
            const pt2 = p.path.getPointAtLength(backProgress * p.len);

            p.el.setAttribute('x1', pt1.x);
            p.el.setAttribute('y1', pt1.y);
            p.el.setAttribute('x2', pt2.x);
            p.el.setAttribute('y2', pt2.y);
        });

        // 2. Process Main Vortices
        updateVortices(vortices, isOverspeed);

        // 3. Process Callout Particles
        calloutParticles.forEach(p => {
            const currentSpeed = (baseSpeed * 1000) * 1.5;
            p.x += currentSpeed;
            
            // Interaction with the zigzag edge (approx x=1160)
            if (p.x > 1160) {
                spawnCalloutVortex(p.x, p.y, vortexColor, isOverspeed);
                p.x = 1000;
                p.y += (Math.random()*20 - 10); // slight random shift
            }

            p.el.setAttribute('x1', p.x);
            p.el.setAttribute('y1', p.y);
            p.el.setAttribute('x2', p.x - (currentSpeed * 4));
            p.el.setAttribute('y2', p.y);
        });

        // 4. Process Callout Vortices
        updateVortices(calloutVortices, isOverspeed, true);

        requestAnimationFrame(animate);
    }

    function spawnMainVortex(pt, color, isOverspeed, speedVal) {
        // IFR behavior: large low-frequency vortices are broken into tiny high-freq noise
        // Over-speed behavior: resonance creates larger, grouped distinct pulses
        const count = isOverspeed ? 1 : 3;
        const sizeBase = isOverspeed ? 3 : 1;
        
        for (let i = 0; i < count; i++) {
            const el = createSvgEl('circle', {
                r: sizeBase + Math.random() * 1.5,
                fill: color,
                opacity: 0.9
            }, mainVorticesGroup);

            vortices.push({
                el: el,
                x: pt.x,
                y: pt.y,
                // Scatter logic: wider scatter for white noise, concentrated for resonance
                vx: (isOverspeed ? 4 : 2) + Math.random() * speedVal * 0.5,
                vy: (Math.random() - 0.5) * (isOverspeed ? 1 : 6),
                life: 1,
                decay: isOverspeed ? 0.015 : 0.04 // White noise dissipates rapidly
            });
        }
    }

    function spawnCalloutVortex(x, y, color, isOverspeed) {
        const count = isOverspeed ? 1 : 2;
        for(let i=0; i<count; i++) {
            const el = createSvgEl('circle', {
                r: (isOverspeed ? 4 : 1.5) + Math.random(),
                fill: color,
                opacity: 0.8
            }, calloutVorticesGroup);

            calloutVortices.push({
                el: el,
                x: x,
                y: y,
                vx: 1 + Math.random() * 2,
                vy: (Math.random() - 0.5) * 4,
                life: 1,
                decay: 0.05
            });
        }
    }

    function updateVortices(vortexArray, isOverspeed, isCallout = false) {
        for (let i = vortexArray.length - 1; i >= 0; i--) {
            let v = vortexArray[i];
            v.life -= v.decay;
            v.x += v.vx;
            v.y += v.vy;

            // Add swirling motion for visual effect
            if(!isOverspeed) {
                v.vy += (Math.random() - 0.5) * 0.5; 
            } else {
                // In overspeed, they oscillate creating a distinct frequency visual
                v.y += Math.sin(v.x * 0.2) * 2;
            }

            if (v.life <= 0 || (isCallout && v.x > 1300)) {
                v.el.remove();
                vortexArray.splice(i, 1);
            } else {
                v.el.setAttribute('cx', v.x);
                v.el.setAttribute('cy', v.y);
                v.el.setAttribute('opacity', v.life);
                if(isOverspeed) {
                    v.el.setAttribute('r', (isCallout?4:3) * v.life + 1); // pulse effect
                }
            }
        }
    }

    // Auto-start
    animate();
});
</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分