<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>被动伸缩滑块小腿 · 原理动画</title>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{
--bg:#060a14;--fg:#d8e2ec;--muted:#4a5e72;
--cyan:#00e5ff;--green:#00ff88;--amber:#ffb800;
--red:#ff4422;--spring:#c8e600;--core:#5a9ae6;
--card:rgba(8,16,34,0.92);--border:rgba(0,229,255,0.15);
}
body{background:var(--bg);color:var(--fg);font-family:'Share Tech Mono',monospace;overflow:hidden;height:100vh}
canvas#main{position:fixed;top:0;left:0;width:100%;height:100%}
/* 顶部阶段条 */
.phase-bar{
position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:10;
display:flex;align-items:center;gap:14px;
background:var(--card);border:1px solid var(--border);border-radius:10px;
padding:10px 22px;backdrop-filter:blur(14px);pointer-events:none;
}
.phase-name{font-family:'Orbitron',sans-serif;font-weight:700;font-size:16px;min-width:110px;transition:color .3s}
.phase-dots{display:flex;gap:8px}
.phase-dot{width:10px;height:10px;border-radius:50%;border:2px solid var(--muted);transition:all .35s}
.phase-dot.active{transform:scale(1.4);box-shadow:0 0 8px currentColor}
.phase-progress{width:180px;height:3px;background:rgba(255,255,255,.08);border-radius:2px;overflow:hidden}
.phase-progress-fill{height:100%;border-radius:2px;transition:width .06s linear}
/* 左侧描述面板 */
.desc-panel{
position:fixed;top:90px;left:22px;width:232px;z-index:10;
background:var(--card);border:1px solid var(--border);border-radius:10px;
padding:14px 16px;backdrop-filter:blur(14px);pointer-events:none;
}
.sec-title{font-family:'Orbitron',sans-serif;font-size:9px;letter-spacing:2.5px;margin-bottom:8px;text-transform:uppercase}
.desc-text{font-size:12.5px;line-height:1.65;color:var(--fg)}
.chain-text{font-size:10.5px;color:var(--muted);line-height:1.55}
/* 右侧参数面板 */
.params-panel{
position:fixed;top:90px;right:22px;width:210px;z-index:10;
background:var(--card);border:1px solid var(--border);border-radius:10px;
padding:14px 16px;backdrop-filter:blur(14px);pointer-events:none;
}
.param-row{display:flex;justify-content:space-between;align-items:center;padding:5px 0;border-bottom:1px solid rgba(255,255,255,.04)}
.param-label{font-size:11px;color:var(--muted)}
.param-value{font-size:12px;font-weight:bold;transition:color .3s}
.lock-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:5px;transition:all .3s}
/* 底部力曲线 */
.force-panel{
position:fixed;bottom:20px;left:50%;transform:translateX(-50%);width:480px;height:110px;z-index:10;
background:var(--card);border:1px solid var(--border);border-radius:10px;
padding:8px 14px;backdrop-filter:blur(14px);pointer-events:none;
}
.force-title{font-family:'Orbitron',sans-serif;font-size:8px;letter-spacing:2px;color:var(--cyan);margin-bottom:3px}
canvas#force{width:100%;height:calc(100% - 18px)}
/* 左上标题 */
.title-block{position:fixed;top:22px;left:22px;z-index:10;pointer-events:none}
.title-main{font-family:'Orbitron',sans-serif;font-size:13px;font-weight:900;letter-spacing:3px;color:var(--cyan)}
.title-sub{font-size:10.5px;color:var(--muted);margin-top:3px}
@media(max-width:900px){
.desc-panel,.params-panel{display:none}
.force-panel{width:calc(100% - 40px)}
}
</style>
</head>
<body>
<canvas id="main"></canvas>
<div class="title-block">
<div class="title-main">PASSIVE SLIDER LEG</div>
<div class="title-sub">被动伸缩滑块小腿 · 自适应缓冲原理</div>
</div>
<div class="phase-bar">
<div class="phase-name" id="pName">悬空摆动</div>
<div class="phase-dots">
<div class="phase-dot" id="d0"></div>
<div class="phase-dot" id="d1"></div>
<div class="phase-dot" id="d2"></div>
<div class="phase-dot" id="d3"></div>
</div>
<div class="phase-progress"><div class="phase-progress-fill" id="pFill"></div></div>
</div>
<div class="desc-panel">
<div class="sec-title" style="color:var(--cyan)">当前时序</div>
<div class="desc-text" id="descTxt">电磁锁松开,滑块自由伸缩</div>
<div style="margin-top:12px">
<div class="sec-title" style="color:var(--amber)">机理作用链</div>
<div class="chain-text">摆动解锁 → 触地吸能 → 力矩阈值锁死 → 刚性支撑 → 离地解锁弹起</div>
</div>
<div style="margin-top:12px">
<div class="sec-title" style="color:var(--red)">失效条件</div>
<div class="chain-text">极高频冲击下电磁锁响应滞后,导致支撑刚性不足</div>
</div>
</div>
<div class="params-panel">
<div class="sec-title" style="color:var(--cyan)">实时参数</div>
<div class="param-row"><span class="param-label">弹簧刚度</span><span class="param-value" style="color:var(--spring)">15 N/mm</span></div>
<div class="param-row"><span class="param-label">最大行程</span><span class="param-value" style="color:var(--cyan)">30 mm</span></div>
<div class="param-row"><span class="param-label">锁紧响应</span><span class="param-value" style="color:var(--green)"><10 ms</span></div>
<div class="param-row"><span class="param-label">当前压缩</span><span class="param-value" id="vComp" style="color:var(--amber)">0.0 mm</span></div>
<div class="param-row"><span class="param-label">弹簧力</span><span class="param-value" id="vForce" style="color:var(--spring)">0 N</span></div>
<div class="param-row"><span class="param-label">膝力矩</span><span class="param-value" id="vTorq" style="color:var(--fg)">0.0 Nm</span></div>
<div class="param-row"><span class="param-label">支撑阈值</span><span class="param-value" style="color:var(--red)">5 Nm</span></div>
<div class="param-row"><span class="param-label">电磁锁</span><span class="param-value" id="vLock"><span class="lock-dot" id="lockDot"></span><span id="lockTxt">松开</span></span></div>
</div>
<div class="force-panel">
<div class="force-title">FORCE-TIME 力-时曲线</div>
<canvas id="force"></canvas>
</div>
<script>
/* =========================================
被动伸缩滑块小腿 · 原理动画
========================================= */
const C = document.getElementById('main');
const X = C.getContext('2d');
const FC = document.getElementById('force');
const FX = FC.getContext('2d');
// ─── 阶段定义 ───
const PH = [
{ name:'悬空摆动', sub:'SWING', dur:2000, color:'#00ff88', desc:'电磁锁松开,滑块自由伸缩' },
{ name:'触地缓冲', sub:'IMPACT', dur:1500, color:'#ffb800', desc:'地面反力压缩弹簧吸收冲击能' },
{ name:'刚性支撑', sub:'STANCE', dur:2000, color:'#ff4422', desc:'电磁锁锁死,滑块变刚性连杆' },
{ name:'蹬地离地', sub:'PUSH-OFF', dur:1500, color:'#00e5ff', desc:'解锁释放弹簧余能弹起离地' }
];
const CYCLE = PH.reduce((s,p)=>s+p.dur,0);
// ─── 机构尺寸(以800高为基准) ───
const M = {
sTopOff:28, sLen:200, sOW:78, sIW:56,
spTopOff:36, spNatLen:120,
coreLen:155, coreW:44,
footW:92, footH:14,
maxComp:42
};
M.natRel = M.spTopOff + M.spNatLen + M.coreLen; // 自然足距
// ─── 状态 ───
let S = { ct:0, pi:0, pp:0, by:0, comp:0, compMm:0, lock:false, fog:false, sf:0, kt:0, pc:'#00ff88' };
let particles = [];
let fHist = [];
const FH_MAX = 350;
let lastT = 0;
// ─── 画布尺寸 ───
function resize(){
C.width = innerWidth; C.height = innerHeight;
FC.width = FC.parentElement.clientWidth - 28;
FC.height = FC.parentElement.clientHeight - 22;
}
addEventListener('resize', resize); resize();
// ─── 工具函数 ───
const lerp = (a,b,t)=>a+(b-a)*t;
const clamp = (v,lo,hi)=>Math.max(lo,Math.min(hi,v));
const eio = t=>t<.5?2*t*t:-1+(4-2*t)*t;
const ein = t=>t*t;
const eout = t=>t*(2-t);
// ─── 更新逻辑 ───
function update(dt){
S.ct = (S.ct + dt) % CYCLE;
let el = 0;
for(let i=0;i<4;i++){
if(S.ct < el + PH[i].dur){ S.pi=i; S.pp=(S.ct-el)/PH[i].dur; break; }
el += PH[i].dur;
}
const t = S.pp;
const sc = C.height / 800;
const GY = C.height * 0.78;
const natRel = M.natRel * sc;
const maxC = M.maxComp * sc;
const byContact = GY - natRel; // 足刚好触地时bodyY
switch(S.pi){
case 0: // 悬空摆动
S.by = lerp(byContact - 100*sc, byContact, eio(t));
S.comp = 0; S.lock = false; S.fog = false;
break;
case 1: // 触地缓冲
S.by = lerp(byContact, byContact + maxC, ein(t));
S.comp = S.by - byContact; S.lock = false; S.fog = true;
break;
case 2: // 刚性支撑
S.by = byContact + maxC + Math.sin(t*Math.PI*10)*0.6*sc;
S.comp = maxC; S.lock = true; S.fog = true;
break;
case 3: // 蹬地离地
S.by = lerp(byContact + maxC, byContact - 100*sc, eout(t));
S.lock = false;
if(S.by > byContact){ S.fog=true; S.comp = S.by - byContact; }
else{ S.fog=false; S.comp=0; }
break;
}
S.comp = clamp(S.comp, 0, maxC);
S.compMm = (S.comp / maxC) * 30;
S.sf = 15 * S.compMm;
S.kt = S.fog ? S.sf * 0.12 : 0;
S.pc = PH[S.pi].color;
// 粒子
if(S.pi===2 && t<0.04) spawnLock();
if(S.pi===3 && t>0.1 && t<0.15) spawnRelease();
for(let i=particles.length-1;i>=0;i--){
const p=particles[i]; p.x+=p.vx; p.y+=p.vy; p.vy+=0.02; p.life-=p.dec;
if(p.life<=0) particles.splice(i,1);
}
// 力历史
fHist.push({ sf:S.sf, kt:S.kt, ph:S.pi });
if(fHist.length>FH_MAX) fHist.shift();
}
function spawnLock(){
const sc=C.height/800, cx=C.width*0.55, ly=S.by+(M.sTopOff+M.sLen*0.55)*sc;
for(let i=0;i<20;i++) particles.push({
x:cx+(Math.random()-.5)*80*sc, y:ly+(Math.random()-.5)*16*sc,
vx:(Math.random()-.5)*2.5, vy:(Math.random()-.5)*3,
life:1, dec:.015+Math.random()*.02, color:'#ff4422', sz:2+Math.random()*3
});
}
function spawnRelease(){
const sc=C.height/800, cx=C.width*0.55, ly=S.by+(M.sTopOff+M.sLen*0.55)*sc;
for(let i=0;i<10;i++) particles.push({
x:cx+(Math.random()-.5)*60*sc, y:ly+(Math.random()-.5)*12*sc,
vx:(Math.random()-.5)*1.5, vy:(Math.random()-.5)*2,
life:1, dec:.025+Math.random()*.02, color:'#00e5ff', sz:1.5+Math.random()*2
});
}
// ─── 绘制:网格背景 ───
function drawGrid(){
const w=C.width, h=C.height;
X.strokeStyle='rgba(0,229,255,0.025)'; X.lineWidth=1;
const sp=50;
for(let x=0;x<w;x+=sp){ X.beginPath(); X.moveTo(x,0); X.lineTo(x,h); X.stroke(); }
for(let y=0;y<h;y+=sp){ X.beginPath(); X.moveTo(0,y); X.lineTo(w,y); X.stroke(); }
}
// ─── 绘制:地面 ───
function drawGround(){
const gy=C.height*0.78, w=C.width;
// 地面填充
const grd=X.createLinearGradient(0,gy,0,C.height);
grd.addColorStop(0,'#111827'); grd.addColorStop(1,'#060a14');
X.fillStyle=grd; X.fillRect(0,gy,w,C.height-gy);
// 地面线
X.strokeStyle='#1e2d44'; X.lineWidth=2;
X.beginPath(); X.moveTo(0,gy); X.lineTo(w,gy); X.stroke();
// 纹理斜线
X.strokeStyle='rgba(255,255,255,0.02)'; X.lineWidth=1;
for(let x=-C.height;x<w+C.height;x+=14){
X.beginPath(); X.moveTo(x,gy+2); X.lineTo(x+(C.height-gy),C.height); X.stroke();
}
// 地面冲击波纹
if(S.pi===1 && S.pp<0.5){
const cx=C.width*0.55;
const r = S.pp * 120 * (C.height/800);
const a = 1 - S.pp*2;
X.strokeStyle=`rgba(255,184,0,${a*0.3})`; X.lineWidth=1.5;
X.beginPath(); X.ellipse(cx,gy,r,r*0.3,0,0,Math.PI*2); X.stroke();
}
}
// ─── 绘制:完整机构 ───
function drawMech(){
const sc=C.height/800, cx=C.width*0.55, by=S.by, comp=S.comp;
const sT=by+M.sTopOff*sc, sB=sT+M.sLen*sc;
const spT=by+M.spTopOff*sc, spL=M.spNatLen*sc-comp, spB=spT+spL;
const cT=spB, cB=cT+M.coreLen*sc;
const sOW=M.sOW*sc, sIW=M.sIW*sc, cW=M.coreW*sc, fW=M.footW*sc, fH=M.footH*sc;
// ── 机体连接 ──
X.strokeStyle='#00e5ff'; X.lineWidth=2.5*sc;
X.beginPath(); X.moveTo(cx-30*sc,by); X.lineTo(cx-20*sc,by+15*sc);
X.lineTo(cx+20*sc,by+15*sc); X.lineTo(cx+30*sc,by); X.closePath();
X.fillStyle='rgba(0,229,255,0.04)'; X.fill(); X.stroke();
// 机体横线
X.lineWidth=2*sc;
X.beginPath(); X.moveTo(cx-55*sc,by-5*sc); X.lineTo(cx+55*sc,by-5*sc); X.stroke();
// 连接到套筒
X.lineWidth=2*sc;
X.beginPath(); X.moveTo(cx,by+15*sc); X.lineTo(cx,sT); X.stroke();
// 标注
X.fillStyle='rgba(0,229,255,0.5)'; X.font=`${10*sc}px "Share Tech Mono"`; X.textAlign='center';
X.fillText('BODY 机体',cx,by-14*sc);
// ── 套筒(左侧完整,右侧剖切)──
const hO=sOW/2, hI=sIW/2;
const cutS=sT+40*sc, cutE=sB-28*sc;
X.strokeStyle='#00e5ff'; X.lineWidth=2*sc;
// 左外壁
X.beginPath(); X.moveTo(cx-hO,sT); X.lineTo(cx-hO,sB); X.stroke();
// 左内壁
X.lineWidth=1.2*sc;
X.beginPath(); X.moveTo(cx-hI,sT+8*sc); X.lineTo(cx-hI,sB-8*sc); X.stroke();
// 右外壁(上段+下段)
X.lineWidth=2*sc;
X.beginPath(); X.moveTo(cx+hO,sT); X.lineTo(cx+hO,cutS); X.stroke();
X.beginPath(); X.moveTo(cx+hO,cutE); X.lineTo(cx+hO,sB); X.stroke();
// 右内壁
X.lineWidth=1.2*sc;
X.beginPath(); X.moveTo(cx+hI,sT+8*sc); X.lineTo(cx+hI,cutS-4*sc); X.stroke();
X.beginPath(); X.moveTo(cx+hI,cutE+4*sc); X.lineTo(cx+hI,sB-8*sc); X.stroke();
// 顶盖
X.lineWidth=2*sc;
X.beginPath(); X.moveTo(cx-hO,sT); X.lineTo(cx+hO,sT); X.stroke();
// 底盖(留芯轴孔)
X.beginPath(); X.moveTo(cx-hO,sB); X.lineTo(cx-hI-2*sc,sB); X.stroke();
X.beginPath(); X.moveTo(cx+hI+2*sc,sB); X.lineTo(cx+hO,sB); X.stroke();
// 剖切虚线
X.setLineDash([4*sc,4*sc]); X.strokeStyle='rgba(0,229,255,0.2)'; X.lineWidth=1*sc;
X.beginPath(); X.moveTo(cx+hO,cutS); X.lineTo(cx+hO,cutE); X.stroke();
X.setLineDash([]);
// 剖面线(斜线纹理)
X.strokeStyle='rgba(0,229,255,0.06)'; X.lineWidth=1*sc;
for(let y=cutS;y<cutE;y+=8*sc){
X.beginPath(); X.moveTo(cx+hI,y); X.lineTo(cx+hO,y+6*sc); X.stroke();
}
// 套筒标注
X.fillStyle='rgba(0,229,255,0.45)'; X.font=`${9.5*sc}px "Share Tech Mono"`; X.textAlign='left';
X.fillText('SLEEVE 套筒',cx+hO+10*sc,sT+18*sc);
// ── 弹簧 ──
const spW=sIW*0.72, coils=8;
const glow=S.comp/(M.maxComp*sc);
if(glow>0.05){ X.shadowColor='#c8e600'; X.shadowBlur=12*glow*sc; }
X.strokeStyle='#c8e600'; X.lineWidth=2.5*sc; X.lineJoin='round';
const segH=spL/(coils*2);
X.beginPath(); X.moveTo(cx,spT);
for(let i=0;i<coils*2;i++){
const ny=spT+(i+1)*segH;
const xo=(i%2===0)?spW/2:-spW/2;
X.lineTo(cx+xo,ny);
}
X.lineTo(cx,spB); X.stroke();
X.shadowColor='transparent'; X.shadowBlur=0;
// 弹簧顶板
X.fillStyle='rgba(200,230,0,0.1)'; X.strokeStyle='#c8e600'; X.lineWidth=1.5*sc;
X.fillRect(cx-spW/2-5*sc,spT-3*sc,spW+10*sc,6*sc);
X.strokeRect(cx-spW/2-5*sc,spT-3*sc,spW+10*sc,6*sc);
// 弹簧底板
X.fillRect(cx-spW/2-5*sc,spB-3*sc,spW+10*sc,6*sc);
X.strokeRect(cx-spW/2-5*sc,spB-3*sc,spW+10*sc,6*sc);
// 弹簧标注
X.fillStyle='rgba(200,230,0,0.55)'; X.font=`${9.5*sc}px "Share Tech Mono"`; X.textAlign='right';
X.fillText('SPRING 弹簧',cx-hI-12*sc,(spT+spB)/2-4*sc);
X.fillText('k=15N/mm',cx-hI-12*sc,(spT+spB)/2+10*sc);
// ── 芯轴 ──
const hcW=cW/2;
X.strokeStyle='#5a9ae6'; X.lineWidth=2*sc;
X.fillStyle='rgba(90,154,230,0.06)';
X.fillRect(cx-hcW,cT,cW,cB-cT);
X.strokeRect(cx-hcW,cT,cW,cB-cT);
// 芯轴刻度
X.strokeStyle='rgba(90,154,230,0.2)'; X.lineWidth=1*sc;
const marks=Math.floor((cB-cT)/(14*sc));
for(let i=1;i<=marks;i++){
const my=cT+(cB-cT)*i/(marks+1);
X.beginPath(); X.moveTo(cx-hcW+3*sc,my); X.lineTo(cx+hcW-3*sc,my); X.stroke();
}
// 芯轴标注
X.fillStyle='rgba(90,154,230,0.5)'; X.font=`${9.5*sc}px "Share Tech Mono"`; X.textAlign='left';
X.fillText('CORE 芯轴',cx+hcW+10*sc,cT+28*sc);
// ── 电磁锁 ──
const lockY=sT+(sB-sT)*0.55;
const engaged=S.lock;
const lc=engaged?'#ff4422':'#00ff88';
const pinExt=engaged?hI-hcW-2*sc:4*sc;
const lkW=14*sc, lkH=11*sc;
// 左锁销
X.fillStyle=engaged?'rgba(255,68,34,0.25)':'rgba(0,255,136,0.08)';
X.strokeStyle=lc; X.lineWidth=1.5*sc;
X.beginPath(); X.rect(cx-hI-lkW,lockY-lkH/2,lkW+pinExt,lkH); X.fill(); X.stroke();
// 右锁销(剖切区域内半透明)
X.globalAlpha=0.5;
X.beginPath(); X.rect(cx+hI-pinExt,lockY-lkH/2,lkW+pinExt,lkH); X.fill(); X.stroke();
X.globalAlpha=1;
// 锁紧指示灯
if(engaged){
X.shadowColor='#ff4422'; X.shadowBlur=16*sc;
X.fillStyle='#ff4422'; X.beginPath(); X.arc(cx,lockY,3.5*sc,0,Math.PI*2); X.fill();
X.shadowColor='transparent'; X.shadowBlur=0;
} else {
X.fillStyle='#00ff88'; X.beginPath(); X.arc(cx,lockY,2.5*sc,0,Math.PI*2); X.fill();
}
// 锁标注
X.fillStyle='rgba(255,255,255,0.35)'; X.font=`${9*sc}px "Share Tech Mono"`; X.textAlign='left';
X.fillText('EM-LOCK 电磁锁',cx+hO+10*sc,lockY-8*sc);
X.fillStyle=lc; X.font=`${9*sc}px "Orbitron"`;
X.fillText(engaged?'LOCKED':'UNLOCKED',cx+hO+10*sc,lockY+6*sc);
// 电磁线圈符号
X.strokeStyle='rgba(255,255,255,0.15)'; X.lineWidth=1*sc;
const coilX=cx-hI-lkW-8*sc;
for(let i=0;i<3;i++){
const cy_=lockY-6*sc+i*5*sc;
X.beginPath(); X.arc(coilX,cy_,3*sc,-Math.PI/2,Math.PI/2); X.stroke();
}
// ── 足垫 ──
const fpY=cB;
X.fillStyle='rgba(106,159,181,0.06)'; X.strokeStyle='#6a9fb5'; X.lineWidth=2*sc;
X.beginPath();
X.moveTo(cx-fW/2,fpY); X.lineTo(cx-fW/2,fpY+fH*0.6);
X.quadraticCurveTo(cx-fW/2,fpY+fH,cx-fW/4,fpY+fH);
X.lineTo(cx+fW/4,fpY+fH);
X.quadraticCurveTo(cx+fW/2,fpY+fH,cx+fW/2,fpY+fH*0.6);
X.lineTo(cx+fW/2,fpY); X.closePath(); X.fill(); X.stroke();
// 足垫纹路
X.strokeStyle='rgba(106,159,181,0.2)'; X.lineWidth=1*sc;
for(let i=0;i<5;i++){
const lx=cx-fW/2+10*sc+i*16*sc;
X.beginPath(); X.moveTo(lx,fpY+4*sc); X.lineTo(lx,fpY+fH-4*sc); X.stroke();
}
X.fillStyle='rgba(106,159,181,0.45)'; X.font=`${9*sc}px "Share Tech Mono"`; X.textAlign='center';
X.fillText('FOOT PAD 仿生足垫',cx,fpY+fH+14*sc);
// ── 力箭头 ──
if(S.fog && S.sf>5){
const arrH=clamp(S.sf/450*65*sc,10*sc,70*sc);
drawArrow(cx-fW/2-22*sc,cB+fH+8*sc,cx-fW/2-22*sc,cB+fH+8*sc-arrH,'#00ff88',2*sc);
X.fillStyle='rgba(0,255,136,0.5)'; X.font=`${8.5*sc}px "Share Tech Mono"`; X.textAlign='right';
X.fillText('GRF',cx-fW/2-26*sc,cB+fH+8*sc-arrH/2+3*sc);
}
if(S.comp>3*sc){
const arrH2=clamp(S.sf/450*55*sc,8*sc,55*sc);
const sy=(spT+spB)/2;
drawArrow(cx+hO+8*sc,sy+arrH2/2,cx+hO+8*sc,sy-arrH2/2,'#c8e600',1.5*sc);
X.fillStyle='rgba(200,230,0,0.5)'; X.font=`${8.5*sc}px "Share Tech Mono"`; X.textAlign='left';
X.fillText('Fs',cx+hO+12*sc,sy+3*sc);
}
// 锁死力指示
if(S.lock){
drawArrow(cx-hO-8*sc,lockY-18*sc,cx-hO-8*sc,lockY+18*sc,'#ff4422',1.5*sc);
X.fillStyle='rgba(255,68,34,0.5)'; X.font=`${8*sc}px "Share Tech Mono"`; X.textAlign='right';
X.fillText('Rigid',cx-hO-12*sc,lockY+3*sc);
}
// ── 压缩量标注 ──
if(S.comp>4*sc){
const dimX=cx-hO-30*sc;
const natB=by+M.spTopOff*sc+M.spNatLen*sc;
X.strokeStyle='rgba(255,184,0,0.4)'; X.lineWidth=1*sc;
X.setLineDash([3*sc,3*sc]);
X.beginPath(); X.moveTo(dimX,spB); X.lineTo(dimX,natB); X.stroke();
X.setLineDash([]);
// 端点标记
X.beginPath(); X.moveTo(dimX-4*sc,spB); X.lineTo(dimX+4*sc,spB); X.stroke();
X.beginPath(); X.moveTo(dimX-4*sc,natB); X.lineTo(dimX+4*sc,natB); X.stroke();
X.fillStyle='#ffb800'; X.font=`${10*sc}px "Share Tech Mono"`; X.textAlign='right';
X.fillText(S.compMm.toFixed(1)+'mm',dimX-6*sc,(spB+natB)/2+4*sc);
}
// ── 阶段标签 ──
X.fillStyle=S.pc; X.font=`bold ${12*sc}px "Orbitron"`; X.textAlign='center';
X.fillText(PH[S.pi].sub,cx,by-32*sc);
// ── 摆动相运动线 ──
if(S.pi===0){
X.strokeStyle='rgba(0,255,136,0.12)'; X.lineWidth=1.5*sc;
for(let i=0;i<3;i++){
const ox=(i-1)*18*sc;
X.beginPath(); X.moveTo(cx+ox,cB+fH+5*sc);
X.lineTo(cx+ox-8*sc,cB+fH+25*sc); X.stroke();
}
}
// 蹬地相运动线
if(S.pi===3 && S.pp>0.3){
X.strokeStyle='rgba(0,229,255,0.15)'; X.lineWidth=1.5*sc;
for(let i=0;i<3;i++){
const ox=(i-1)*18*sc;
X.beginPath(); X.moveTo(cx+ox,cB+fH+5*sc);
X.lineTo(cx+ox+8*sc,cB+fH+25*sc); X.stroke();
}
}
}
function drawArrow(x1,y1,x2,y2,col,w){
const hl=8*(C.height/800), ang=Math.atan2(y2-y1,x2-x1);
X.strokeStyle=col; X.lineWidth=w;
X.beginPath(); X.moveTo(x1,y1); X.lineTo(x2,y2); X.stroke();
X.fillStyle=col; X.beginPath(); X.moveTo(x2,y2);
X.lineTo(x2-hl*Math.cos(ang-Math.PI/6),y2-hl*Math.sin(ang-Math.PI/6));
X.lineTo(x2-hl*Math.cos(ang+Math.PI/6),y2-hl*Math.sin(ang+Math.PI/6));
X.closePath(); X.fill();
}
// ─── 绘制:粒子 ───
function drawParticles(){
for(const p of particles){
X.globalAlpha=p.life; X.fillStyle=p.color;
X.beginPath(); X.arc(p.x,p.y,p.sz*p.life*(C.height/800),0,Math.PI*2); X.fill();
}
X.globalAlpha=1;
}
// ─── 绘制:小型犬腿上下文图 ───
function drawDogCtx(){
const sc=C.height/800;
const ox=140*sc, oy=C.height*0.34;
const s=0.55*sc;
X.save(); X.translate(ox,oy); X.scale(s,s);
// 机体躯干
const bob=Math.sin(S.ct/CYCLE*Math.PI*2)*4;
X.strokeStyle='rgba(0,229,255,0.35)'; X.lineWidth=3;
X.beginPath();
X.moveTo(-55,-12+bob); X.quadraticCurveTo(0,-28+bob,55,-12+bob);
X.stroke();
// 头
X.beginPath(); X.ellipse(75,-18+bob,22,14,0.2,0,Math.PI*2); X.stroke();
// 尾巴
X.beginPath(); X.moveTo(-55,-16+bob); X.quadraticCurveTo(-72,-38+bob,-60,-48+bob); X.stroke();
// 四条腿 - 前右腿高亮
drawMiniLeg(25,-10+bob, S.pi, true);
drawMiniLeg(12,-10+bob, (S.pi+2)%4, false);
drawMiniLeg(-28,-10+bob, (S.pi+1)%4, false);
drawMiniLeg(-40,-10+bob, (S.pi+3)%4, false);
X.restore();
// 标注文字
X.fillStyle='rgba(0,229,255,0.3)'; X.font=`${9*sc}px "Share Tech Mono"`; X.textAlign='center';
X.fillText('LEG CONTEXT',ox,oy+110*sc);
}
function drawMiniLeg(hx,hy,ph,hl){
const thL=55, shL=65;
const t=S.pp;
let ta,ka;
switch(ph){
case 0: ta=lerp(-25,18,eio(t))*Math.PI/180; ka=lerp(8,3,t)*Math.PI/180; break;
case 1: ta=lerp(18,5,ein(t))*Math.PI/180; ka=lerp(3,0,t)*Math.PI/180; break;
case 2: ta=lerp(5,-8,eio(t))*Math.PI/180; ka=0; break;
case 3: ta=lerp(-8,-25,eout(t))*Math.PI/180; ka=lerp(0,8,t)*Math.PI/180; break;
}
const kx=hx+Math.sin(ta)*thL, ky=hy+Math.cos(ta)*thL;
const sa=ta+ka, fx=kx+Math.sin(sa)*shL, fy=ky+Math.cos(sa)*shL;
const col=hl?'#00e5ff':'rgba(0,229,255,0.18)';
X.strokeStyle=col; X.lineWidth=hl?3:2;
X.beginPath(); X.moveTo(hx,hy); X.lineTo(kx,ky); X.stroke();
X.beginPath(); X.moveTo(kx,ky); X.lineTo(fx,fy); X.stroke();
X.fillStyle=col;
X.beginPath(); X.arc(hx,hy,3,0,Math.PI*2); X.fill();
X.beginPath(); X.arc(kx,ky,hl?4:2.5,0,Math.PI*2); X.fill();
if(hl){
// 高亮框
const mx=(kx+fx)/2, my=(ky+fy)/2;
X.strokeStyle='#ff4422'; X.lineWidth=1; X.setLineDash([3,3]);
X.beginPath(); X.rect(mx-10,my-6,20,12); X.stroke();
X.setLineDash([]);
// 箭头指向主视图
X.strokeStyle='rgba(255,68,34,0.25)'; X.lineWidth=1;
X.beginPath(); X.moveTo(mx+10,my); X.lineTo(mx+40,my); X.stroke();
drawArrow(mx+40,my,mx+55,my,'rgba(255,68,34,0.25)',1);
}
}
// ─── 力曲线 ───
function drawForceGraph(){
const w=FC.width, h=FC.height;
FX.clearRect(0,0,w,h);
FX.fillStyle='rgba(0,0,0,0.15)'; FX.fillRect(0,0,w,h);
// 网格
FX.strokeStyle='rgba(255,255,255,0.04)'; FX.lineWidth=1;
for(let y=0;y<h;y+=h/4){ FX.beginPath(); FX.moveTo(0,y); FX.lineTo(w,y); FX.stroke(); }
// 相位底色
const phColors=['0,255,136','255,184,0','255,68,34','0,229,255'];
for(let i=0;i<fHist.length;i++){
const x=(i/FH_MAX)*w;
FX.fillStyle=`rgba(${phColors[fHist[i].ph]},0.06)`;
FX.fillRect(x,0,w/FH_MAX+1,h);
}
// 弹簧力曲线
const maxF=450;
FX.strokeStyle='#c8e600'; FX.lineWidth=1.5;
FX.beginPath();
for(let i=0;i<fHist.length;i++){
const x=(i/FH_MAX)*w, y=h-(fHist[i].sf/maxF)*(h-10)-5;
i===0?FX.moveTo(x,y):FX.lineTo(x,y);
}
FX.stroke();
// 膝力矩曲线
const maxT=55;
FX.strokeStyle='#ff4422'; FX.lineWidth=1.5;
FX.beginPath();
for(let i=0;i<fHist.length;i++){
const x=(i/FH_MAX)*w, y=h-(fHist[i].kt/maxT)*(h-10)-5;
i===0?FX.moveTo(x,y):FX.lineTo(x,y);
}
FX.stroke();
// 阈值线
const thY=h-(5/maxT)*(h-10)-5;
FX.strokeStyle='rgba(255,68,34,0.3)'; FX.lineWidth=1; FX.setLineDash([4,4]);
FX.beginPath(); FX.moveTo(0,thY); FX.lineTo(w,thY); FX.stroke();
FX.setLineDash([]);
// 图例
FX.font='9px "Share Tech Mono"';
FX.fillStyle='#c8e600'; FX.textAlign='left'; FX.fillText('Spring Force',5,10);
FX.fillStyle='#ff4422'; FX.fillText('Knee Torque',105,10);
FX.fillStyle='rgba(255,68,34,0.5)'; FX.fillText('Threshold 5Nm',220,10);
}
// ─── UI同步 ───
function syncUI(){
const ph=PH[S.pi];
document.getElementById('pName').textContent=ph.name;
document.getElementById('pName').style.color=ph.color;
for(let i=0;i<4;i++){
const d=document.getElementById('d'+i);
d.style.borderColor=i===S.pi?PH[i].color:'#4a5e72';
d.style.backgroundColor=i===S.pi?PH[i].color:'transparent';
d.className='phase-dot'+(i===S.pi?' active':'');
}
document.getElementById('pFill').style.width=(S.pp*100)+'%';
document.getElementById('pFill').style.backgroundColor=ph.color;
document.getElementById('descTxt').textContent=ph.desc;
document.getElementById('vComp').textContent=S.compMm.toFixed(1)+' mm';
document.getElementById('vForce').textContent=S.sf.toFixed(0)+' N';
document.getElementById('vTorq').textContent=S.kt.toFixed(1)+' Nm';
const ld=document.getElementById('lockDot'), lt=document.getElementById('lockTxt');
if(S.lock){
ld.style.backgroundColor='#ff4422'; ld.style.boxShadow='0 0 8px #ff4422';
lt.textContent='锁死'; lt.style.color='#ff4422';
} else {
ld.style.backgroundColor='#00ff88'; ld.style.boxShadow='0 0 4px #00ff88';
lt.textContent='松开'; lt.style.color='#00ff88';
}
}
// ─── 主循环 ───
function loop(ts){
const dt=lastT?ts-lastT:16; lastT=ts;
update(dt);
X.clearRect(0,0,C.width,C.height);
drawGrid();
drawGround();
drawDogCtx();
drawMech();
drawParticles();
drawForceGraph();
syncUI();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>
</body>
</html>
实现说明
这是一个完整的被动伸缩滑块小腿自适应缓冲原理动画,采用深色技术蓝图美学风格,核心特点:
视觉设计
- 深海蓝黑底色 + 青色结构线 + 黄绿弹簧 + 蓝色芯轴 + 红/绿锁状态色,形成清晰的功能色彩语义
- 套筒右侧采用剖切视图,内部弹簧、芯轴、锁销一目了然
- 锁紧时红色脉冲发光 + 粒子爆裂,解锁时青色粒子释放,增强状态转换的感知冲击
四阶段动画循环(7秒一轮)
- 悬空摆动(2s):机体下降,足垫悬空,弹簧自然长度,锁松开
- 触地缓冲(1.5s):足垫触地,地面冲击波纹,弹簧压缩吸能
- 刚性支撑(2s):电磁锁锁死(粒子闪烁),芯轴被夹紧,微振动表现刚性传力
- 蹬地离地(1.5s):解锁释放,弹簧余能弹起,运动线表示离地
交互信息层
- 左上角:小型犬腿上下文图,高亮腿对应主视图,红色虚线框标注机构位置
- 右侧面板:实时参数(压缩量、弹簧力、膝力矩、锁状态)
- 底部:力-时曲线同步绘制,含5Nm阈值线标示锁紧触发点
- 压缩量标注随动画动态显示,力箭头大小随实际力值缩放
积分规则:第一轮对话扣减6分,后续每轮扣4分
等待动画代码生成...
