独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TRIZ IFR: 负压悬浮攀爬系统</title>
<style>
:root {
--bg-base: #050914;
--grid-color: rgba(30, 58, 138, 0.2);
--neon-blue: #00f0ff;
--neon-amber: #ffb703;
--metal-dark: #1e293b;
--metal-light: #475569;
--text-main: #e2e8f0;
--text-muted: #64748b;
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: var(--bg-base);
color: var(--text-main);
font-family: 'SF Pro Display', 'Inter', -apple-system, sans-serif;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
/* 背景网格 */
.grid-bg {
position: absolute;
inset: 0;
background-image:
linear-gradient(to right, var(--grid-color) 1px, transparent 1px),
linear-gradient(to bottom, var(--grid-color) 1px, transparent 1px);
background-size: 40px 40px;
opacity: 0.5;
z-index: 0;
}
/* 绝对定位的小字号 HUD 层,严格避免遮挡核心区 */
.hud {
position: absolute;
z-index: 10;
background: rgba(15, 23, 42, 0.8);
border: 1px solid rgba(0, 240, 255, 0.3);
border-radius: 6px;
padding: 12px 16px;
backdrop-filter: blur(8px);
pointer-events: none;
}
.hud-tl { top: 24px; left: 24px; width: 280px; }
.hud-br { bottom: 24px; right: 24px; width: 320px; text-align: right; }
.hud-bl { bottom: 24px; left: 24px; width: 240px; }
.title {
font-size: 14px;
font-weight: 700;
color: #fff;
letter-spacing: 1.5px;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 8px;
}
.title::before {
content: '';
display: block;
width: 8px;
height: 8px;
background: var(--neon-blue);
border-radius: 50%;
box-shadow: 0 0 8px var(--neon-blue);
}
.text-sm {
font-size: 12px;
line-height: 1.6;
color: var(--text-muted);
}
.mono {
font-family: 'JetBrains Mono', 'Fira Code', monospace;
}
.highlight { color: var(--neon-blue); font-weight: 600; }
.amber { color: var(--neon-amber); font-weight: 600; }
.data-row {
display: flex;
justify-content: space-between;
margin-top: 6px;
font-size: 11px;
border-top: 1px dashed rgba(100, 116, 139, 0.3);
padding-top: 6px;
}
/* SVG 核心动画区 */
#scene {
position: relative;
z-index: 5;
width: 100vw;
height: 100vh;
}
/* SVG 元素样式 */
.stair-polygon { fill: #0f172a; stroke: none; }
.stair-edge { stroke: var(--neon-blue); stroke-width: 2; filter: drop-shadow(0 0 4px rgba(0, 240, 255, 0.6)); }
.stair-surface { stroke: #1e293b; stroke-width: 1; stroke-dasharray: 4 4; }
.rail { stroke: var(--metal-light); stroke-width: 10; stroke-linecap: round; }
.piston-rod { stroke: #cbd5e1; stroke-width: 4; stroke-linecap: round; }
.joint { fill: #94a3b8; stroke: var(--bg-base); stroke-width: 2; }
.suction-cup { transition: fill 0.3s, filter 0.3s; }
.cup-idle { fill: var(--metal-light); }
.cup-active { fill: var(--neon-blue); filter: drop-shadow(0 0 8px var(--neon-blue)); }
.vac-flow {
stroke: var(--neon-blue);
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 4 8;
animation: flowUp 0.6s linear infinite;
opacity: 0;
transition: opacity 0.3s;
}
.vac-active .vac-flow { opacity: 1; }
@keyframes flowUp {
to { stroke-dashoffset: -12; }
}
.robot-body {
fill: rgba(30, 41, 59, 0.9);
stroke: var(--neon-amber);
stroke-width: 2;
filter: drop-shadow(0 10px 20px rgba(0,0,0,0.5));
}
.target-line {
stroke: var(--neon-amber);
stroke-width: 1.5;
stroke-dasharray: 4 4;
opacity: 0.4;
}
</style>
</head>
<body>
<div class="grid-bg"></div>
<!-- 左上角:状态与核心参数 -->
<div class="hud hud-tl">
<div class="title">IFR: 空气负压悬浮攀爬系统</div>
<div class="text-sm">消除轮式打滑矛盾,变连续滚动为交替步行。</div>
<div class="data-row mono">
<span>系统状态</span>
<span id="sys-status" class="highlight">初始化序列...</span>
</div>
<div class="data-row mono">
<span>前置机械足极值</span>
<span id="front-press">-0.0 kPa</span>
</div>
<div class="data-row mono">
<span>后置机械足极值</span>
<span id="rear-press">-0.0 kPa</span>
</div>
</div>
<!-- 左下角:实时推杆行程 -->
<div class="hud hud-bl">
<div class="text-sm" style="color: #fff; margin-bottom: 4px;">两自由度电动推杆监测</div>
<div class="data-row mono">
<span>前足垂直行程</span>
<span id="front-stroke">0 mm</span>
</div>
<div class="data-row mono">
<span>后足垂直行程</span>
<span id="rear-stroke">0 mm</span>
</div>
</div>
<!-- 右下角:TRIZ 原理说明 -->
<div class="hud hud-br">
<div class="text-sm" style="color: #fff; margin-bottom: 4px;">TRIZ 最终理想解 (IFR)</div>
<div class="text-sm">
<span class="highlight">原理剖析:</span>利用环境中无处不在的空气作为“隐形锚点资源”。当机械足贴合台阶表面时,微型真空泵瞬间建立极高负压(-60kPa),将环境大气压转化为强大的法向抓取力。车身无需依赖任何斜面摩擦力,通过纯粹的悬空拉拽实现绝对抓地。
</div>
</div>
<svg id="scene" viewBox="0 0 1000 600" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 发光滤镜 -->
<filter id="glow-amber" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
</defs>
<g id="world">
<!-- 台阶环境 -->
<g id="stairs-group"></g>
<!-- 机器人主体 -->
<g id="robot">
<!-- 预测运动轨迹 -->
<line id="target-vector" class="target-line" x1="0" y1="0" x2="0" y2="0" />
<!-- 后置机械足 -->
<g id="rear-leg">
<line id="r-h-rail" class="rail" x1="0" y1="0" x2="0" y2="0"/>
<line id="r-h-piston" class="piston-rod" x1="0" y1="0" x2="0" y2="0"/>
<line id="r-v-rail" class="rail" x1="0" y1="0" x2="0" y2="0"/>
<line id="r-v-piston" class="piston-rod" x1="0" y1="0" x2="0" y2="0"/>
<circle id="r-joint" class="joint" cx="0" cy="0" r="6"/>
<g id="r-cup-group">
<path id="r-cup" class="suction-cup cup-idle" d="M -15 -8 L 15 -8 L 20 0 L -20 0 Z"/>
<g id="r-vac-fx" class="vac-active" style="opacity: 0;">
<line class="vac-flow" x1="-10" y1="15" x2="-2" y2="0"/>
<line class="vac-flow" x1="0" y1="20" x2="0" y2="0"/>
<line class="vac-flow" x1="10" y1="15" x2="2" y2="0"/>
</g>
<text x="25" y="0" fill="#64748b" font-size="10" font-family="monospace">REAR_VAC</text>
</g>
</g>
<!-- 前置机械足 -->
<g id="front-leg">
<line id="f-h-rail" class="rail" x1="0" y1="0" x2="0" y2="0"/>
<line id="f-h-piston" class="piston-rod" x1="0" y1="0" x2="0" y2="0"/>
<line id="f-v-rail" class="rail" x1="0" y1="0" x2="0" y2="0"/>
<line id="f-v-piston" class="piston-rod" x1="0" y1="0" x2="0" y2="0"/>
<circle id="f-joint" class="joint" cx="0" cy="0" r="6"/>
<g id="f-cup-group">
<path id="f-cup" class="suction-cup cup-idle" d="M -15 -8 L 15 -8 L 20 0 L -20 0 Z"/>
<g id="f-vac-fx" class="vac-active" style="opacity: 0;">
<line class="vac-flow" x1="-10" y1="15" x2="-2" y2="0"/>
<line class="vac-flow" x1="0" y1="20" x2="0" y2="0"/>
<line class="vac-flow" x1="10" y1="15" x2="2" y2="0"/>
</g>
<text x="25" y="0" fill="#00f0ff" font-size="10" font-family="monospace">FRONT_VAC</text>
</g>
</g>
<!-- 主车身箱体 -->
<rect id="main-body" class="robot-body" x="-70" y="-35" width="140" height="70" rx="8" />
<circle cx="0" cy="0" r="4" fill="#ffb703" filter="url(#glow-amber)"/>
<text x="-55" y="-15" fill="#94a3b8" font-size="10" font-family="monospace" letter-spacing="1">CORE BOX</text>
<line x1="-55" y1="15" x2="55" y2="15" stroke="#334155" stroke-width="2" stroke-dasharray="2 4"/>
</g>
</g>
</svg>
<script>
/**
* 动画状态机引擎 (抗标签页节流)
*/
let logicalTime = 0;
let lastRealTime = performance.now();
const tweens = [];
function tween(obj, prop, target, duration, delay = 0, ease = t => t) {
return new Promise(resolve => {
tweens.push({
obj, prop, start: obj[prop], target,
startTime: logicalTime + delay,
duration, ease, resolve
});
});
}
function sleep(ms) {
return new Promise(resolve => {
tweens.push({ isSleep: true, startTime: logicalTime, duration: ms, resolve });
});
}
// 缓动函数
const easeInOutCubic = t => t < .5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
const easeOutQuad = t => t * (2 - t);
const easeInQuad = t => t * t;
/**
* 状态与场景数据
*/
const state = {
camX: 300, camY: 200, // 相机中心点
bodyX: 300, bodyY: 180, // 车身中心点
fFootX: 500, fFootY: 200, // 前足落点
rFootX: 300, rFootY: 300, // 后足落点
fVac: false, rVac: false // 真空状态
};
// 物理映射常量
const SCALE = 3; // 1px = 3mm (行程300mm映射为100px)
const BODY_HALF_W = 70;
const RAIL_OFFSET_Y = -10; // 导轨在车身的相对高度
/**
* 构建无限循环台阶几何
*/
function buildStairs() {
const group = document.getElementById('stairs-group');
let html = '';
// 预生成足够宽的台阶,通过后续平移实现无限视觉循环
for(let i = -3; i <= 6; i++) {
let x = i * 200;
let y = 400 - i * 100;
// 台阶实体
html += `<polygon class="stair-polygon" points="${x},1000 ${x},${y} ${x+200},${y} ${x+200},1000" />`;
// 发光边缘
html += `<polyline class="stair-edge" points="${x},${y} ${x+200},${y} ${x+200},${y-100}" />`;
// 表面纹理
html += `<line class="stair-surface" x1="${x+10}" y1="${y+10}" x2="${x+190}" y2="${y+10}" />`;
}
group.innerHTML = html;
}
/**
* 核心渲染循环
*/
function render() {
// 更新相机世界视图
const world = document.getElementById('world');
world.setAttribute('transform', `translate(${500 - state.camX}, ${300 - state.camY})`);
// 更新车身
const body = document.getElementById('main-body');
body.setAttribute('x', state.bodyX - BODY_HALF_W);
body.setAttribute('y', state.bodyY - 35);
// 核心连接点
const attachY = state.bodyY + RAIL_OFFSET_Y;
const fAttachX = state.bodyX + BODY_HALF_W;
const rAttachX = state.bodyX - BODY_HALF_W;
// 更新前机械足 (两自由度结构: 水平导轨 + 垂直推杆)
document.getElementById('f-h-rail').setAttribute('x1', fAttachX);
document.getElementById('f-h-rail').setAttribute('y1', attachY);
document.getElementById('f-h-rail').setAttribute('x2', state.fFootX);
document.getElementById('f-h-rail').setAttribute('y2', attachY);
document.getElementById('f-h-piston').setAttribute('x1', fAttachX);
document.getElementById('f-h-piston').setAttribute('y1', attachY);
document.getElementById('f-h-piston').setAttribute('x2', state.fFootX);
document.getElementById('f-h-piston').setAttribute('y2', attachY);
document.getElementById('f-v-rail').setAttribute('x1', state.fFootX);
document.getElementById('f-v-rail').setAttribute('y1', attachY);
document.getElementById('f-v-rail').setAttribute('x2', state.fFootX);
document.getElementById('f-v-rail').setAttribute('y2', state.fFootY - 30);
document.getElementById('f-v-piston').setAttribute('x1', state.fFootX);
document.getElementById('f-v-piston').setAttribute('y1', state.fFootY - 30);
document.getElementById('f-v-piston').setAttribute('x2', state.fFootX);
document.getElementById('f-v-piston').setAttribute('y2', state.fFootY);
document.getElementById('f-joint').setAttribute('cx', state.fFootX);
document.getElementById('f-joint').setAttribute('cy', attachY);
const fCupGroup = document.getElementById('f-cup-group');
fCupGroup.setAttribute('transform', `translate(${state.fFootX}, ${state.fFootY})`);
// 更新后机械足
document.getElementById('r-h-rail').setAttribute('x1', rAttachX);
document.getElementById('r-h-rail').setAttribute('y1', attachY);
document.getElementById('r-h-rail').setAttribute('x2', state.rFootX);
document.getElementById('r-h-rail').setAttribute('y2', attachY);
document.getElementById('r-h-piston').setAttribute('x1', rAttachX);
document.getElementById('r-h-piston').setAttribute('y1', attachY);
document.getElementById('r-h-piston').setAttribute('x2', state.rFootX);
document.getElementById('r-h-piston').setAttribute('y2', attachY);
document.getElementById('r-v-rail').setAttribute('x1', state.rFootX);
document.getElementById('r-v-rail').setAttribute('y1', attachY);
document.getElementById('r-v-rail').setAttribute('x2', state.rFootX);
document.getElementById('r-v-rail').setAttribute('y2', state.rFootY - 30);
document.getElementById('r-v-piston').setAttribute('x1', state.rFootX);
document.getElementById('r-v-piston').setAttribute('y1', state.rFootY - 30);
document.getElementById('r-v-piston').setAttribute('x2', state.rFootX);
document.getElementById('r-v-piston').setAttribute('y2', state.rFootY);
document.getElementById('r-joint').setAttribute('cx', state.rFootX);
document.getElementById('r-joint').setAttribute('cy', attachY);
const rCupGroup = document.getElementById('r-cup-group');
rCupGroup.setAttribute('transform', `translate(${state.rFootX}, ${state.rFootY})`);
// 更新目标牵引线
const targetLine = document.getElementById('target-vector');
targetLine.setAttribute('x1', state.bodyX);
targetLine.setAttribute('y1', state.bodyY);
targetLine.setAttribute('x2', state.fFootX);
targetLine.setAttribute('y2', state.fFootY);
// 更新 UI 状态
const fCup = document.getElementById('f-cup');
const fVacFx = document.getElementById('f-vac-fx');
if(state.fVac) {
fCup.setAttribute('class', 'suction-cup cup-active');
fVacFx.style.opacity = '1';
document.getElementById('front-press').textContent = '-60.0 kPa';
document.getElementById('front-press').classList.add('highlight');
} else {
fCup.setAttribute('class', 'suction-cup cup-idle');
fVacFx.style.opacity = '0';
document.getElementById('front-press').textContent = '-0.0 kPa';
document.getElementById('front-press').classList.remove('highlight');
}
const rCup = document.getElementById('r-cup');
const rVacFx = document.getElementById('r-vac-fx');
if(state.rVac) {
rCup.setAttribute('class', 'suction-cup cup-active');
rVacFx.style.opacity = '1';
document.getElementById('rear-press').textContent = '-60.0 kPa';
document.getElementById('rear-press').classList.add('highlight');
} else {
rCup.setAttribute('class', 'suction-cup cup-idle');
rVacFx.style.opacity = '0';
document.getElementById('rear-press').textContent = '-0.0 kPa';
document.getElementById('rear-press').classList.remove('highlight');
}
// 动态行程计算 (垂直距 * SCALE)
const fStroke = Math.max(0, state.fFootY - attachY) * SCALE;
const rStroke = Math.max(0, state.rFootY - attachY) * SCALE;
document.getElementById('front-stroke').textContent = fStroke.toFixed(0) + ' mm';
document.getElementById('rear-stroke').textContent = rStroke.toFixed(0) + ' mm';
}
function updateSysStatus(text, isAction = true) {
const el = document.getElementById('sys-status');
el.textContent = text;
el.className = isAction ? 'amber' : 'highlight';
}
/**
* 动画主循环
*/
function animateLoop(now) {
let dt = now - lastRealTime;
lastRealTime = now;
if (dt > 100) dt = 16; // 防止切换标签页导致的暴走
logicalTime += dt;
let activeTweens = [];
for (let tw of tweens) {
if (logicalTime < tw.startTime) {
activeTweens.push(tw);
continue;
}
if (tw.isSleep) {
if (logicalTime >= tw.startTime + tw.duration) {
if(tw.resolve) tw.resolve();
} else {
activeTweens.push(tw);
}
continue;
}
let progress = (logicalTime - tw.startTime) / tw.duration;
if (progress >= 1) {
tw.obj[tw.prop] = tw.target;
if (tw.resolve) tw.resolve();
} else {
tw.obj[tw.prop] = tw.start + (tw.target - tw.start) * tw.ease(progress);
activeTweens.push(tw);
}
}
tweens.length = 0;
tweens.push(...activeTweens);
render();
requestAnimationFrame(animateLoop);
}
/**
* 核心业务序列 (锚定-拉拽-换脚)
*/
async function runSequence() {
await sleep(500);
while(true) {
// ---- 阶段 1: 前足建立锚定 ----
updateSysStatus("前置机械足下探锚定台阶");
tween(state, 'fFootY', 200, 400, 0, easeOutQuad);
await sleep(500);
state.fVac = true;
updateSysStatus("建立极端负压场 [-60kPa]", false);
await sleep(800);
// ---- 阶段 2: 拉拽车身悬空 ----
updateSysStatus("后足释放,推杆收缩悬空拉拽");
state.rVac = false;
// 车身移动到 S2 级台阶的正上方
tween(state, 'bodyX', 500, 1500, 0, easeInOutCubic);
tween(state, 'bodyY', 80, 1500, 0, easeInOutCubic);
// 相机跟随平移,保持居中
tween(state, 'camX', 500, 1500, 0, easeInOutCubic);
tween(state, 'camY', 100, 1500, 0, easeInOutCubic);
// 后足离地前摆收缩
tween(state, 'rFootX', 500, 1200, 0, easeInOutCubic);
tween(state, 'rFootY', 150, 1000, 0, easeInOutCubic);
await sleep(1500);
// ---- 阶段 3: 后足降落锚定 ----
updateSysStatus("后置机械足精准就位");
tween(state, 'rFootY', 200, 400, 0, easeOutQuad);
await sleep(400);
state.rVac = true;
updateSysStatus("后置防滑负压锁定", false);
await sleep(600);
// ---- 阶段 4: 前足释放前探 ----
updateSysStatus("交替步行:前足释放探寻新锚点");
state.fVac = false;
// 前足抬起
tween(state, 'fFootY', 50, 400, 0, easeInOutCubic);
await sleep(400);
// 前足水平前移至下一台阶 S3
tween(state, 'fFootX', 700, 800, 0, easeInOutCubic);
await sleep(800);
// ---- 阶段 5: 无缝重置循环 (视觉无感空间平移) ----
// 车身从 (300,180) -> (500,80),平移量 (200, -100)
// 整体减去平移量,实现无限阶梯逻辑
state.bodyX -= 200; state.bodyY += 100;
state.rFootX -= 200; state.rFootY += 100;
state.fFootX -= 200; state.fFootY += 100;
state.camX -= 200; state.camY += 100;
// 重置渲染跳过中间态
render();
}
}
// 初始化
buildStairs();
requestAnimationFrame(animateLoop);
runSequence();
</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
