独立渲染引擎就绪引擎就绪
<!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分
等待动画代码生成...
