独立渲染引擎就绪引擎就绪
<!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>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Noto+Sans+SC:wght@400;500;700;900&display=swap');
:root {
--bg-base: #020617;
--bg-panel: rgba(15, 23, 42, 0.75);
--accent-cyan: #06b6d4;
--accent-cyan-glow: rgba(6, 182, 212, 0.6);
--accent-orange: #f59e0b;
--accent-orange-glow: rgba(245, 158, 11, 0.6);
--text-main: #f8fafc;
--text-muted: #94a3b8;
--metal-light: #cbd5e1;
--metal-dark: #475569;
--border-color: rgba(255, 255, 255, 0.15);
--panel-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.5);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
background-color: var(--bg-base);
color: var(--text-main);
font-family: 'Noto Sans SC', sans-serif;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
width: 100vw;
background-image:
linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
background-size: 40px 40px;
background-position: center center;
}
/* 顶部标题区 */
.header {
position: absolute;
top: 40px;
text-align: center;
z-index: 10;
}
.header h1 {
font-size: 2.2rem;
font-weight: 900;
letter-spacing: 2px;
background: linear-gradient(90deg, #fff, var(--accent-cyan));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 8px;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.8));
}
.header p {
font-size: 1rem;
color: var(--text-muted);
letter-spacing: 1px;
}
.ifr-badge {
display: inline-block;
margin-top: 10px;
padding: 4px 12px;
background: rgba(6, 182, 212, 0.15);
border: 1px solid var(--accent-cyan);
border-radius: 20px;
font-size: 0.85rem;
color: var(--accent-cyan);
font-weight: bold;
box-shadow: 0 0 10px var(--accent-cyan-glow);
}
/* 核心动画容器 */
.main-view {
width: 100%;
max-width: 1200px;
height: 50vh;
min-height: 400px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-top: -50px;
}
svg {
width: 100%;
height: 100%;
filter: drop-shadow(0 20px 40px rgba(0,0,0,0.7));
}
/* 底部仪表盘 */
.dashboard {
position: absolute;
bottom: 40px;
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
z-index: 10;
width: 100%;
max-width: 900px;
}
.metrics-container {
display: flex;
gap: 20px;
width: 100%;
justify-content: center;
}
.metric-card {
background: var(--bg-panel);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid var(--border-color);
border-radius: 16px;
padding: 20px 24px;
width: 260px;
box-shadow: var(--panel-shadow);
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.metric-card::before {
content: '';
position: absolute;
top: 0; left: 0; width: 100%; height: 3px;
background: var(--accent-cyan);
}
.metric-card.orange::before { background: var(--accent-orange); }
.metric-title {
font-size: 0.9rem;
color: var(--text-muted);
margin-bottom: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.metric-value {
font-family: 'JetBrains Mono', monospace;
font-size: 2.5rem;
font-weight: 700;
color: var(--text-main);
display: flex;
align-items: baseline;
gap: 8px;
}
.metric-unit {
font-size: 1.1rem;
color: var(--text-muted);
font-family: 'Noto Sans SC', sans-serif;
}
/* 交互控制器 */
.control-panel {
background: var(--bg-panel);
backdrop-filter: blur(12px);
border: 1px solid var(--border-color);
border-radius: 40px;
padding: 16px 40px;
display: flex;
align-items: center;
gap: 20px;
box-shadow: var(--panel-shadow);
width: 100%;
max-width: 600px;
}
.status-indicator {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.85rem;
font-weight: 700;
letter-spacing: 1px;
width: 80px;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #22c55e;
box-shadow: 0 0 10px #22c55e;
animation: pulse 1.5s infinite;
}
.status-indicator.manual .status-dot {
background: var(--accent-orange);
box-shadow: 0 0 10px var(--accent-orange-glow);
animation: none;
}
@keyframes pulse {
0% { transform: scale(0.95); opacity: 1; }
50% { transform: scale(1.2); opacity: 0.5; }
100% { transform: scale(0.95); opacity: 1; }
}
.slider-container {
flex-grow: 1;
display: flex;
align-items: center;
position: relative;
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 6px;
background: rgba(255, 255, 255, 0.1);
border-radius: 3px;
border: 1px solid rgba(0,0,0,0.5);
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 24px;
width: 24px;
border-radius: 50%;
background: var(--text-main);
border: 3px solid var(--bg-base);
box-shadow: 0 0 15px var(--text-main);
cursor: pointer;
margin-top: -10px;
transition: transform 0.1s;
}
input[type=range]::-webkit-slider-thumb:hover {
transform: scale(1.2);
}
input[type=range]:active::-webkit-slider-thumb {
transform: scale(1.1);
background: var(--accent-orange);
box-shadow: 0 0 20px var(--accent-orange);
}
.slider-label {
font-size: 0.85rem;
color: var(--text-muted);
white-space: nowrap;
}
/* SVG 内嵌样式 */
.svg-text {
font-family: 'Noto Sans SC', sans-serif;
fill: var(--text-main);
font-size: 14px;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.8));
}
.svg-text.bold { font-weight: 700; }
.svg-text.cyan { fill: var(--accent-cyan); }
.svg-text.orange { fill: var(--accent-orange); }
</style>
</head>
<body>
<div class="header">
<h1>自适应弹性喉口限流系统</h1>
<p>TRIZ 最终理想解 (IFR):利用系统内部流体压力,实现无源被动限流</p>
<div class="ifr-badge">核心机理:血管自调节效应</div>
</div>
<div class="main-view">
<svg viewBox="0 0 1000 500" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 钢壳渐变 -->
<linearGradient id="metalGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#94a3b8" />
<stop offset="50%" stop-color="#cbd5e1" />
<stop offset="100%" stop-color="#475569" />
</linearGradient>
<!-- 橡胶网格纹理 (钢丝骨架) -->
<pattern id="wireMesh" width="12" height="12" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<line x1="0" y1="0" x2="0" y2="12" stroke="rgba(255,255,255,0.15)" stroke-width="1.5" />
<line x1="0" y1="0" x2="12" y2="0" stroke="rgba(255,255,255,0.15)" stroke-width="1.5" />
</pattern>
<!-- 流体发光滤镜 -->
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="glow-orange" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<!-- 箭头标记 -->
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 10 5 L 0 9 z" fill="var(--text-muted)" />
</marker>
<marker id="arrow-cyan" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 10 5 L 0 9 z" fill="var(--accent-cyan)" />
</marker>
</defs>
<!-- 背景辅助线 (蓝图风格) -->
<g stroke="rgba(255,255,255,0.05)" stroke-width="1">
<line x1="0" y1="250" x2="1000" y2="250" stroke-dasharray="10 10" />
<line x1="500" y1="0" x2="500" y2="500" stroke-dasharray="10 10" />
</g>
<!-- 流体主区域 (动态路径填充) -->
<path id="fluidArea" fill="rgba(6, 182, 212, 0.15)" stroke="rgba(6, 182, 212, 0.5)" stroke-width="2" filter="url(#glow)"/>
<!-- 动态流线 (由 JS 生成并更新) -->
<g id="streamlinesGroup"></g>
<!-- ================= 上半部分结构 ================= -->
<!-- 外部不锈钢壳 (上) -->
<path d="M 50 10 L 950 10 L 950 50 L 50 50 Z" fill="url(#metalGrad)" />
<!-- 限位环 (上) -->
<rect x="290" y="50" width="10" height="80" fill="url(#metalGrad)" />
<rect x="700" y="50" width="10" height="80" fill="url(#metalGrad)" />
<!-- 弹性橡胶喉口 (上) - 动态路径 -->
<path id="rubberTop" fill="#b45309" />
<path id="rubberTopWire" fill="url(#wireMesh)" />
<path id="rubberTopEdge" fill="none" stroke="#f59e0b" stroke-width="3" filter="url(#glow-orange)" />
<!-- ================= 下半部分结构 ================= -->
<!-- 外部不锈钢壳 (下) -->
<path d="M 50 490 L 950 490 L 950 450 L 50 450 Z" fill="url(#metalGrad)" />
<!-- 限位环 (下) -->
<rect x="290" y="370" width="10" height="80" fill="url(#metalGrad)" />
<rect x="700" y="370" width="10" height="80" fill="url(#metalGrad)" />
<!-- 弹性橡胶喉口 (下) - 动态路径 -->
<path id="rubberBottom" fill="#b45309" />
<path id="rubberBottomWire" fill="url(#wireMesh)" />
<path id="rubberBottomEdge" fill="none" stroke="#f59e0b" stroke-width="3" filter="url(#glow-orange)" />
<!-- ================= 标注与受力指示 ================= -->
<!-- IFR 受力箭头组 (随压力显现) -->
<g id="forceArrowsGroup" stroke="#ef4444" stroke-width="2" opacity="0">
<!-- 上方受力 -->
<path d="M 470 200 L 470 170" marker-end="url(#arrow)" />
<path d="M 500 210 L 500 180" marker-end="url(#arrow)" />
<path d="M 530 200 L 530 170" marker-end="url(#arrow)" />
<!-- 下方受力 -->
<path d="M 470 300 L 470 330" marker-end="url(#arrow)" />
<path d="M 500 290 L 500 320" marker-end="url(#arrow)" />
<path d="M 530 300 L 530 330" marker-end="url(#arrow)" />
</g>
<!-- 静态标注线和文字 -->
<g class="annotations">
<!-- 进料端 -->
<text x="80" y="220" class="svg-text bold cyan">进料端 (高压)</text>
<text x="80" y="240" class="svg-text">流体流速 <tspan id="lblVelocityIn">v1</tspan></text>
<path d="M 70 250 L 150 250" stroke="var(--accent-cyan)" stroke-width="2" marker-end="url(#arrow-cyan)" />
<!-- 出料端 -->
<text x="820" y="220" class="svg-text bold cyan">出料端 (稳流)</text>
<text x="820" y="240" class="svg-text">输出流量保持稳定</text>
<path d="M 810 250 L 890 250" stroke="var(--accent-cyan)" stroke-width="2" marker-end="url(#arrow-cyan)" />
<!-- 喉口标注 -->
<path d="M 500 110 L 500 40 L 550 40" stroke="var(--text-muted)" stroke-width="1.5" fill="none" />
<text x="560" y="45" class="svg-text orange bold">高强度聚氨酯弹性体 (内衬钢丝)</text>
<path d="M 290 90 L 250 90" stroke="var(--text-muted)" stroke-width="1.5" fill="none" />
<text x="190" y="95" class="svg-text">不锈钢限位环</text>
<path d="M 750 30 L 800 30" stroke="var(--text-muted)" stroke-width="1.5" fill="none" />
<text x="810" y="35" class="svg-text">坚固外壳</text>
<!-- 动态受力文本 -->
<text id="txtForce" x="410" y="255" class="svg-text bold" fill="#ef4444" opacity="0">系统压力直接挤压形变</text>
</g>
<!-- 动态孔径尺寸指示线 -->
<g id="dimensionGroup">
<line x1="610" y1="130" x2="630" y2="130" stroke="var(--text-main)" stroke-width="1" id="dimTopLine" />
<line x1="610" y1="370" x2="630" y2="370" stroke="var(--text-main)" stroke-width="1" id="dimBottomLine" />
<line x1="620" y1="130" x2="620" y2="370" stroke="var(--text-main)" stroke-width="1.5" id="dimVerLine" marker-start="url(#arrow)" marker-end="url(#arrow)" />
<rect x="635" y="240" width="60" height="20" fill="var(--bg-base)" rx="4" />
<text x="665" y="255" text-anchor="middle" class="svg-text bold" id="lblDiameter">200 mm</text>
</g>
</svg>
</div>
<div class="dashboard">
<div class="metrics-container">
<!-- 压力表 -->
<div class="metric-card">
<div class="metric-title">系统压力 (P) <span>输入端</span></div>
<div class="metric-value">
<span id="valPressure">0.00</span>
<span class="metric-unit">MPa</span>
</div>
</div>
<!-- 孔径表 -->
<div class="metric-card orange">
<div class="metric-title">自适应喉口孔径 (D) <span>节流元件</span></div>
<div class="metric-value" style="color: var(--accent-orange);">
<span id="valDiameter">200</span>
<span class="metric-unit">mm</span>
</div>
</div>
<!-- 流量表 -->
<div class="metric-card">
<div class="metric-title">输出流量 (Q) <span>被动稳流</span></div>
<div class="metric-value">
<span id="valFlow">0.0</span>
<span class="metric-unit">m³/h</span>
</div>
</div>
</div>
<div class="control-panel">
<div class="status-indicator" id="statusIndicator">
<div class="status-dot"></div>
<span id="statusText">AUTO</span>
</div>
<div class="slider-label">低压</div>
<div class="slider-container">
<input type="range" id="pressureSlider" min="0" max="1" step="0.001" value="0">
</div>
<div class="slider-label">高压</div>
</div>
</div>
<script>
// DOM 元素引用
const paths = {
rubberTop: document.getElementById('rubberTop'),
rubberTopWire: document.getElementById('rubberTopWire'),
rubberTopEdge: document.getElementById('rubberTopEdge'),
rubberBottom: document.getElementById('rubberBottom'),
rubberBottomWire: document.getElementById('rubberBottomWire'),
rubberBottomEdge: document.getElementById('rubberBottomEdge'),
fluidArea: document.getElementById('fluidArea'),
forceArrowsGroup: document.getElementById('forceArrowsGroup'),
txtForce: document.getElementById('txtForce'),
dimTopLine: document.getElementById('dimTopLine'),
dimBottomLine: document.getElementById('dimBottomLine'),
dimVerLine: document.getElementById('dimVerLine'),
lblDiameter: document.getElementById('lblDiameter'),
streamlinesGroup: document.getElementById('streamlinesGroup')
};
const metrics = {
pressure: document.getElementById('valPressure'),
diameter: document.getElementById('valDiameter'),
flow: document.getElementById('valFlow'),
velocity: document.getElementById('lblVelocityIn')
};
const slider = document.getElementById('pressureSlider');
const statusIndicator = document.getElementById('statusIndicator');
const statusText = document.getElementById('statusText');
// 系统状态与参数
let state = {
autoPlay: true,
time: 0,
targetPressure: 0,
currentPressure: 0, // 0 到 1 之间
streamOffsets: [0, 0, 0, 0, 0] // 5条流线的偏移量
};
// 初始化流线 SVG 元素
const streamLines = [];
for (let i = 0; i < 5; i++) {
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("fill", "none");
path.setAttribute("stroke", "rgba(255,255,255,0.7)");
path.setAttribute("stroke-width", i === 2 ? "2" : "1"); // 中间线粗一点
path.setAttribute("stroke-dasharray", "15 30");
paths.streamlinesGroup.appendChild(path);
streamLines.push(path);
}
// 核心渲染函数
function render(dt) {
// 1. 状态更新逻辑 (平滑插值)
if (state.autoPlay) {
// 自动播放模式:压力随时间呈正弦波动,模拟系统压力波动
state.time += dt * 0.001;
// 映射到 0.05 ~ 0.95 避免贴边
state.targetPressure = 0.5 + 0.45 * Math.sin(state.time);
slider.value = state.targetPressure;
} else {
state.targetPressure = parseFloat(slider.value);
}
// 阻尼跟随,使形变平滑
state.currentPressure += (state.targetPressure - state.currentPressure) * 0.08;
const P = state.currentPressure; // 0.0 ~ 1.0
// 2. 物理与几何参数计算
// 孔径最小120mm,最大200mm。对应的像素形变量
// 原始管道半高: 120px (对应直径200mm)
// 单侧最大压缩量: 48px (对应直径减少80mm) -> 方便视觉表现放大至 60px
const maxDisplacement = 65;
const disp = P * maxDisplacement;
// 动态直径数据计算
const realDiameter = 200 - P * 80;
// 流量计算模型:展示无源限流的“理想终极解”特性
// 基础流量模型: Q = k * P * A. A 与 D^2 成正比。
// 我们构造一个平滑的渐近曲线,表现出压力增大但流量稳定(甚至在极高压时略微下降,保护后级)
// 公式:Q = 450 * (1 - exp(-4 * P)) * (1 - 0.1 * P)
let flowRate = 480 * (1 - Math.exp(-4.5 * P)) * (1 - 0.05 * P);
if (P < 0.01) flowRate = 0;
// 3. 构建 SVG 动态路径
const topY = 130;
const bottomY = 370;
const ctrlY_top = topY + disp;
const ctrlY_bottom = bottomY - disp;
// 上橡胶体路径
const topRubberPath = `M 150 50 L 850 50 L 850 ${topY} L 700 ${topY} C 620 ${topY}, 600 ${ctrlY_top}, 500 ${ctrlY_top} C 400 ${ctrlY_top}, 380 ${topY}, 300 ${topY} L 150 ${topY} Z`;
// 上橡胶边缘路径
const topEdgePath = `M 150 ${topY} L 300 ${topY} C 380 ${topY}, 400 ${ctrlY_top}, 500 ${ctrlY_top} C 600 ${ctrlY_top}, 620 ${topY}, 700 ${topY} L 850 ${topY}`;
// 下橡胶体路径
const bottomRubberPath = `M 150 450 L 850 450 L 850 ${bottomY} L 700 ${bottomY} C 620 ${bottomY}, 600 ${ctrlY_bottom}, 500 ${ctrlY_bottom} C 400 ${ctrlY_bottom}, 380 ${bottomY}, 300 ${bottomY} L 150 ${bottomY} Z`;
// 下橡胶边缘路径
const bottomEdgePath = `M 150 ${bottomY} L 300 ${bottomY} C 380 ${bottomY}, 400 ${ctrlY_bottom}, 500 ${ctrlY_bottom} C 600 ${ctrlY_bottom}, 620 ${bottomY}, 700 ${bottomY} L 850 ${bottomY}`;
// 流体区域路径
const fluidPath = `M 50 ${topY} L 300 ${topY} C 380 ${topY}, 400 ${ctrlY_top}, 500 ${ctrlY_top} C 600 ${ctrlY_top}, 620 ${topY}, 700 ${topY} L 950 ${topY} L 950 ${bottomY} L 700 ${bottomY} C 620 ${bottomY}, 600 ${ctrlY_bottom}, 500 ${ctrlY_bottom} C 400 ${ctrlY_bottom}, 380 ${bottomY}, 300 ${bottomY} L 50 ${bottomY} Z`;
// 应用路径
paths.rubberTop.setAttribute('d', topRubberPath);
paths.rubberTopWire.setAttribute('d', topRubberPath);
paths.rubberTopEdge.setAttribute('d', topEdgePath);
paths.rubberBottom.setAttribute('d', bottomRubberPath);
paths.rubberBottomWire.setAttribute('d', bottomRubberPath);
paths.rubberBottomEdge.setAttribute('d', bottomEdgePath);
paths.fluidArea.setAttribute('d', fluidPath);
// 4. 更新流线和动画
// 流速随着压力略微增加(文丘里效应),但整体流量受到截面积减小的限制
const baseSpeed = 2 + P * 4;
for (let i = 0; i < 5; i++) {
const k = i - 2; // -2, -1, 0, 1, 2
const startY = 250 + k * 45;
// 中心处的 Y 坐标随形变挤压
const midY = 250 + k * (45 - disp/2.5);
const streamPath = `M 50 ${startY} L 300 ${startY} C 380 ${startY}, 400 ${midY}, 500 ${midY} C 600 ${midY}, 620 ${startY}, 700 ${startY} L 950 ${startY}`;
streamLines[i].setAttribute('d', streamPath);
// 更新虚线偏移量产生流动动画
state.streamOffsets[i] -= baseSpeed * (1 + 0.1 * Math.abs(k)); // 中间稍微慢一点,边缘快一点造成错落感
streamLines[i].setAttribute('stroke-dashoffset', state.streamOffsets[i]);
// 透明度随压力增强
streamLines[i].setAttribute('opacity', 0.4 + 0.6 * P);
}
// 5. 动态标注更新
// 孔径标注线位置更新
paths.dimTopLine.setAttribute('y1', ctrlY_top);
paths.dimTopLine.setAttribute('y2', ctrlY_top);
paths.dimBottomLine.setAttribute('y1', ctrlY_bottom);
paths.dimBottomLine.setAttribute('y2', ctrlY_bottom);
paths.dimVerLine.setAttribute('y1', ctrlY_top + 5);
paths.dimVerLine.setAttribute('y2', ctrlY_bottom - 5);
// 受力指示器 (IFR 特征):压力越大,向内的挤压箭头越明显
paths.forceArrowsGroup.setAttribute('opacity', P * 1.5); // 加强显示
paths.txtForce.setAttribute('opacity', P > 0.3 ? (P - 0.3) * 2 : 0);
// 6. 仪表盘数据更新
metrics.pressure.innerText = P.toFixed(2);
metrics.diameter.innerText = realDiameter.toFixed(0);
metrics.flow.innerText = flowRate.toFixed(1);
// 进料速度指示器
metrics.velocity.innerText = (1 + P*3).toFixed(1) + " m/s";
// 颜色动态变化:压力较高时仪表盘数值变红/橙警告
if(P > 0.75) {
metrics.pressure.style.color = '#ef4444';
} else if (P > 0.5) {
metrics.pressure.style.color = 'var(--accent-orange)';
} else {
metrics.pressure.style.color = 'var(--text-main)';
}
}
// 主循环
let lastTime = 0;
function animate(timestamp) {
const dt = timestamp - lastTime;
lastTime = timestamp;
if(dt < 100) { // 防止切后台回来瞬间的时间跳变
render(dt);
}
requestAnimationFrame(animate);
}
// 交互事件监听
slider.addEventListener('mousedown', () => {
state.autoPlay = false;
statusIndicator.classList.add('manual');
statusText.innerText = 'MANUAL';
});
slider.addEventListener('touchstart', () => {
state.autoPlay = false;
statusIndicator.classList.add('manual');
statusText.innerText = 'MANUAL';
}, {passive: true});
// 页面加载完成后自动启动
window.addEventListener('DOMContentLoaded', () => {
requestAnimationFrame(animate);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>柔性自适应节流阀 - TRIZ 最终理想解</title>
<style>
:root {
--bg-color: #07090f;
--panel-bg: #111625;
--border-color: #2a3556;
--text-main: #e2e8f0;
--text-muted: #64748b;
--accent-cyan: #00f0ff;
--accent-blue: #3b82f6;
--accent-orange: #ff5500;
--accent-amber: #f59e0b;
--rubber-color: #1e293b;
--pipe-color: #334155;
--font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', 'Courier New', monospace;
--font-sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", 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);
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.header {
padding: 1.5rem 2rem;
display: flex;
justify-content: space-between;
align-items: flex-end;
border-bottom: 1px solid var(--border-color);
background: linear-gradient(to bottom, #111625, #07090f);
z-index: 10;
}
.title-group h1 {
font-size: 1.5rem;
font-weight: 600;
letter-spacing: 1px;
margin-bottom: 0.5rem;
display: flex;
align-items: center;
gap: 0.75rem;
}
.title-group h1::before {
content: '';
display: inline-block;
width: 8px;
height: 24px;
background-color: var(--accent-cyan);
box-shadow: 0 0 10px var(--accent-cyan);
}
.subtitle {
font-size: 0.85rem;
color: var(--text-muted);
max-width: 600px;
line-height: 1.5;
}
.ifr-badge {
background-color: rgba(0, 240, 255, 0.1);
border: 1px solid var(--accent-cyan);
color: var(--accent-cyan);
padding: 0.5rem 1rem;
border-radius: 4px;
font-size: 0.8rem;
font-family: var(--font-mono);
box-shadow: 0 0 15px rgba(0, 240, 255, 0.15) inset;
}
.main-container {
flex: 1;
display: flex;
position: relative;
justify-content: center;
align-items: center;
background-image:
linear-gradient(rgba(42, 53, 86, 0.3) 1px, transparent 1px),
linear-gradient(90deg, rgba(42, 53, 86, 0.3) 1px, transparent 1px);
background-size: 40px 40px;
overflow: hidden;
}
.svg-wrapper {
width: 100%;
height: 100%;
max-width: 1200px;
max-height: 700px;
padding: 2rem;
position: relative;
z-index: 2;
}
svg {
width: 100%;
height: 100%;
filter: drop-shadow(0 10px 30px rgba(0,0,0,0.5));
}
/* Overlay UI */
.hud-panel {
position: absolute;
background: rgba(17, 22, 37, 0.85);
backdrop-filter: blur(10px);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1rem;
z-index: 5;
display: flex;
flex-direction: column;
gap: 1rem;
}
.hud-left {
bottom: 2rem;
left: 2rem;
min-width: 280px;
}
.hud-right {
top: 2rem;
right: 2rem;
min-width: 220px;
}
.data-row {
display: flex;
justify-content: space-between;
align-items: center;
font-family: var(--font-mono);
font-size: 0.9rem;
}
.data-label {
color: var(--text-muted);
font-family: var(--font-sans);
font-size: 0.8rem;
}
.data-value {
color: var(--accent-cyan);
font-weight: bold;
font-size: 1.1rem;
text-shadow: 0 0 8px rgba(0, 240, 255, 0.5);
}
.data-value.warning {
color: var(--accent-orange);
text-shadow: 0 0 8px rgba(255, 85, 0, 0.5);
}
/* Custom Slider */
.control-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-top: 0.5rem;
padding-top: 1rem;
border-top: 1px dashed var(--border-color);
}
.slider-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.mode-indicator {
font-size: 0.7rem;
padding: 2px 6px;
border-radius: 3px;
background: var(--accent-blue);
color: white;
transition: all 0.3s ease;
}
.mode-indicator.manual {
background: var(--accent-orange);
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 6px;
cursor: pointer;
background: var(--pipe-color);
border-radius: 3px;
}
input[type=range]::-webkit-slider-thumb {
height: 16px;
width: 16px;
border-radius: 50%;
background: var(--accent-cyan);
cursor: pointer;
-webkit-appearance: none;
margin-top: -5px;
box-shadow: 0 0 10px var(--accent-cyan);
transition: transform 0.1s, background 0.3s;
}
input[type=range]:active::-webkit-slider-thumb {
transform: scale(1.2);
background: var(--accent-orange);
box-shadow: 0 0 10px var(--accent-orange);
}
/* SVG Inner Styling via CSS */
.flow-line {
fill: none;
stroke-width: 2.5;
stroke-linecap: round;
}
.skeleton-wire {
fill: none;
stroke: rgba(255, 255, 255, 0.15);
stroke-width: 1.5;
stroke-dasharray: 4 4;
}
.dim-text {
font-family: var(--font-mono);
font-size: 14px;
fill: var(--accent-cyan);
text-anchor: middle;
}
.annotation-text {
font-family: var(--font-sans);
font-size: 12px;
fill: var(--text-muted);
}
</style>
</head>
<body>
<header class="header">
<div class="title-group">
<h1>柔性自适应节流阀原理演示</h1>
<p class="subtitle">
最终理想解 (IFR):无需外部传感器与能源,利用管道内流体压力上升自身的物理力,压缩高强度弹性喉口,实现自动减缩孔径进行无源限流。压力降低后依靠骨架回弹复位。
</p>
</div>
<div class="ifr-badge">TRIZ / 资源利用 / 最终理想解</div>
</header>
<main class="main-container">
<!-- Left HUD: Interaction & System Status -->
<div class="hud-panel hud-left">
<div class="data-row">
<span class="data-label">系统运行状态</span>
<span class="data-value" id="status-text">自适应调节中</span>
</div>
<div class="data-row">
<span class="data-label">喉口孔径 (D)</span>
<span class="data-value" id="aperture-readout">200 mm</span>
</div>
<div class="data-row">
<span class="data-label">上游瞬时压力 (P)</span>
<span class="data-value" id="pressure-readout">0.00 MPa</span>
</div>
<div class="control-group">
<div class="slider-header">
<span class="data-label">压力模拟控制</span>
<span class="mode-indicator" id="mode-badge">自动播放</span>
</div>
<input type="range" id="pressure-slider" min="0" max="1" step="0.001" value="0">
<span class="data-label" style="text-align: right; font-size: 0.7rem;">*滑动以切换手动模式</span>
</div>
</div>
<!-- Right HUD: Structural Info -->
<div class="hud-panel hud-right">
<div class="data-row" style="border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem; margin-bottom: 0.5rem;">
<span class="data-label" style="color:var(--text-main); font-weight:bold;">关键构件说明</span>
</div>
<div class="data-row">
<span class="data-label">外壳材质</span>
<span class="data-value" style="font-size:0.9rem; color:var(--text-muted)">高强度不锈钢</span>
</div>
<div class="data-row">
<span class="data-label">节流元件</span>
<span class="data-value" style="font-size:0.9rem; color:var(--accent-amber)">聚氨酯/橡胶</span>
</div>
<div class="data-row">
<span class="data-label">内部支撑</span>
<span class="data-value" style="font-size:0.9rem; color:var(--text-muted)">预应力钢丝骨架</span>
</div>
<div class="data-row">
<span class="data-label">压力适用范围</span>
<span class="data-value" style="font-size:0.9rem; color:var(--text-muted)">0 - 1.0 MPa</span>
</div>
</div>
<div class="svg-wrapper">
<svg id="core-svg" viewBox="0 0 1000 500" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- Flow Gradients -->
<linearGradient id="flow-grad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop id="grad-stop-1" offset="0%" stop-color="#00f0ff" />
<stop id="grad-stop-2" offset="50%" stop-color="#3b82f6" />
<stop id="grad-stop-3" offset="100%" stop-color="#00f0ff" />
</linearGradient>
<!-- Rubber Gradients -->
<linearGradient id="rubber-grad-top" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#111827" />
<stop offset="80%" stop-color="#1e293b" />
<stop id="rubber-inner-top" offset="100%" stop-color="#334155" />
</linearGradient>
<linearGradient id="rubber-grad-bottom" x1="0%" y1="100%" x2="0%" y2="0%">
<stop offset="0%" stop-color="#111827" />
<stop offset="80%" stop-color="#1e293b" />
<stop id="rubber-inner-bottom" offset="100%" stop-color="#334155" />
</linearGradient>
<!-- Metallic Pipe Gradient -->
<linearGradient id="pipe-grad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#475569" />
<stop offset="50%" stop-color="#94a3b8" />
<stop offset="100%" stop-color="#334155" />
</linearGradient>
<filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="5" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="glow-orange" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<!-- Arrow Marker -->
<marker id="arrowhead" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto">
<polygon points="0 0, 6 3, 0 6" fill="var(--accent-cyan)" />
</marker>
<marker id="arrowhead-reverse" markerWidth="6" markerHeight="6" refX="3" refY="3" orient="auto">
<polygon points="6 0, 0 3, 6 6" fill="var(--accent-cyan)" />
</marker>
</defs>
<!-- Background Elements -->
<!-- Flow Direction Indicator -->
<path d="M 50,450 L 950,450" stroke="rgba(255,255,255,0.05)" stroke-width="1" />
<path d="M 850,450 L 950,450" stroke="rgba(255,255,255,0.1)" stroke-width="2" marker-end="url(#arrowhead)" />
<text x="900" y="440" class="annotation-text" text-anchor="middle">流体方向</text>
<!-- Outer Steel Pipe Casing -->
<g id="pipe-casing">
<!-- Top Wall -->
<rect x="100" y="50" width="800" height="20" fill="url(#pipe-grad)" stroke="#1e293b" />
<rect x="80" y="30" width="40" height="60" fill="#64748b" rx="2" /> <!-- Flange Left -->
<rect x="880" y="30" width="40" height="60" fill="#64748b" rx="2" /> <!-- Flange Right -->
<!-- Bottom Wall -->
<rect x="100" y="430" width="800" height="20" fill="url(#pipe-grad)" stroke="#1e293b" />
<rect x="80" y="410" width="40" height="60" fill="#64748b" rx="2" /> <!-- Flange Left -->
<rect x="880" y="410" width="40" height="60" fill="#64748b" rx="2" /> <!-- Flange Right -->
</g>
<!-- Dynamic Fluid Flow Lines -->
<g id="fluid-lines" filter="url(#glow-cyan)"></g>
<!-- Rubber Elastic Throat (Dynamic Paths) -->
<g id="throat-system">
<!-- Top Rubber -->
<path id="rubber-top" d="" fill="url(#rubber-grad-top)" stroke="#0f172a" stroke-width="2" />
<g id="skeleton-top"></g> <!-- Populated by JS -->
<!-- Bottom Rubber -->
<path id="rubber-bottom" d="" fill="url(#rubber-grad-bottom)" stroke="#0f172a" stroke-width="2" />
<g id="skeleton-bottom"></g> <!-- Populated by JS -->
</g>
<!-- Dimension Indicators -->
<g id="dimensions" opacity="0.8">
<!-- Center vertical line indicating narrowest point -->
<line x1="500" y1="70" x2="500" y2="430" stroke="rgba(0, 240, 255, 0.2)" stroke-width="1" stroke-dasharray="5 5" />
<!-- Dynamic Dimension Arrows -->
<line id="dim-line-top" x1="500" y1="250" x2="500" y2="150" stroke="var(--accent-cyan)" stroke-width="1.5" marker-end="url(#arrowhead)" />
<line id="dim-line-bottom" x1="500" y1="250" x2="500" y2="350" stroke="var(--accent-cyan)" stroke-width="1.5" marker-end="url(#arrowhead)" />
<!-- Fixed bounding lines for measurement -->
<line x1="520" y1="150" x2="550" y2="150" id="dim-bound-top" stroke="rgba(255,255,255,0.3)" stroke-width="1" />
<line x1="520" y1="350" x2="550" y2="350" id="dim-bound-bottom" stroke="rgba(255,255,255,0.3)" stroke-width="1" />
<!-- Dynamic Text -->
<text id="dim-text-val" x="535" y="255" class="dim-text">200mm</text>
</g>
<!-- Labels -->
<text x="350" y="100" class="annotation-text" fill="rgba(255,255,255,0.5)">钢丝骨架内衬</text>
<line x1="350" y1="110" x2="350" y2="130" stroke="rgba(255,255,255,0.3)" stroke-width="1" />
<text x="650" y="400" class="annotation-text" fill="rgba(255,255,255,0.5)">高弹性聚氨酯喉体</text>
<line x1="650" y1="385" x2="650" y2="365" stroke="rgba(255,255,255,0.3)" stroke-width="1" />
<!-- Upstream Pressure Visualization Area (Overlay) -->
<rect x="120" y="70" width="100" height="360" fill="url(#glow-orange)" id="pressure-zone-glow" opacity="0" style="mix-blend-mode: screen;" />
<text x="170" y="255" class="annotation-text" id="pressure-zone-text" text-anchor="middle" fill="#ff5500" opacity="0" font-weight="bold">高压积聚区</text>
</svg>
</div>
</main>
<script>
document.addEventListener('DOMContentLoaded', () => {
// Configuration & Constants
const CONFIG = {
svgWidth: 1000,
svgHeight: 500,
centerY: 250,
pipeStartX: 100,
pipeEndX: 900,
throatCenterX: 500,
rubberBaseTop: 70, // Inner edge of top pipe casing
rubberBaseBottom: 430, // Inner edge of bottom pipe casing
uncompressedGap: 280, // Visual pixels mapped to 200mm
compressedGap: 168, // Visual pixels mapped to 120mm (280 * 120/200)
maxPressure: 1.0, // MPa
numFlowLines: 11,
numSkeletonLines: 5
};
// Max displacement per rubber side
const maxDisplacement = (CONFIG.uncompressedGap - CONFIG.compressedGap) / 2;
// DOM Elements
const rubberTop = document.getElementById('rubber-top');
const rubberBottom = document.getElementById('rubber-bottom');
const skeletonTop = document.getElementById('skeleton-top');
const skeletonBottom = document.getElementById('skeleton-bottom');
const fluidLinesGroup = document.getElementById('fluid-lines');
const slider = document.getElementById('pressure-slider');
const modeBadge = document.getElementById('mode-badge');
const apertureReadout = document.getElementById('aperture-readout');
const pressureReadout = document.getElementById('pressure-readout');
const gradStop1 = document.getElementById('grad-stop-1');
const gradStop2 = document.getElementById('grad-stop-2');
const rubberInnerTop = document.getElementById('rubber-inner-top');
const rubberInnerBottom = document.getElementById('rubber-inner-bottom');
const dimLineTop = document.getElementById('dim-line-top');
const dimLineBottom = document.getElementById('dim-line-bottom');
const dimBoundTop = document.getElementById('dim-bound-top');
const dimBoundBottom = document.getElementById('dim-bound-bottom');
const dimTextVal = document.getElementById('dim-text-val');
const pressureZoneGlow = document.getElementById('pressure-zone-glow');
const pressureZoneText = document.getElementById('pressure-zone-text');
const fluidLinesGroupFilter = fluidLinesGroup;
// State
let currentPressure = 0; // 0.0 to 1.0
let isAutoMode = true;
let autoPlayTime = 0;
let lastFrameTime = performance.now();
let flowOffsets = Array(CONFIG.numFlowLines).fill(0);
let animationFrameId;
let interactionTimeout;
// Utility: Color Interpolation
function lerpColor(c1, c2, t) {
const hex2rgb = hex => [parseInt(hex.slice(1,3),16), parseInt(hex.slice(3,5),16), parseInt(hex.slice(5,7),16)];
const [r1, g1, b1] = hex2rgb(c1);
const [r2, g2, b2] = hex2rgb(c2);
const r = Math.round(r1 + (r2 - r1) * t);
const g = Math.round(g1 + (g2 - g1) * t);
const b = Math.round(b1 + (b2 - b1) * t);
return `rgb(${r},${g},${b})`;
}
// Initialization: Create SVG Sub-elements
function initSVG() {
// Initialize Fluid Lines
for (let i = 0; i < CONFIG.numFlowLines; i++) {
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('class', 'flow-line');
path.setAttribute('stroke', 'url(#flow-grad)');
// Dash array creates the dotted flow effect
path.setAttribute('stroke-dasharray', `${10 + Math.random()*20} ${15 + Math.random()*20}`);
path.setAttribute('id', `flow-line-${i}`);
fluidLinesGroup.appendChild(path);
}
// Initialize Skeleton Wires
for (let i = 0; i < CONFIG.numSkeletonLines; i++) {
const pTop = document.createElementNS('http://www.w3.org/2000/svg', 'path');
pTop.setAttribute('class', 'skeleton-wire');
pTop.setAttribute('id', `skeleton-top-${i}`);
skeletonTop.appendChild(pTop);
const pBottom = document.createElementNS('http://www.w3.org/2000/svg', 'path');
pBottom.setAttribute('class', 'skeleton-wire');
pBottom.setAttribute('id', `skeleton-bottom-${i}`);
skeletonBottom.appendChild(pBottom);
}
}
// Core Render Function
function updateVisualization(pressure, deltaTime) {
// Constrain pressure
pressure = Math.max(0, Math.min(1, pressure));
// 1. Calculate Physical Displacement
const displacement = maxDisplacement * pressure;
// Actual mm value mapping
const currentApertureMM = 200 - (80 * pressure);
// Top inner boundary Y
const uncompressedTopInnerY = CONFIG.centerY - (CONFIG.uncompressedGap / 2);
const currentTopInnerY = uncompressedTopInnerY + displacement;
// Bottom inner boundary Y
const uncompressedBottomInnerY = CONFIG.centerY + (CONFIG.uncompressedGap / 2);
const currentBottomInnerY = uncompressedBottomInnerY - displacement;
// Bezier Curve Control Point Offsets
const cX1 = 300, cX2 = 400; // Left side of throat curve
const cX3 = 600, cX4 = 700; // Right side of throat curve
// 2. Update Rubber Paths
// Top Rubber Profile
const topD = `M ${CONFIG.pipeStartX},${CONFIG.rubberBaseTop}
L ${CONFIG.pipeEndX},${CONFIG.rubberBaseTop}
L ${CONFIG.pipeEndX},${uncompressedTopInnerY}
C ${cX4},${uncompressedTopInnerY} ${cX3},${currentTopInnerY} ${CONFIG.throatCenterX},${currentTopInnerY}
C ${cX2},${currentTopInnerY} ${cX1},${uncompressedTopInnerY} ${CONFIG.pipeStartX},${uncompressedTopInnerY} Z`;
rubberTop.setAttribute('d', topD);
// Bottom Rubber Profile
const bottomD = `M ${CONFIG.pipeStartX},${CONFIG.rubberBaseBottom}
L ${CONFIG.pipeEndX},${CONFIG.rubberBaseBottom}
L ${CONFIG.pipeEndX},${uncompressedBottomInnerY}
C ${cX4},${uncompressedBottomInnerY} ${cX3},${currentBottomInnerY} ${CONFIG.throatCenterX},${currentBottomInnerY}
C ${cX2},${currentBottomInnerY} ${cX1},${uncompressedBottomInnerY} ${CONFIG.pipeStartX},${uncompressedBottomInnerY} Z`;
rubberBottom.setAttribute('d', bottomD);
// 3. Update Skeleton Wires (Interpolated between base and inner surface)
for(let i=0; i<CONFIG.numSkeletonLines; i++) {
const ratio = (i + 1) / (CONFIG.numSkeletonLines + 1); // e.g., 0.2, 0.4, 0.6...
// Top skeleton line
const wireYTopEnd = CONFIG.rubberBaseTop + (uncompressedTopInnerY - CONFIG.rubberBaseTop) * ratio;
const wireYTopMid = CONFIG.rubberBaseTop + (currentTopInnerY - CONFIG.rubberBaseTop) * ratio;
const topWireD = `M ${CONFIG.pipeStartX},${wireYTopEnd}
C ${cX1},${wireYTopEnd} ${cX2},${wireYTopMid} ${CONFIG.throatCenterX},${wireYTopMid}
C ${cX3},${wireYTopMid} ${cX4},${wireYTopEnd} ${CONFIG.pipeEndX},${wireYTopEnd}`;
document.getElementById(`skeleton-top-${i}`).setAttribute('d', topWireD);
// Bottom skeleton line
const wireYBottomEnd = CONFIG.rubberBaseBottom - (CONFIG.rubberBaseBottom - uncompressedBottomInnerY) * ratio;
const wireYBottomMid = CONFIG.rubberBaseBottom - (CONFIG.rubberBaseBottom - currentBottomInnerY) * ratio;
const bottomWireD = `M ${CONFIG.pipeStartX},${wireYBottomEnd}
C ${cX1},${wireYBottomEnd} ${cX2},${wireYBottomMid} ${CONFIG.throatCenterX},${wireYBottomMid}
C ${cX3},${wireYBottomMid} ${cX4},${wireYBottomEnd} ${CONFIG.pipeEndX},${wireYBottomEnd}`;
document.getElementById(`skeleton-bottom-${i}`).setAttribute('d', bottomWireD);
}
// 4. Update Fluid Lines
// Dynamic flow speed calculation based on venturi effect and pressure
// Flow is generally faster under high pressure, especially in the narrow throat.
// We simulate this by moving the dashoffset.
const baseSpeed = 1.0;
const pressureMultiplier = 4.0;
const currentSpeed = baseSpeed + (pressure * pressureMultiplier);
const currentApertureVisual = currentBottomInnerY - currentTopInnerY;
for(let i=0; i<CONFIG.numFlowLines; i++) {
// Calculate relative vertical position (-1 to 1)
const relY = (i / (CONFIG.numFlowLines - 1)) * 2 - 1;
// Apply a power curve to bunch lines closer to the center slightly
const distProfile = Math.sign(relY) * Math.pow(Math.abs(relY), 0.8);
const startY = CONFIG.centerY + distProfile * (CONFIG.uncompressedGap / 2) * 0.85; // 0.85 margin to not hit rubber exactly
const midY = CONFIG.centerY + distProfile * (currentApertureVisual / 2) * 0.85;
const flowD = `M ${CONFIG.pipeStartX},${startY}
C ${cX1},${startY} ${cX2},${midY} ${CONFIG.throatCenterX},${midY}
C ${cX3},${midY} ${cX4},${startY} ${CONFIG.pipeEndX},${startY}`;
const pathEl = document.getElementById(`flow-line-${i}`);
pathEl.setAttribute('d', flowD);
// Animate Offset
flowOffsets[i] -= currentSpeed * (deltaTime / 16); // Normalize to 60fps roughly
pathEl.setAttribute('stroke-dashoffset', flowOffsets[i]);
}
// 5. Update Colors & Materials based on Pressure
const colorCyan = '#00f0ff';
const colorBlue = '#3b82f6';
const colorOrange = '#ff5500';
const colorRed = '#ef4444';
const colorRubberBase = '#334155';
const colorRubberStress = '#9a3412'; // Dark amber/orange for stress
// Fluid Gradient changes (Upstream gets orange/red under high pressure)
gradStop1.setAttribute('stop-color', lerpColor(colorCyan, colorRed, Math.pow(pressure, 1.5)));
gradStop2.setAttribute('stop-color', lerpColor(colorBlue, colorOrange, pressure));
// Stop 3 remains cyan (regulated flow downstream)
// Rubber inner edge glow indicates stress
const stressColor = lerpColor(colorRubberBase, colorRubberStress, pressure);
rubberInnerTop.setAttribute('stop-color', stressColor);
rubberInnerBottom.setAttribute('stop-color', stressColor);
// Upstream visual warning overlay
pressureZoneGlow.style.opacity = (pressure * 0.6).toFixed(2);
pressureZoneText.style.opacity = pressure > 0.5 ? ((pressure - 0.5) * 2).toFixed(2) : 0;
// 6. Update UI Annotations
// Dimensions
dimLineTop.setAttribute('y2', currentTopInnerY);
dimLineBottom.setAttribute('y2', currentBottomInnerY);
dimBoundTop.setAttribute('y1', currentTopInnerY);
dimBoundTop.setAttribute('y2', currentTopInnerY);
dimBoundBottom.setAttribute('y1', currentBottomInnerY);
dimBoundBottom.setAttribute('y2', currentBottomInnerY);
dimTextVal.textContent = `${currentApertureMM.toFixed(1)} mm`;
// Readouts
apertureReadout.textContent = `${currentApertureMM.toFixed(1)} mm`;
const actualPressureValue = (pressure * CONFIG.maxPressure).toFixed(2);
pressureReadout.textContent = `${actualPressureValue} MPa`;
if(pressure > 0.8) {
pressureReadout.classList.add('warning');
apertureReadout.classList.add('warning');
document.getElementById('status-text').textContent = "高压强力限流中";
document.getElementById('status-text').style.color = "var(--accent-orange)";
} else if (pressure > 0.3) {
pressureReadout.classList.remove('warning');
apertureReadout.classList.remove('warning');
document.getElementById('status-text').textContent = "自适应调节中";
document.getElementById('status-text').style.color = "var(--accent-cyan)";
} else {
pressureReadout.classList.remove('warning');
apertureReadout.classList.remove('warning');
document.getElementById('status-text').
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TRIZ 最终理想解 - 柔性自适应无源限流器</title>
<style>
:root {
--bg-base: #06090e;
--panel-bg: rgba(13, 17, 28, 0.85);
--border-glow: #1e3a5f;
--text-main: #e2e8f0;
--text-muted: #64748b;
--neon-cyan: #00f0ff;
--neon-red: #ff3c00;
--rubber-color: #d97706;
--font-mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
}
body, html {
margin: 0;
padding: 0;
height: 100%;
background-color: var(--bg-base);
background-image:
radial-gradient(circle at 50% 50%, rgba(0, 240, 255, 0.03) 0%, transparent 60%),
linear-gradient(rgba(255, 255, 255, 0.02) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.02) 1px, transparent 1px);
background-size: 100% 100%, 40px 40px, 40px 40px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
color: var(--text-main);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.dashboard {
width: 96%;
max-width: 1400px;
display: flex;
flex-direction: column;
gap: 1.5rem;
position: relative;
z-index: 10;
}
.header {
display: flex;
justify-content: space-between;
align-items: flex-end;
padding-bottom: 1rem;
border-bottom: 1px solid rgba(0, 240, 255, 0.2);
}
.header-title {
margin: 0;
font-size: 1.75rem;
font-weight: 300;
letter-spacing: 2px;
color: var(--neon-cyan);
text-transform: uppercase;
}
.header-subtitle {
font-family: var(--font-mono);
font-size: 0.85rem;
color: var(--text-muted);
letter-spacing: 1px;
}
.visualizer-container {
position: relative;
width: 100%;
aspect-ratio: 21 / 9;
background: rgba(4, 7, 12, 0.6);
border: 1px solid var(--border-glow);
border-radius: 8px;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.8), inset 0 0 20px rgba(0, 240, 255, 0.05);
overflow: hidden;
backdrop-filter: blur(4px);
}
.svg-canvas {
width: 100%;
height: 100%;
display: block;
}
.hud-overlay {
position: absolute;
top: 20px;
left: 20px;
pointer-events: none;
}
.hud-badge {
background: rgba(0, 240, 255, 0.1);
border: 1px solid var(--neon-cyan);
color: var(--neon-cyan);
padding: 4px 10px;
font-size: 0.75rem;
font-family: var(--font-mono);
border-radius: 4px;
box-shadow: 0 0 10px rgba(0, 240, 255, 0.2);
}
.control-panel {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 1.5rem;
background: var(--panel-bg);
padding: 1.5rem;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.05);
}
.data-card {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 1rem;
background: rgba(0, 0, 0, 0.4);
border-left: 3px solid var(--neon-cyan);
border-radius: 0 6px 6px 0;
}
.data-card.pressure-card { border-left-color: var(--neon-red); }
.data-card.aperture-card { border-left-color: var(--rubber-color); }
.data-label {
font-size: 0.8rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1px;
}
.data-value {
font-family: var(--font-mono);
font-size: 1.8rem;
font-weight: bold;
display: flex;
align-items: baseline;
gap: 0.5rem;
}
.data-unit {
font-size: 1rem;
color: var(--text-muted);
font-weight: normal;
}
.slider-container {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-top: 0.5rem;
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 20px;
width: 20px;
border-radius: 50%;
background: var(--neon-red);
cursor: pointer;
margin-top: -8px;
box-shadow: 0 0 15px var(--neon-red);
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
background: rgba(255, 255, 255, 0.1);
border-radius: 2px;
}
.auto-tag {
font-size: 0.75rem;
color: var(--neon-cyan);
background: rgba(0, 240, 255, 0.1);
padding: 2px 6px;
border-radius: 4px;
margin-left: 10px;
display: inline-block;
transition: opacity 0.3s;
}
.hidden { opacity: 0.2; }
</style>
</head>
<body>
<div class="dashboard">
<div class="header">
<div>
<h1 class="header-title">柔性自适应无源限流器</h1>
<div class="header-subtitle">TRIZ 最终理想解 (IFR) 原理动态推演系统 // VASCULAR AUTO-REGULATION MODEL</div>
</div>
<div class="header-subtitle" style="text-align: right;">
STATUS: <span style="color: var(--neon-cyan)">ACTIVE</span><br>
POWER: PASSIVE / ZERO-ENERGY
</div>
</div>
<div class="visualizer-container">
<div class="hud-overlay">
<span class="hud-badge">SYS. IFR_VISUALIZATION</span>
</div>
<svg class="svg-canvas" viewBox="0 0 1200 500" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- Flow Gradient (Dynamic based on pressure) -->
<linearGradient id="fluidGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop id="grad-start" offset="0%" stop-color="#00f0ff" />
<stop id="grad-mid" offset="40%" stop-color="#00f0ff" />
<stop offset="65%" stop-color="#00f0ff" />
<stop offset="100%" stop-color="#00f0ff" />
</linearGradient>
<!-- Metallic Casing Gradient -->
<linearGradient id="metalGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#475569" />
<stop offset="50%" stop-color="#1e293b" />
<stop offset="100%" stop-color="#0f172a" />
</linearGradient>
<!-- Rubber Texture Pattern -->
<pattern id="rubberHatch" width="8" height="8" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
<line x1="0" y1="0" x2="0" y2="8" stroke="#78350f" stroke-width="2" opacity="0.5"/>
</pattern>
<!-- Glow Filter -->
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="intense-glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
</defs>
<!-- Grid Background inside SVG -->
<g stroke="rgba(255,255,255,0.03)" stroke-width="1">
<line x1="300" y1="0" x2="300" y2="500" stroke-dasharray="4 4" />
<line x1="600" y1="0" x2="600" y2="500" stroke-dasharray="4 4" />
<line x1="900" y1="0" x2="900" y2="500" stroke-dasharray="4 4" />
<line x1="0" y1="250" x2="1200" y2="250" stroke="rgba(0,240,255,0.1)" />
</g>
<!-- Fluid Dynamics Group (Rendered below pipes) -->
<g id="fluid-group"></g>
<!-- Steel Casing Structure -->
<!-- Top Casing -->
<path d="M -50 90 L 1250 90 L 1250 150 L -50 150 Z" fill="url(#metalGrad)" stroke="#64748b" stroke-width="1.5" />
<!-- Bottom Casing -->
<path d="M -50 350 L 1250 350 L 1250 410 L -50 410 Z" fill="url(#metalGrad)" stroke="#64748b" stroke-width="1.5" />
<!-- Inner Pipe Guidelines -->
<line x1="0" y1="150" x2="400" y2="150" stroke="#cbd5e1" stroke-width="3" />
<line x1="0" y1="350" x2="400" y2="350" stroke="#cbd5e1" stroke-width="3" />
<line x1="800" y1="150" x2="1200" y2="150" stroke="#cbd5e1" stroke-width="3" />
<line x1="800" y1="350" x2="1200" y2="350" stroke="#cbd5e1" stroke-width="3" />
<!-- Elastic Rubber Throat Core -->
<g id="throat-group">
<!-- Top Rubber Base -->
<path id="rubber-top" d="M 400 90 L 800 90 L 800 150 C 650 150, 550 150, 400 150 Z" fill="#b45309" stroke="#fbbf24" stroke-width="2" />
<path id="rubber-top-tex" d="M 400 90 L 800 90 L 800 150 C 650 150, 550 150, 400 150 Z" fill="url(#rubberHatch)" />
<!-- Bottom Rubber Base -->
<path id="rubber-bottom" d="M 400 410 L 800 410 L 800 350 C 650 350, 550 350, 400 350 Z" fill="#b45309" stroke="#fbbf24" stroke-width="2" />
<path id="rubber-bottom-tex" d="M 400 410 L 800 410 L 800 350 C 650 350, 550 350, 400 350 Z" fill="url(#rubberHatch)" />
<!-- Internal Steel Wire Skeleton (Generated dynamically) -->
<g id="skeleton-group"></g>
</g>
<!-- Annotation Lines and Labels -->
<g class="annotations" font-family="'JetBrains Mono', Consolas, monospace" font-size="13" fill="#cbd5e1" font-weight="bold">
<!-- Left: Problem / Resource -->
<polyline points="200,280 200,440 220,440" fill="none" stroke="#ff3c00" stroke-width="1.5" />
<circle cx="200" cy="280" r="3" fill="#ff3c00" />
<text x="230" y="444" fill="#ff3c00">1. 动能资源化</text>
<text x="230" y="464" font-size="11" fill="#94a3b8" font-weight="normal">压力升高,自动驱动形变(无需外部电源)</text>
<!-- Center: Core Innovation -->
<polyline points="600,150 600,60 620,60" fill="none" stroke="#fbbf24" stroke-width="1.5" />
<circle cx="600" cy="150" r="3" fill="#fbbf24" />
<text x="630" y="64" fill="#fbbf24">2. 仿生弹性喉口 (可变节流)</text>
<text x="630" y="84" font-size="11" fill="#94a3b8" font-weight="normal">利用橡胶骨架随压力收缩孔径,抵消流量增量</text>
<!-- Right: IFR State -->
<polyline points="1000,280 1000,440 1020,440" fill="none" stroke="#00f0ff" stroke-width="1.5" />
<circle cx="1000" cy="280" r="3" fill="#00f0ff" filter="url(#glow)"/>
<text x="1030" y="444" fill="#00f0ff">3. 最终理想解 (IFR)</text>
<text x="1030" y="464" font-size="11" fill="#94a3b8" font-weight="normal">消除矛盾:零传感器实现无源自适应恒定稳流</text>
</g>
</svg>
</div>
<div class="control-panel">
<!-- Manual Pressure Override -->
<div class="data-card pressure-card">
<div class="data-label">
系统入口流体压力 (P_in)
<span class="auto-tag" id="auto-status">自动演示中</span>
</div>
<div class="slider-container">
<input type="range" id="pressure-slider" min="0" max="1" step="0.01" value="0">
</div>
<div class="data-value" style="color: var(--neon-red); margin-top: 5px;">
<span id="val-pressure">0.00</span> <span class="data-unit">MPa</span>
</div>
</div>
<!-- Throat Status -->
<div class="data-card aperture-card">
<div class="data-label">喉口实时动态孔径 (D_throat)</div>
<div class="data-value" style="color: var(--rubber-color); margin-top: auto;">
<span id="val-aperture">200</span> <span class="data-unit">mm</span>
</div>
<div style="font-size: 0.75rem; color: #64748b; font-family: var(--font-mono);">
额定: 200mm | 极限压缩: 120mm
</div>
</div>
<!-- Output Status -->
<div class="data-card">
<div class="data-label">出口稳态流量判定 (Q_out)</div>
<div class="data-value" style="color: var(--neon-cyan); margin-top: auto;">
<span id="val-flow">STABLE</span> <span class="data-unit">100% REGULATED</span>
</div>
<div style="font-size: 0.75rem; color: #64748b; font-family: var(--font-mono);">
状态: <span id="flow-status-text" style="color: #00f0ff;">畅通 (低压)</span>
</div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
// Configuration
const numFluidLines = 9;
const fluidGroup = document.getElementById('fluid-group');
const skeletonGroup = document.getElementById('skeleton-group');
const fluidPaths = [];
// Create Fluid Lines
for (let i = 0; i < numFluidLines; i++) {
const p = document.createElementNS("http://www.w3.org/2000/svg", "path");
p.setAttribute("fill", "none");
p.setAttribute("stroke", "url(#fluidGradient)");
// Central lines are thicker
const width = i === 4 ? 4 : (i === 3 || i === 5) ? 3 : 2;
p.setAttribute("stroke-width", width.toString());
p.setAttribute("stroke-dasharray", "15 45");
p.setAttribute("filter", "url(#glow)");
fluidGroup.appendChild(p);
fluidPaths.push(p);
// Add a faint background track for each line
const track = document.createElementNS("http://www.w3.org/2000/svg", "path");
track.setAttribute("fill", "none");
track.setAttribute("stroke", "rgba(0, 240, 255, 0.15)");
track.setAttribute("stroke-width", "1");
fluidGroup.appendChild(track);
fluidPaths.push({ isTrack: true, el: track, index: i });
}
// Create Rubber Skeleton Lines (Steel Wires)
const numSkeletons = 4;
const skeletonsTop = [];
const skeletonsBottom = [];
for (let i = 0; i < numSkeletons; i++) {
const st = document.createElementNS("http://www.w3.org/2000/svg", "path");
st.setAttribute("fill", "none");
st.setAttribute("stroke", "rgba(255, 255, 255, 0.4)");
st.setAttribute("stroke-width", "1.5");
st.setAttribute("stroke-dasharray", "3 4");
skeletonGroup.appendChild(st);
skeletonsTop.push(st);
const sb = document.createElementNS("http://www.w3.org/2000/svg", "path");
sb.setAttribute("fill", "none");
sb.setAttribute("stroke", "rgba(255, 255, 255, 0.4)");
sb.setAttribute("stroke-width", "1.5");
sb.setAttribute("stroke-dasharray", "3 4");
skeletonGroup.appendChild(sb);
skeletonsBottom.push(sb);
}
// State & Interaction
let time = 0;
let dashOffset = 0;
let isManual = false;
let targetPressure = 0;
let currentPressure = 0; // Smoothed
const slider = document.getElementById('pressure-slider');
const autoStatus = document.getElementById('auto-status');
slider.addEventListener('input', (e) => {
isManual = true;
targetPressure = parseFloat(e.target.value);
autoStatus.innerText = "手动控制中";
autoStatus.classList.add('hidden');
autoStatus.style.color = "#ff3c00";
autoStatus.style.background = "rgba(255, 60, 0, 0.1)";
});
// Geometry Constraints
const center = 250;
const maxGap = 200; // 350 - 150 = 200 (at 0 MPa)
const minGap = 120; // Target min gap (at 1 MPa)
const maxDeformation = (maxGap - minGap) / 2; // 40px per side
function updatePathInterpolation() {
// Determine target pressure (Sine wave if auto)
if (!isManual) {
targetPressure = (Math.sin(time * 0.8) + 1) / 2; // Cycles between 0 and 1
slider.value = targetPressure;
}
// Easing for smooth visual transition
currentPressure += (targetPressure - currentPressure) * 0.1;
// Deformation Calculation
const defY = maxDeformation * currentPressure; // 0 to 40
// --- 1. Update Rubber Throat Shapes ---
const topY = 150 + defY;
const botY = 350 - defY;
const topPath = `M 400 90 L 800 90 L 800 150 C 650 ${topY}, 550 ${topY}, 400 150 Z`;
document.getElementById('rubber-top').setAttribute('d', topPath);
document.getElementById('rubber-top-tex').setAttribute('d', topPath);
const botPath = `M 400 410 L 800 410 L 800 350 C 650 ${botY}, 550 ${botY}, 400 350 Z`;
document.getElementById('rubber-bottom').setAttribute('d', botPath);
document.getElementById('rubber-bottom-tex').setAttribute('d', botPath);
// --- 2. Update Wire Skeletons ---
for (let i = 0; i < numSkeletons; i++) {
const depthTop = 105 + (i * 12);
const defFactorT = (depthTop - 90) / 60;
const yCtrlTop = depthTop + (defY * defFactorT);
skeletonsTop[i].setAttribute('d', `M 400 ${depthTop} C 550 ${yCtrlTop}, 650 ${yCtrlTop}, 800 ${depthTop}`);
const depthBot = 395 - (i * 12);
const defFactorB = (410 - depthBot) / 60;
const yCtrlBot = depthBot - (defY * defFactorB);
skeletonsBottom[i].setAttribute('d', `M 400 ${depthBot} C 550 ${yCtrlBot}, 650 ${yCtrlBot}, 800 ${depthBot}`);
}
// --- 3. Update Fluid Dynamics ---
const currentGap = maxGap - (defY * 2);
const gapRatio = currentGap / maxGap;
// Base flow speed + increase in the throat due to restriction
// However, visual dash speed is uniform, so we manipulate offset globally,
// but we simulate higher kinetic energy via color and frequency.
const baseSpeed = 2;
const kineticMultiplier = 1 + (currentPressure * 2);
dashOffset -= baseSpeed * kineticMultiplier;
fluidPaths.forEach((pathObj, i) => {
const isTrack = pathObj.isTrack;
const el = isTrack ? pathObj.el : pathObj;
const idx = isTrack ? pathObj.index : i;
// Offset from center for this specific streamline
const originalOffset = (idx - Math.floor(numFluidLines/2)) * 22;
const currentOffset = originalOffset * gapRatio;
const yEnter = center + originalOffset;
const yMid = center + currentOffset;
// Smooth B-spline like curve through the throat
const d = `M -50 ${yEnter} L 400 ${yEnter} C 500 ${yEnter}, 500 ${yMid}, 600 ${yMid} C 700 ${yMid}, 700 ${yEnter}, 800 ${yEnter} L 1250 ${yEnter}`;
el.setAttribute('d', d);
if (!isTrack) {
el.setAttribute('stroke-dashoffset', dashOffset.toString());
}
});
// --- 4. Update Colors (Pressure visualization) ---
// Red shifts as pressure increases on the left side
const r = Math.floor(0 + currentPressure * 255);
const g = Math.floor(240 - currentPressure * 180); // Cyan to Orange/Red
const b = Math.floor(255 - currentPressure * 255);
const highPressureColor = `rgb(${r}, ${g}, ${b})`;
document.getElementById('grad-start').setAttribute('stop-color', highPressureColor);
document.getElementById('grad-mid').setAttribute('stop-color', highPressureColor);
// Color intensity logic for glow
if(currentPressure > 0.8) {
document.getElementById('rubber-top').setAttribute('filter', 'url(#glow)');
document.getElementById('rubber-bottom').setAttribute('filter', 'url(#glow)');
} else {
document.getElementById('rubber-top').removeAttribute('filter');
document.getElementById('rubber-bottom').removeAttribute('filter');
}
// --- 5. Update UI Data ---
document.getElementById('val-pressure').innerText = currentPressure.toFixed(2);
document.getElementById('val-aperture').innerText = Math.round(maxGap - (defY * 2));
const statusText = document.getElementById('flow-status-text');
if (currentPressure < 0.3) {
statusText.innerText = "畅通 (低压区)";
statusText.style.color = "#00f0ff";
} else if (currentPressure < 0.7) {
statusText.innerText = "自适应调节中 (中压区)";
statusText.style.color = "#fbbf24";
} else {
statusText.innerText = "高压强力限流 (稳压输出)";
statusText.style.color = "#ff3c00";
}
}
// Animation Loop
let lastTime = 0;
function animate(timestamp) {
const deltaTime = timestamp - lastTime;
lastTime = timestamp;
// time scale for auto-play
time += 0.015;
updatePathInterpolation();
requestAnimationFrame(animate);
}
// Start Loop
requestAnimationFrame(animate);
});
</script>
</body>
</html>
积分规则:每轮对话扣10分
等待动画代码生成...
