分享图
动画工坊
引擎就绪
<!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-base: #050b14;
            --bg-grid: rgba(6, 182, 212, 0.05);
            --accent-cyan: #06b6d4;
            --accent-cyan-glow: #22d3ee;
            --accent-blue: #3b82f6;
            --text-main: #94a3b8;
            --text-bright: #e2e8f0;
            --glass-surface: #0f172a;
            --metal-dark: #1e293b;
            --metal-light: #475569;
            --alert-green: #10b981;
            --alert-orange: #f59e0b;
        }

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

        body {
            background-color: var(--bg-base);
            color: var(--text-main);
            font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
            overflow: hidden;
            width: 100vw;
            height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            background-image: 
                linear-gradient(var(--bg-grid) 1px, transparent 1px),
                linear-gradient(90deg, var(--bg-grid) 1px, transparent 1px);
            background-size: 40px 40px;
            background-position: center center;
        }

        /* 核心动画区,占据最大空间 */
        #animation-container {
            width: 100%;
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            z-index: 1;
        }

        svg {
            width: 100%;
            height: 100%;
            display: block;
        }

        /* HUD 信息层:绝对定位在角落,严格控制尺寸,避免遮挡中心 */
        .hud-panel {
            position: absolute;
            z-index: 10;
            background: rgba(5, 11, 20, 0.75);
            backdrop-filter: blur(4px);
            border: 1px solid rgba(6, 182, 212, 0.2);
            padding: 12px 16px;
            border-radius: 6px;
            font-size: 13px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
            pointer-events: none; /* 让鼠标事件穿透给下层,除非是交互按钮 */
        }

        .hud-top-left {
            top: 24px;
            left: 24px;
            max-width: 320px;
        }

        .hud-top-right {
            top: 24px;
            right: 24px;
            width: 240px;
        }

        .hud-bottom-left {
            bottom: 24px;
            left: 24px;
            max-width: 300px;
        }

        .hud-title {
            color: var(--accent-cyan);
            font-size: 15px;
            font-weight: bold;
            margin-bottom: 6px;
            letter-spacing: 1px;
            text-transform: uppercase;
        }

        .hud-desc {
            line-height: 1.5;
            color: var(--text-bright);
            opacity: 0.85;
            margin-bottom: 8px;
        }

        .hud-tag {
            display: inline-block;
            background: rgba(6, 182, 212, 0.1);
            color: var(--accent-cyan);
            padding: 2px 6px;
            border-radius: 4px;
            font-size: 11px;
            margin-right: 6px;
            margin-bottom: 4px;
            border: 1px solid rgba(6, 182, 212, 0.3);
        }

        /* 状态监控面板样式 */
        .status-row {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
            font-family: monospace;
            font-size: 12px;
        }

        .status-label {
            color: var(--text-main);
        }

        .status-value {
            color: var(--text-bright);
            font-weight: bold;
        }

        .status-indicator {
            display: inline-block;
            width: 8px;
            height: 8px;
            border-radius: 50%;
            margin-right: 6px;
            background-color: var(--text-main);
            box-shadow: 0 0 5px transparent;
            transition: all 0.3s ease;
        }

        .status-indicator.active {
            background-color: var(--alert-green);
            box-shadow: 0 0 8px var(--alert-green);
        }

        .status-indicator.charging {
            background-color: var(--alert-orange);
            box-shadow: 0 0 8px var(--alert-orange);
        }

        .progress-bar-bg {
            width: 100%;
            height: 4px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 2px;
            margin-top: 4px;
            overflow: hidden;
        }

        .progress-bar-fill {
            height: 100%;
            width: 0%;
            background: var(--accent-cyan);
            box-shadow: 0 0 8px var(--accent-cyan);
            transition: width 0.1s linear;
        }

        /* 交互按钮 */
        .interactive-btn {
            pointer-events: auto;
            margin-top: 10px;
            width: 100%;
            background: transparent;
            border: 1px solid var(--accent-cyan);
            color: var(--accent-cyan);
            padding: 6px 0;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
            font-weight: bold;
            text-transform: uppercase;
            transition: all 0.2s;
            letter-spacing: 1px;
        }

        .interactive-btn:hover {
            background: rgba(6, 182, 212, 0.15);
            box-shadow: 0 0 10px rgba(6, 182, 212, 0.3);
        }
        
        .interactive-btn:disabled {
            border-color: var(--text-main);
            color: var(--text-main);
            cursor: not-allowed;
            box-shadow: none;
            background: transparent;
        }

        /* SVG 内动画类 */
        .airflow-path {
            fill: none;
            stroke: var(--accent-cyan);
            stroke-width: 1.5;
            opacity: 0;
            stroke-dasharray: 60 40 20 40;
            stroke-linecap: round;
        }

        .airflow-active .airflow-path {
            opacity: 0.8;
            animation: flowDash 0.4s linear infinite;
        }

        @keyframes flowDash {
            to {
                stroke-dashoffset: -160;
            }
        }

        .dust-particle {
            fill: #a1a1aa;
            transform-origin: center;
            transition: opacity 0.3s;
        }

        .dust-blow-away {
            animation: flyAway 0.8s forwards cubic-bezier(0.1, 0.8, 0.3, 1);
        }

        @keyframes flyAway {
            to {
                transform: translate(calc(400px + 200px * var(--vx)), calc(-150px + 100px * var(--vy))) rotate(calc(720deg * var(--vr))) scale(0.5);
                opacity: 0;
            }
        }

        .pump-vibrate {
            animation: vibrate 0.1s linear infinite;
        }

        @keyframes vibrate {
            0% { transform: translateY(0); }
            25% { transform: translateY(-0.5px); }
            50% { transform: translateY(0); }
            75% { transform: translateY(0.5px); }
            100% { transform: translateY(0); }
        }
    </style>
</head>
<body>

    <!-- 主视觉区 (居中底层) -->
    <div id="animation-container">
        <!-- viewBox 设定大画幅比例,确保适应各种屏幕而不变形 -->
        <svg viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice" id="main-svg">
            <defs>
                <!-- 材质渐变 -->
                <linearGradient id="glass-grad" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="#1e293b" />
                    <stop offset="10%" stop-color="#0f172a" />
                    <stop offset="100%" stop-color="#020617" />
                </linearGradient>
                <linearGradient id="metal-grad" x1="0" y1="0" x2="1" y2="1">
                    <stop offset="0%" stop-color="#64748b" />
                    <stop offset="50%" stop-color="#334155" />
                    <stop offset="100%" stop-color="#1e293b" />
                </linearGradient>
                <linearGradient id="metal-light-grad" x1="0" y1="0" x2="0" y2="1">
                    <stop offset="0%" stop-color="#94a3b8" />
                    <stop offset="100%" stop-color="#475569" />
                </linearGradient>

                <!-- 辉光滤镜 -->
                <filter id="cyan-glow" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="4" result="blur" />
                    <feComposite in="SourceGraphic" in2="blur" operator="over" />
                </filter>
                <filter id="blue-glow" x="-20%" y="-20%" width="140%" height="140%">
                    <feGaussianBlur stdDeviation="8" result="blur" />
                    <feComponentTransfer in="blur" result="glow">
                        <feFuncA type="linear" slope="0.5"/>
                    </feComponentTransfer>
                    <feComposite in="SourceGraphic" in2="glow" operator="over" />
                </filter>

                <!-- 网格图案用于透视参考 -->
                <pattern id="lens-grid" width="100" height="100" patternUnits="userSpaceOnUse">
                    <path d="M 100 0 L 0 0 0 100" fill="none" stroke="rgba(255,255,255,0.03)" stroke-width="1"/>
                </pattern>
            </defs>

            <!-- 坐标系与背景 -->
            <g opacity="0.4">
                <line x1="0" y1="500" x2="1600" y2="500" stroke="var(--text-main)" stroke-width="1" stroke-dasharray="5 5" opacity="0.3"/>
                <text x="50" y="490" fill="var(--text-main)" font-size="12" font-family="monospace">Y=0 (镜面基准线)</text>
            </g>

            <!-- 镜头玻璃主体 (宏观剖面) -->
            <!-- 将焦点放置在偏下中心 -->
            <g id="lens-group" transform="translate(0, 500)">
                <!-- 玻璃本体 -->
                <rect x="0" y="0" width="1600" height="300" fill="url(#glass-grad)" />
                <rect x="0" y="0" width="1600" height="300" fill="url(#lens-grid)" />
                <!-- 表面反光高光 -->
                <rect x="0" y="0" width="1600" height="2" fill="rgba(255,255,255,0.15)" />
                <path d="M 0 2 Q 800 10 1600 2 L 1600 20 Q 800 30 0 20 Z" fill="rgba(255,255,255,0.02)" />
            </g>

            <!-- 气流组 (在粉尘层下方/中间,表现贴壁效应) -->
            <g id="airflow-group" filter="url(#cyan-glow)" class="airflow-inactive">
                <!-- 气流路径将由 JS 动态生成以模拟流场分布 -->
            </g>

            <!-- 环形气刀剖面 (左侧切入) -->
            <g id="air-knife-nozzle">
                <!-- 内部气腔截面 -->
                <path d="M -50 350 L 300 350 L 320 400 L 280 430 L -50 430 Z" fill="url(#metal-grad)" stroke="#0f172a" stroke-width="2"/>
                <!-- 喷嘴上唇边 (呈 15-25 度角指向镜面) -->
                <path d="M -50 430 L 280 430 L 380 495 L 375 498 L 270 435 L -50 435 Z" fill="url(#metal-light-grad)" />
                <!-- 喷嘴下唇边 (贴近镜面) -->
                <path d="M -50 460 L 280 460 L 370 499.5 L 360 500 L 270 465 L -50 465 Z" fill="url(#metal-light-grad)" />
                
                <!-- 几何辅助线:喷射角指示 -->
                <g opacity="0.6">
                    <!-- 切线 -->
                    <line x1="380" y1="495" x2="600" y2="495" stroke="var(--alert-orange)" stroke-width="1" stroke-dasharray="4 4" />
                    <!-- 喷射流线 -->
                    <line x1="380" y1="495" x2="580" y2="540" stroke="var(--alert-orange)" stroke-width="1" stroke-dasharray="4 4" />
                    <!-- 角度圆弧 -->
                    <path d="M 450 495 A 70 70 0 0 1 445 510" fill="none" stroke="var(--alert-orange)" stroke-width="1.5" />
                    <text x="460" y="508" fill="var(--alert-orange)" font-size="12" font-family="monospace">θ ≈ 20°</text>
                </g>
                <!-- 高压进气口指示 -->
                <text x="50" y="395" fill="var(--accent-cyan)" font-size="14" font-weight="bold" font-family="monospace" letter-spacing="2">AIR INLET (0.5MPa) ➔</text>
            </g>

            <!-- 粉尘颗粒组 -->
            <g id="dust-group"></g>

            <!-- 附壁层剪切力指示箭头 (吹气时显示) -->
            <g id="shear-force-arrows" opacity="0" transition="opacity 0.3s">
                <!-- JS 会控制其显示隐藏 -->
            </g>

        </svg>
    </div>

    <!-- HUD 信息面板:左上角 -->
    <div class="hud-panel hud-top-left">
        <div class="hud-title">Ideal Final Result (IFR)</div>
        <div class="hud-desc">利用超薄贴壁气流,在极短时间内施加极高的动态剪切力,剥离镜面粉尘,实现无接触、零磨损自清洁。</div>
        <div>
            <span class="hud-tag">附壁效应 (Coanda)</span>
            <span class="hud-tag">湍流剪切</span>
            <span class="hud-tag">动量交换</span>
        </div>
    </div>

    <!-- HUD 信息面板:右上角状态监控 -->
    <div class="hud-panel hud-top-right">
        <div class="hud-title">System Telemetry</div>
        
        <div class="status-row">
            <span class="status-label"><span id="ind-pump" class="status-indicator"></span>隔膜气泵</span>
            <span class="status-value" id="val-pump">待命</span>
        </div>
        
        <div class="status-row">
            <span class="status-label"><span id="ind-valve" class="status-indicator"></span>脉冲电磁阀</span>
            <span class="status-value" id="val-valve" style="color: var(--text-main);">关闭</span>
        </div>

        <div class="status-row" style="margin-bottom: 2px;">
            <span class="status-label">储气罐压力 (0.5L)</span>
            <span class="status-value"><span id="val-pressure">0.00</span> MPa</span>
        </div>
        <div class="progress-bar-bg">
            <div class="progress-bar-fill" id="bar-pressure"></div>
        </div>
        
        <button id="btn-manual" class="interactive-btn" onclick="manualTrigger()">手动脉冲触发 (Override)</button>
    </div>

    <!-- HUD 信息面板:左下角参数 -->
    <div class="hud-panel hud-bottom-left">
        <div class="hud-title" style="font-size: 12px; color: var(--text-main);">Key Parameters</div>
        <div class="status-row" style="margin-bottom: 4px;">
            <span class="status-label">气缝宽度</span>
            <span class="status-value">0.4 mm</span>
        </div>
        <div class="status-row" style="margin-bottom: 4px;">
            <span class="status-label">喷射偏角</span>
            <span class="status-value">20 ° (切向)</span>
        </div>
        <div class="status-row" style="margin-bottom: 0;">
            <span class="status-label">脉冲时长</span>
            <span class="status-value">0.5 ~ 1.0 s</span>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', () => {
            initScene();
            startAutoCycle();
        });

        // 核心元素获取
        const dustGroup = document.getElementById('dust-group');
        const airflowGroup = document.getElementById('airflow-group');
        const indPump = document.getElementById('ind-pump');
        const valPump = document.getElementById('val-pump');
        const indValve = document.getElementById('ind-valve');
        const valValve = document.getElementById('val-valve');
        const valPressure = document.getElementById('val-pressure');
        const barPressure = document.getElementById('bar-pressure');
        const btnManual = document.getElementById('btn-manual');
        const svgContainer = document.getElementById('main-svg');

        // 系统状态变量
        let currentPressure = 0.0;
        const targetPressure = 0.55;
        let isAnimating = false;
        let cycleTimeout;
        let pressureInterval;
        let dustParticlesArray = [];

        // 1. 场景初始化
        function initScene() {
            generateAirflowPaths();
            generateDust();
        }

        // 生成高保真附壁气流路径
        function generateAirflowPaths() {
            const pathCount = 12;
            const startX = 375;
            const startY = 497;
            
            for (let i = 0; i < pathCount; i++) {
                const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
                path.classList.add("airflow-path");
                
                // 模拟附壁效应 (Coanda effect):气流喷出后向下贴合镜面 (Y=500),然后平行扩散
                // 不同的控制点产生错落有致的流线
                const dropDownX = startX + 50 + Math.random() * 80;
                const endX = 1600;
                // 贴壁高度分层:最底层的最贴近,上层有一定的湍流扩散
                const finalY = 500 - (Math.random() * i * 3) - 1; 
                
                // 贝塞尔曲线:起点 -> 控制点(喷射方向延伸) -> 控制点(贴壁弯折) -> 终点
                const cp1X = startX + 40;
                const cp1Y = startY + Math.tan(20 * Math.PI / 180) * 40; 
                
                const d = `M ${startX} ${startY} C ${cp1X} ${cp1Y}, ${dropDownX - 50} ${finalY}, ${dropDownX} ${finalY} L ${endX} ${finalY}`;
                
                path.setAttribute("d", d);
                // 随机化线条宽度和动画偏移,增加真实感
                path.setAttribute("stroke-width", 1 + Math.random() * 2);
                path.style.opacity = 0.4 + Math.random() * 0.6;
                path.style.animationDuration = (0.2 + Math.random() * 0.3) + "s";
                
                airflowGroup.appendChild(path);
            }
        }

        // 生成粉尘颗粒
        function generateDust() {
            dustGroup.innerHTML = '';
            dustParticlesArray = [];
            // 集中在喷嘴右侧区域
            const dustCount = 150;
            const startX = 420;
            const endX = 1400;

            for (let i = 0; i < dustCount; i++) {
                const particle = document.createElementNS("http://www.w3.org/2000/svg", "path");
                
                // 随机位置 (x) 和堆积高度 (y),模拟真实粉尘分布,边缘薄中间厚
                const x = startX + Math.random() * (endX - startX);
                // 使用正态分布拟合,让粉尘贴近表面
                const depth = Math.pow(Math.random(), 3) * 12; 
                const y = 499 - depth;
                const size = 1 + Math.random() * 3.5;

                // 绘制不规则多边形模拟粉尘
                const d = `M ${x},${y} l ${size},${Math.random()*size} l -${Math.random()*size},${size} l -${size},-${Math.random()*size} Z`;
                
                particle.setAttribute("d", d);
                particle.classList.add("dust-particle");
                
                // 为CSS动画注入随机变量
                particle.style.setProperty('--vx', (0.5 + Math.random() * 1.5).toString());
                particle.style.setProperty('--vy', (Math.random() * 2 - 0.5).toString());
                particle.style.setProperty('--vr', (Math.random() * 2 - 1).toString());
                // 随机延迟产生剥离的层次感
                particle.style.animationDelay = (Math.random() * 0.15) + "s";

                dustGroup.appendChild(particle);
                dustParticlesArray.push(particle);
            }
        }

        // 2. 状态机与时序控制
        
        async function runCycle(isManual = false) {
            if (isAnimating) return;
            isAnimating = true;
            btnManual.disabled = true;

            try {
                // 阶段1: 储能充气 (如果压力不足)
                if (currentPressure < targetPressure) {
                    await stageCharging();
                }

                // 准备动作极短延迟
                await sleep(200);

                // 阶段2: 脉冲吹扫 (核心破除矛盾动作)
                await stageBlowing();

                // 阶段3: 停机待命展示清洁结果
                await stageIdle();

                // 阶段4: 重置 (仅在自动循环时粉尘重新累积)
                if (!isManual) {
                    await stageReset();
                    isAnimating = false;
                    btnManual.disabled = false;
                    // 循环调用
                    startAutoCycle();
                } else {
                    isAnimating = false;
                    btnManual.disabled = false;
                }
            } catch (e) {
                console.error("Animation cycle interrupted", e);
                isAnimating = false;
                btnManual.disabled = false;
            }
        }

        // 启动自动循环
        function startAutoCycle() {
            clearTimeout(cycleTimeout);
            cycleTimeout = setTimeout(() => runCycle(false), 500);
        }

        // 手动触发
        function manualTrigger() {
            clearTimeout(cycleTimeout);
            if (!isAnimating) {
                // 如果是干干净净的状态,先重置粉尘再吹
                if(dustParticlesArray.length > 0 && dustParticlesArray[0].classList.contains('dust-blow-away')){
                    generateDust();
                    setTimeout(() => runCycle(true), 100);
                } else {
                    runCycle(true);
                }
            }
        }

        // 工具函数:非阻塞等待
        function sleep(ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        }

        // 阶段实现函数
        function stageCharging() {
            return new Promise(resolve => {
                // UI 更新
                indPump.className = "status-indicator charging";
                valPump.innerText = "充压中 (60L/min)";
                valPump.style.color = "var(--alert-orange)";
                document.getElementById('air-knife-nozzle').classList.add('pump-vibrate'); // 微微震动模拟泵运行

                // 模拟压力上升
                clearInterval(pressureInterval);
                pressureInterval = setInterval(() => {
                    currentPressure += 0.02;
                    if (currentPressure >= targetPressure) {
                        currentPressure = targetPressure;
                        clearInterval(pressureInterval);
                        
                        // 结束充压
                        indPump.className = "status-indicator";
                        valPump.innerText = "待命 (满压)";
                        valPump.style.color = "var(--text-bright)";
                        document.getElementById('air-knife-nozzle').classList.remove('pump-vibrate');
                        
                        resolve();
                    }
                    updatePressureUI();
                }, 50); // 充压耗时约 1.5s
            });
        }

        function stageBlowing() {
            return new Promise(resolve => {
                // UI 更新: 电磁阀开启
                indValve.className = "status-indicator active";
                valValve.innerText = "瞬态开启 (ON)";
                valValve.style.color = "var(--alert-green)";

                // SVG 动画: 激活气流
                airflowGroup.classList.replace("airflow-inactive", "airflow-active");
                
                // SVG 动画: 剥离粉尘
                dustParticlesArray.forEach(p => p.classList.add("dust-blow-away"));

                // 模拟压力释放衰减
                clearInterval(pressureInterval);
                pressureInterval = setInterval(() => {
                    currentPressure -= 0.08;
                    if (currentPressure <= 0.05) currentPressure = 0.0;
                    updatePressureUI();
                }, 100);

                // 吹扫持续 0.8s
                setTimeout(() => {
                    clearInterval(pressureInterval);
                    
                    // UI 恢复: 电磁阀关闭
                    indValve.className = "status-indicator";
                    valValve.innerText = "关闭 (OFF)";
                    valValve.style.color = "var(--text-main)";
                    
                    // SVG 动画: 停止气流
                    airflowGroup.classList.replace("airflow-active", "airflow-inactive");
                    
                    resolve();
                }, 800); 
            });
        }

        function stageIdle() {
            return new Promise(resolve => {
                // 保持清洁状态展示 2 秒
                setTimeout(resolve, 2000);
            });
        }

        function stageReset() {
            return new Promise(resolve => {
                // 清除带有动画类的旧节点,重新生成粉尘,模拟再次污染
                generateDust();
                // 渐显效果
                dustGroup.style.opacity = 0;
                setTimeout(() => {
                    dustGroup.style.transition = "opacity 1.5s ease";
                    dustGroup.style.opacity = 1;
                }, 50);
                
                setTimeout(resolve, 1500);
            });
        }

        function updatePressureUI() {
            valPressure.innerText = currentPressure.toFixed(2);
            // 假设最大承受 0.6MPa,计算百分比
            const percent = Math.min(100, Math.max(0, (currentPressure / 0.6) * 100));
            barPressure.style.width = percent + "%";
            
            // 颜色预警
            if (currentPressure > 0.4) {
                barPressure.style.background = "var(--alert-green)";
                barPressure.style.boxShadow = "0 0 8px var(--alert-green)";
            } else {
                barPressure.style.background = "var(--accent-cyan)";
                barPressure.style.boxShadow = "0 0 8px var(--accent-cyan)";
            }
        }

        // 页面可见性处理 (离开标签页时暂停避免混乱)
        document.addEventListener("visibilitychange", () => {
            if (document.hidden) {
                clearTimeout(cycleTimeout);
                clearInterval(pressureInterval);
            } else {
                // 重新回来时直接重置并播放
                if(!isAnimating) {
                    currentPressure = 0;
                    updatePressureUI();
                    generateDust();
                    startAutoCycle();
                }
            }
        });

    </script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分