独立渲染引擎就绪就绪
<!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>
<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}
body{background:#050911;font-family:'Share Tech Mono',monospace;color:#a0bcc8;min-height:100vh;display:flex;flex-direction:column;overflow-x:hidden}
.header{width:100%;padding:14px 28px;display:flex;align-items:baseline;gap:18px;background:linear-gradient(180deg,rgba(0,24,48,0.95) 60%,transparent);position:relative;z-index:2}
.header h1{font-family:'Orbitron',sans-serif;font-weight:900;font-size:1.15rem;color:#00e5ff;letter-spacing:3px}
.header .sub{font-size:0.78rem;color:#3a6878;letter-spacing:1px}
.wrap{flex:1;display:flex;width:100%;max-width:1440px;margin:0 auto;padding:0 16px 16px;gap:16px}
.canvas-wrap{flex:1;display:flex;align-items:center;justify-content:center;min-height:0}
.canvas-wrap svg{width:100%;max-height:82vh;display:block}
.panel{width:270px;display:flex;flex-direction:column;gap:12px;overflow-y:auto;max-height:82vh;padding-right:4px}
.panel::-webkit-scrollbar{width:3px}
.panel::-webkit-scrollbar-thumb{background:rgba(0,180,255,0.2);border-radius:2px}
.card{background:rgba(8,18,32,0.92);border:1px solid rgba(0,160,230,0.15);border-radius:7px;padding:13px 14px}
.card h3{font-family:'Orbitron',sans-serif;font-size:0.68rem;color:#00b4ff;margin-bottom:10px;letter-spacing:1.5px;text-transform:uppercase}
.btn{width:100%;padding:9px 12px;margin-bottom:7px;border:1px solid rgba(0,160,230,0.25);border-radius:4px;background:rgba(0,36,72,0.45);color:#7ec8e3;font-family:'Share Tech Mono',monospace;font-size:0.82rem;cursor:pointer;transition:all .18s;outline:none}
.btn:last-child{margin-bottom:0}
.btn:hover{background:rgba(0,70,130,0.45);border-color:#00e5ff;color:#fff}
.btn:active{transform:scale(.97)}
.btn.warn{border-color:rgba(255,109,0,0.35);color:#ff9800}
.btn.warn:hover{background:rgba(255,70,0,0.15);border-color:#ff6d00}
.btn.ok{border-color:rgba(0,230,118,0.3);color:#00e676}
.btn.ok:hover{background:rgba(0,140,70,0.15);border-color:#00e676}
.btn:disabled{opacity:.35;cursor:not-allowed;transform:none}
.si{display:flex;justify-content:space-between;padding:5px 0;font-size:0.76rem;border-bottom:1px solid rgba(0,160,230,0.08)}
.si:last-child{border-bottom:none}
.sl{color:#3e6878}.sv{font-weight:bold}
.sv.idle{color:#546e7a}.sv.run{color:#00e5ff}.sv.lock{color:#ff6d00}.sv.safe{color:#00e676}.sv.danger{color:#ff1744}
.sg{margin-bottom:10px}
.sg:last-child{margin-bottom:0}
.sg label{display:block;font-size:0.7rem;color:#3e6878;margin-bottom:4px}
.sg input[type=range]{width:100%;-webkit-appearance:none;height:3px;background:rgba(0,160,230,0.18);border-radius:2px;outline:none}
.sg input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:13px;height:13px;background:#00b4ff;border-radius:50%;cursor:pointer;box-shadow:0 0 6px rgba(0,180,255,0.4)}
.sg .rv{text-align:right;font-size:0.7rem;color:#00b4ff;margin-top:2px}
.ifr{border-color:rgba(255,109,0,0.22)}
.ifr h3{color:#ff8f00}
.ifr p{font-size:0.72rem;color:#5a7a8a;line-height:1.65}
.ifr .hl{color:#ff9800;font-weight:bold}
@keyframes pulse-lock{0%,100%{opacity:.55}50%{opacity:1}}
.lock-pulse{animation:pulse-lock 1.2s ease-in-out infinite}
</style>
</head>
<body>
<div class="header">
<h1>WEDGE-LOCK MODULE</h1>
<span class="sub">楔形自锁纯电动升降模组 · IFR 最终理想解原理演示</span>
</div>
<div class="wrap">
<div class="canvas-wrap">
<svg id="svg" viewBox="0 0 1000 800" xmlns="http://www.w3.org/2000/svg"></svg>
</div>
<div class="panel">
<div class="card">
<h3>Operation</h3>
<button class="btn" id="bUp">▲ 升降上升</button>
<button class="btn" id="bDn">▼ 升降下降</button>
<button class="btn warn" id="bEm">⚠ 模拟断电</button>
<button class="btn ok" id="bRs">✦ 恢复供电</button>
<button class="btn" id="bWk">◉ 行走演示</button>
</div>
<div class="card">
<h3>Status</h3>
<div class="si"><span class="sl">运行状态</span><span class="sv idle" id="sState">待机</span></div>
<div class="si"><span class="sl">升降位置</span><span class="sv run" id="sPos">50%</span></div>
<div class="si"><span class="sl">丝杠转速</span><span class="sv run" id="sRpm">0</span></div>
<div class="si"><span class="sl">楔形自锁</span><span class="sv idle" id="sLock">待命</span></div>
<div class="si"><span class="sl">供电状态</span><span class="sv safe" id="sPow">正常</span></div>
</div>
<div class="card">
<h3>Parameters</h3>
<div class="sg"><label>丝杠导程</label><input type="range" id="rLead" min="2" max="10" value="5" step="0.5"><div class="rv" id="vLead">5.0 mm/转</div></div>
<div class="sg"><label>楔形角 α</label><input type="range" id="rAngle" min="3" max="14" value="8" step="0.5"><div class="rv" id="vAngle">8.0° (摩擦角 ρ≈12°)</div></div>
</div>
<div class="card ifr">
<h3>IFR · 最终理想解</h3>
<p>安全不依赖传感器或电子器件,而由<span class="hl">几何结构本身</span>保障。楔形角 α < 摩擦角 ρ → 断电即自锁,<span class="hl">零能耗维持安全</span>。弹簧仅为触发资源,锁定由楔面摩擦自锁永久维持。</p>
</div>
</div>
</div>
<script>
// ============ 配置 ============
const CFG = { lead: 5, wedgeAngle: 8, frictionAngle: 12 };
const NS = 'http://www.w3.org/2000/svg';
// ============ 状态 ============
let S = {
mode: 'idle', // idle | lifting | emergency | locked | walking
liftDir: 0, // -1 0 1
platNorm: 0.5, // 0顶 1底
targetNorm: 0.5,
screwAng: 0,
screwSpd: 0,
wedgeExt: 0, // 0收 1出
targetWedge: 0,
wheelAng: 0,
walkT: 0,
flashA: 0,
lockPulse: 0,
powerOn: true,
emergencyT: 0,
};
// ============ 坐标常量 ============
const COL_L = 300, COL_R = 700, COL_IN_L = 328, COL_IN_R = 672;
const COL_TOP = 58, COL_BOT = 680;
const PLAT_YMIN = 130, PLAT_YMAX = 600;
const SCREW_CX = 500, SCREW_W = 28;
const NUT_W = 110, NUT_H = 44;
const WEDGE_MAX_EXT = 118;
const RACK_DEPTH = 14, RACK_H = 8, RACK_GAP = 18;
function platY() { return PLAT_YMIN + S.platNorm * (PLAT_YMAX - PLAT_YMIN); }
// ============ SVG辅助 ============
function el(tag, a, parent) {
const e = document.createElementNS(NS, tag);
if (a) Object.entries(a).forEach(([k,v]) => e.setAttribute(k, v));
if (parent) parent.appendChild(e);
return e;
}
function $(id) { return document.getElementById(id); }
// ============ 初始化SVG ============
const svg = $('svg');
let refs = {};
function init() {
svg.innerHTML = '';
// -- defs --
const defs = el('defs', null, svg);
// 网格
const gp = el('pattern', {id:'grid',width:40,height:40,patternUnits:'userSpaceOnUse'}, defs);
el('path', {d:'M 40 0 L 0 0 0 40',fill:'none',stroke:'rgba(0,130,220,0.055)','stroke-width':0.5}, gp);
// 发光滤镜
['glow','glow-s','glow-r'].forEach((id,i) => {
const f = el('filter', {id,x:'-60%',y:'-60%',width:'220%',height:'220%'}, defs);
el('feGaussianBlur', {stdDeviation: String([4,8,10][i]), result:'b'}, f);
const m = el('feMerge', null, f);
el('feMergeNode', {in:'b'}, m);
el('feMergeNode', {in:'SourceGraphic'}, m);
});
// 柱体渐变
const cg = el('linearGradient', {id:'cg',x1:'0%',y1:'0%',x2:'100%',y2:'0%'}, defs);
el('stop', {offset:'0%','stop-color':'#0b1622'}, cg);
el('stop', {offset:'50%','stop-color':'#162636'}, cg);
el('stop', {offset:'100%','stop-color':'#0b1622'}, cg);
// 楔块渐变
const wg = el('linearGradient', {id:'wg',x1:'0%',y1:'0%',x2:'100%',y2:'100%'}, defs);
el('stop', {offset:'0%','stop-color':'#ffa726'}, wg);
el('stop', {offset:'100%','stop-color':'#e65100'}, wg);
// 丝杠渐变
const sg = el('linearGradient', {id:'sg',x1:'0%',y1:'0%',x2:'1',y2:'0'}, defs);
el('stop', {offset:'0%','stop-color':'#1a2a3a'}, sg);
el('stop', {offset:'40%','stop-color':'#2a4050'}, sg);
el('stop', {offset:'100%','stop-color':'#1a2a3a'}, sg);
// 底座渐变
const bg = el('linearGradient', {id:'bg',x1:'0%',y1:'0%',x2:'0%',y2:'1'}, defs);
el('stop', {offset:'0%','stop-color':'#1a2a38'}, bg);
el('stop', {offset:'100%','stop-color':'#0e1820'}, bg);
// -- 背景 --
el('rect', {x:0,y:0,width:1000,height:800,fill:'#050911'}, svg);
el('rect', {x:0,y:0,width:1000,height:800,fill:'url(#grid)'}, svg);
// -- 闪光层(断电效果) --
refs.flash = el('rect', {x:0,y:0,width:1000,height:800,fill:'#ff1744',opacity:0,'pointer-events':'none'}, svg);
// -- 立柱组 --
const colG = el('g', {id:'col'}, svg);
// 顶板+电机壳
el('rect', {x:COL_L,y:30,width:COL_R-COL_L,height:32,rx:3,fill:'#12202e',stroke:'#1a3a50','stroke-width':1.5}, colG);
el('rect', {x:430,y:8,width:140,height:28,rx:4,fill:'#0e1a28',stroke:'#00b4ff','stroke-width':1}, colG);
refs.motorLabel = el('text', {x:500,y:27,textAnchor:'middle',fill:'#00b4ff','font-size':'10','font-family':'Orbitron,sans-serif'}, colG);
refs.motorLabel.textContent = 'SERVO MOTOR';
// 左墙
el('rect', {x:COL_L,y:COL_TOP,width:COL_IN_L-COL_L,height:COL_BOT-COL_TOP,fill:'url(#cg)',stroke:'#1a3a52','stroke-width':1.2}, colG);
// 右墙
el('rect', {x:COL_IN_R,y:COL_TOP,width:COL_R-COL_IN_R,height:COL_BOT-COL_TOP,fill:'url(#cg)',stroke:'#1a3a52','stroke-width':1.2}, colG);
// 齿条 - 左
const rackLG = el('g', null, colG);
for (let y = COL_TOP + 8; y < COL_BOT - 8; y += RACK_GAP) {
el('rect', {x:COL_IN_L,y,width:RACK_DEPTH,height:RACK_H,fill:'#1e3040',stroke:'#2a4a5a','stroke-width':0.5,rx:1}, rackLG);
}
// 齿条 - 右
const rackRG = el('g', null, colG);
for (let y = COL_TOP + 8; y < COL_BOT - 8; y += RACK_GAP) {
el('rect', {x:COL_IN_R-RACK_DEPTH,y,width:RACK_DEPTH,height:RACK_H,fill:'#1e3040',stroke:'#2a4a5a','stroke-width':0.5,rx:1}, rackRG);
}
// 标注 - 齿条
el('text', {x:COL_IN_L+8,y:COL_BOT+14,fill:'#2a5a6a','font-size':'9','font-family':'Share Tech Mono,monospace'}, colG).textContent = '齿条槽';
el('text', {x:COL_IN_R-30,y:COL_BOT+14,fill:'#2a5a6a','font-size':'9','font-family':'Share Tech Mono,monospace'}, colG).textContent = '齿条槽';
// -- 底座 --
const baseG = el('g', {id:'base'}, svg);
el('rect', {x:COL_L-40,y:COL_BOT,width:COL_R-COL_L+80,height:30,rx:4,fill:'url(#bg)',stroke:'#1a3a50','stroke-width':1.2}, baseG);
// 轮子组
refs.wheelL = el('g', null, baseG);
drawWheel(refs.wheelL, 340, COL_BOT + 30 + 18, 18);
refs.wheelR = el('g', null, baseG);
drawWheel(refs.wheelR, 660, COL_BOT + 30 + 18, 18);
// 万向轮
el('circle', {cx:500,cy:COL_BOT+30+20,r:10,fill:'#0e1820',stroke:'#1a3a50','stroke-width':1}, baseG);
el('circle', {cx:500,cy:COL_BOT+30+20,r:4,fill:'#1a2a3a'}, baseG);
// 轮毂电机标注
el('text', {x:340,y:COL_BOT+74,textAnchor:'middle',fill:'#1a5a6a','font-size':'8','font-family':'Share Tech Mono,monospace'}, baseG).textContent = '轮毂电机';
el('text', {x:660,y:COL_BOT+74,textAnchor:'middle',fill:'#1a5a6a','font-size':'8','font-family':'Share Tech Mono,monospace'}, baseG).textContent = '轮毂电机';
// 地面线
refs.groundLine = el('line', {x1:100,y1:COL_BOT+68,x2:900,y2:COL_BOT+68,stroke:'#1a3040','stroke-width':1,'stroke-dasharray':'6,4'}, baseG);
// -- 丝杠(静态部分) --
const screwG = el('g', {id:'screw'}, svg);
el('rect', {x:SCREW_CX-SCREW_W/2,y:COL_TOP+5,width:SCREW_W,height:COL_BOT-COL_TOP-10,fill:'url(#sg)',stroke:'#2a4a5a','stroke-width':0.8,rx:2}, screwG);
// 丝杠螺纹线 - 旋转动画
refs.screwThreads = el('g', null, screwG);
for (let i = 0; i < 40; i++) {
const ty = COL_TOP + 10 + i * 16;
el('line', {
x1: SCREW_CX - SCREW_W/2 + 2,
y1: ty,
x2: SCREW_CX + SCREW_W/2 - 2,
y2: ty + 8,
stroke: '#3a6a7a',
'stroke-width': 1.2,
opacity: 0.5
}, refs.screwThreads);
}
// 丝杠标注
el('text', {x:SCREW_CX,y:COL_BOT-8,textAnchor:'middle',fill:'#2a6a7a','font-size':'9','font-family':'Share Tech Mono,monospace'}, screwG).textContent = '行星滚柱丝杠';
// -- 运动组件组 --
refs.moveG = el('g', {id:'moving'}, svg);
// 载物台/夹具
refs.platform = el('rect', {x:COL_IN_L+16,y:0,width:COL_IN_R-COL_IN_L-32,height:16,rx:3,fill:'#14303e',stroke:'#00b4ff','stroke-width':1.2}, refs.moveG);
// 夹具装饰
refs.fixture = el('g', null, refs.moveG);
el('rect', {x:COL_IN_L+40,y:-14,width:60,height:14,rx:2,fill:'#0e2030',stroke:'#1a5a6a','stroke-width':0.8}, refs.fixture);
el('rect', {x:COL_IN_R-100,y:-14,width:60,height:14,rx:2,fill:'#0e2030',stroke:'#1a5a6a','stroke-width':0.8}, refs.fixture);
refs.fixtureLabel = el('text', {x:500,y:-18,textAnchor:'middle',fill:'#3a8a9a','font-size':'9','font-family':'Share Tech Mono,monospace'}, refs.fixture);
refs.fixtureLabel.textContent = '夹具 / 载物台';
// 螺母
refs.nut = el('rect', {x:SCREW_CX-NUT_W/2,y:0,width:NUT_W,height:NUT_H,rx:3,fill:'#162a3a',stroke:'#4fc3f7','stroke-width':1}, refs.moveG);
// 滚柱(行星滚柱) - 在螺母内绕丝杠
refs.rollers = el('g', null, refs.moveG);
for (let i = 0; i < 8; i++) {
const angle = (i / 8) * Math.PI * 2;
const rx = SCREW_W/2 + 8;
el('ellipse', {
cx: SCREW_CX + Math.cos(angle) * rx,
cy: NUT_H/2 + Math.sin(angle) * 6,
rx: 4, ry: 6,
fill: '#2a5a6a', stroke: '#4fc3f7', 'stroke-width': 0.6,
class: 'roller'
}, refs.rollers);
}
// -- 楔形自锁块 --
refs.wedgeL = el('polygon', {points:'',fill:'url(#wg)',stroke:'#ff8f00','stroke-width':1.2}, refs.moveG);
refs.wedgeR = el('polygon', {points:'',fill:'url(#wg)',stroke:'#ff8f00','stroke-width':1.2}, refs.moveG);
// 楔块发光层
refs.wedgeLGlow = el('polygon', {points:'',fill:'none',stroke:'#ff6d00','stroke-width':2,opacity:0,filter:'url(#glow)'}, refs.moveG);
refs.wedgeRGlow = el('polygon', {points:'',fill:'none',stroke:'#ff6d00','stroke-width':2,opacity:0,filter:'url(#glow)'}, refs.moveG);
// -- 弹簧 --
refs.springL = el('polyline', {points:'',fill:'none',stroke:'#76ff03','stroke-width':1.8,'stroke-linecap':'round','stroke-linejoin':'round'}, refs.moveG);
refs.springR = el('polyline', {points:'',fill:'none',stroke:'#76ff03','stroke-width':1.8,'stroke-linecap':'round','stroke-linejoin':'round'}, refs.moveG);
// 弹簧标注
refs.springLabel = el('text', {x:0,y:0,fill:'#4a8a3a','font-size':'8','font-family':'Share Tech Mono,monospace',opacity:0.7}, refs.moveG);
refs.springLabel.textContent = '弹簧预紧';
// -- 电机轴连接 --
refs.motorShaft = el('line', {x1:SCREW_CX,y1:62,x2:SCREW_CX,y2:COL_TOP+5,stroke:'#4fc3f7','stroke-width':2,'stroke-dasharray':'4,3'}, svg);
// -- 力箭头组(自锁时显示) --
refs.forceG = el('g', {id:'forces',opacity:0}, svg);
// 下行力箭头
drawArrow(refs.forceG, 500, 0, 500, 60, '#ff1744', 2.5);
refs.forceDown = el('text', {x:518,y:40,fill:'#ff1744','font-size':'10','font-family':'Share Tech Mono,monospace','font-weight':'bold'}, refs.forceG);
refs.forceDown.textContent = '负载下行力';
// 左侧法向力
drawArrow(refs.forceG, 0, 0, 0, 0, '#00e676', 2);
refs.forceNormL = el('text', {x:0,y:0,fill:'#00e676','font-size':'9','font-family':'Share Tech Mono,monospace'}, refs.forceG);
refs.forceNormL.textContent = '法向力N';
// 左侧摩擦力
drawArrow(refs.forceG, 0, 0, 0, 0, '#ffeb3b', 2);
refs.forceFricL = el('text', {x:0,y:0,fill:'#ffeb3b','font-size':'9','font-family':'Share Tech Mono,monospace'}, refs.forceFricL);
refs.forceFricL.textContent = '摩擦力f';
// 自锁条件文字
refs.lockCond = el('text', {x:500,y:0,textAnchor:'middle',fill:'#ff8f00','font-size':'12','font-family':'Orbitron,sans-serif','font-weight':'bold',filter:'url(#glow-s)'}, refs.forceG);
refs.lockCond.textContent = 'α < ρ → 几何自锁';
// -- 楔角详图 --
refs.detailG = el('g', {id:'detail',opacity:0}, svg);
drawDetail();
// -- 运动轨迹 --
refs.trailG = el('g', {id:'trail',opacity:0}, svg);
// -- 状态指示灯 --
refs.indicator = el('circle', {cx:COL_R+30,y:50,r:6,fill:'#00e676',filter:'url(#glow)'}, svg);
refs.indText = el('text', {x:COL_R+42,y:54,fill:'#5a8a9a','font-size':'9','font-family':'Share Tech Mono,monospace'}, svg);
refs.indText.textContent = 'POWER';
// -- 导程标注 --
refs.leadMark = el('g', {opacity:0}, svg);
el('line', {x1:SCREW_CX+SCREW_W/2+6,y1:0,x2:SCREW_CX+SCREW_W/2+6,y2:0+16,stroke:'#00e5ff','stroke-width':1}, refs.leadMark);
el('line', {x1:SCREW_CX+SCREW_W/2+3,y1:0,x2:SCREW_CX+SCREW_W/2+9,y2:0,stroke:'#00e5ff','stroke-width':1}, refs.leadMark);
el('line', {x1:SCREW_CX+SCREW_W/2+3,y1:0+16,x2:SCREW_CX+SCREW_W/2+9,y2:0+16,stroke:'#00e5ff','stroke-width':1}, refs.leadMark);
refs.leadText = el('text', {x:SCREW_CX+SCREW_W/2+14,y:0+12,fill:'#00e5ff','font-size':'9','font-family':'Share Tech Mono,monospace'}, refs.leadMark);
refs.leadText.textContent = '导程5mm';
}
function drawWheel(parent, cx, cy, r) {
el('circle', {cx,cy,r,fill:'#0e1820',stroke:'#1a4a5a','stroke-width':1.5}, parent);
el('circle', {cx,cy,r:r*0.55,fill:'none',stroke:'#1a3a48','stroke-width':0.8}, parent);
// 辐条
for (let i = 0; i < 6; i++) {
const a = (i/6)*Math.PI*2;
el('line', {x1:cx+Math.cos(a)*r*0.2,y1:cy+Math.sin(a)*r*0.2,x2:cx+Math.cos(a)*r*0.85,y2:cy+Math.sin(a)*r*0.85,stroke:'#1a3a48','stroke-width':1}, parent);
}
el('circle', {cx,cy,r:3,fill:'#2a4a5a'}, parent);
}
function drawArrow(parent, x1, y1, x2, y2, color, w) {
const dx = x2-x1, dy = y2-y1;
const len = Math.sqrt(dx*dx+dy*dy);
const ux = dx/len, uy = dy/len;
const nx = -uy, ny = ux;
const hs = 8;
el('line', {x1,y1,x2:x2-ux*hs,y2:y2-uy*hs,stroke:color,'stroke-width':w}, parent);
el('polygon', {points:`${x2},${y2} ${x2-ux*hs+nx*hs*0.4},${y2-uy*hs+ny*hs*0.4} ${x2-ux*hs-nx*hs*0.4},${y2-uy*hs-ny*hs*0.4}`,fill:color}, parent);
}
function drawDetail() {
const g = refs.detailG;
// 背景框
el('rect', {x:730,y:470,width:240,height:190,rx:6,fill:'rgba(8,16,28,0.95)',stroke:'#ff8f00','stroke-width':1.2}, g);
el('text', {x:850,y:492,textAnchor:'middle',fill:'#ff8f00','font-size':'10','font-family':'Orbitron,sans-serif',letterSpacing:'1'}, g).textContent = 'WEDGE ANGLE DETAIL';
// 楔形放大图
const ox = 800, oy = 580;
// 楔块形状
const wa = CFG.wedgeAngle * Math.PI / 180;
const wLen = 100, wH = 50;
const narrowH = wH - 2 * wLen * Math.tan(wa);
el('polygon', {
points: `${ox},${oy-wH/2} ${ox},${oy+wH/2} ${ox-wLen},${oy+narrowH/2} ${ox-wLen},${oy-narrowH/2}`,
fill: 'url(#wg)', stroke: '#ff8f00', 'stroke-width': 1.5
}, g);
// 角度标注弧
const arcR = 40;
const arcEndX = ox - arcR;
const arcEndY = oy - narrowH/2 + (wH/2 - narrowH/2) * (arcR / wLen);
el('line', {x1:ox,y1:oy-wH/2,x2:ox-60,y2:oy-wH/2,stroke:'#aaa','stroke-width':0.6,'stroke-dasharray':'3,3'}, g);
el('path', {
d: `M ${ox-arcR} ${oy-wH/2} A ${arcR} ${arcR} 0 0 0 ${ox - arcR*Math.cos(wa*3)} ${oy-wH/2+arcR*Math.sin(wa*3)}`,
fill: 'none', stroke: '#ffeb3b', 'stroke-width': 1.5
}, g);
// 角度文字
refs.detailAngle = el('text', {x:ox-52,y:oy-wH/2+18,fill:'#ffeb3b','font-size':'11','font-family':'Share Tech Mono,monospace'}, g);
// 齿条面
el('rect', {x:ox-wLen-12,y:oy-narrowH/2-4,width:12,height:narrowH+8,fill:'#1e3040',stroke:'#2a5a6a','stroke-width':0.8}, g);
// 摩擦角对比
refs.detailComp = el('text', {x:ox-wLen-5,y:oy+narrowH/2+24,fill:'#5a8a9a','font-size':'9','font-family':'Share Tech Mono,monospace'}, g);
// 自锁判断
refs.detailJudge = el('text', {x:ox-wLen-5,y:oy+narrowH/2+40,fill:'#00e676','font-size':'10','font-family':'Orbitron,sans-serif','font-weight':'bold'}, g);
// 连接线到主视图
el('line', {x1:730,y1:560,x2:COL_IN_L+20,y2:platY()+30,stroke:'#ff8f00','stroke-width':0.6,'stroke-dasharray':'4,4',opacity:0.4}, g);
}
// ============ 弹簧路径生成 ============
function springPts(x1, y, x2, coils, amp) {
const pts = [];
const n = coils * 2;
const seg = (x2 - x1) / n;
for (let i = 0; i <= n; i++) {
const x = x1 + seg * i;
const yOff = (i === 0 || i === n) ? 0 : ((i % 2 === 1) ? amp : -amp);
pts.push(`${x},${y + yOff}`);
}
return pts.join(' ');
}
// ============ 更新SVG ============
function updateSVG() {
const py = platY();
const nutY = py + 16;
// 运动组件整体位置
refs.platform.setAttribute('y', py);
refs.nut.setAttribute('y', nutY);
// 夹具跟随
refs.fixture.setAttribute('transform', `translate(0,${py})`);
refs.fixtureLabel.setAttribute('y', py - 18);
// 滚柱旋转
refs.rollers.setAttribute('transform', `translate(0,${nutY}) rotate(${S.screwAng * 2},500,${NUT_H/2})`);
// 丝杠螺纹动画
const threadOffset = (S.screwAng * 0.5) % 16;
refs.screwThreads.setAttribute('transform', `translate(0,${threadOffset})`);
// 楔形块
const ext = S.wedgeExt * WEDGE_MAX_EXT;
const nutLX = SCREW_CX - NUT_W/2;
const nutRX = SCREW_CX + NUT_W/2;
const wH = 36;
const wcy = nutY + NUT_H / 2;
// 左楔块 - 梯形,窄端朝左(齿条方向)
const lNarrow = Math.max(4, wH - 2 * ext * Math.tan(CFG.wedgeAngle * Math.PI / 180));
const lPts = `${nutLX},${wcy-wH/2} ${nutLX},${wcy+wH/2} ${nutLX-ext},${wcy+lNarrow/2} ${nutLX-ext},${wcy-lNarrow/2}`;
refs.wedgeL.setAttribute('points', lPts);
refs.wedgeLGlow.setAttribute('points', lPts);
// 右楔块 - 镜像
const rNarrow = lNarrow;
const rPts = `${nutRX},${wcy-wH/2} ${nutRX},${wcy+wH/2} ${nutRX+ext},${wcy+rNarrow/2} ${nutRX+ext},${wcy-rNarrow/2}`;
refs.wedgeR.setAttribute('points', rPts);
refs.wedgeRGlow.setAttribute('points', rPts);
// 楔块发光
const glowOp = S.lockPulse * 0.7;
refs.wedgeLGlow.setAttribute('opacity', glowOp);
refs.wedgeRGlow.setAttribute('opacity', glowOp);
// 弹簧
const springAmp = 6 * (1 - S.wedgeExt * 0.4);
const spY = wcy;
const lSpringEnd = nutLX - ext + 4;
refs.springL.setAttribute('points', springPts(nutLX - 2, spY - 8, lSpringEnd, 5, springAmp));
refs.springR.setAttribute('points', springPts(nutRX + 2, spY - 8, nutRX + ext - 4, 5, springAmp));
// 弹簧标注
refs.springLabel.setAttribute('x', nutLX - ext/2 - 10);
refs.springLabel.setAttribute('y', wcy - 26);
// 闪光效果
refs.flash.setAttribute('opacity', S.flashA);
// 力箭头
if (S.wedgeExt > 0.8) {
refs.forceG.setAttribute('opacity', Math.min(1, (S.wedgeExt - 0.8) * 5));
// 下行力
const fdy = py - 10;
refs.forceG.children[0].setAttribute('x1', 500);
refs.forceG.children[0].setAttribute('y1', fdy - 50);
refs.forceG.children[0].setAttribute('x2', 500);
refs.forceG.children[0].setAttribute('y2', fdy);
refs.forceG.children[1].setAttribute('x', 500); // arrowhead
refs.forceG.children[1].setAttribute('y', fdy);
refs.forceDown.setAttribute('x', 516);
refs.forceDown.setAttribute('y', fdy - 20);
// 法向力 - 从左楔块接触点
const contactLX = nutLX - ext;
refs.forceG.children[3].setAttribute('x1', contactLX);
refs.forceG.children[3].setAttribute('y1', wcy);
refs.forceG.children[3].setAttribute('x2', contactLX + 30);
refs.forceG.children[3].setAttribute('y2', wcy - 15);
refs.forceNormL.setAttribute('x', contactLX + 32);
refs.forceNormL.setAttribute('y', wcy - 12);
// 摩擦力
refs.forceG.children[5].setAttribute('x1', contactLX);
refs.forceG.children[5].setAttribute('y1', wcy);
refs.forceG.children[5].setAttribute('x2', contactLX);
refs.forceG.children[5].setAttribute('y2', wcy - 30);
refs.forceFricL.setAttribute('x', contactLX - 40);
refs.forceFricL.setAttribute('y', wcy - 20);
// 自锁条件
refs.lockCond.setAttribute('y', py + 80);
} else {
refs.forceG.setAttribute('opacity', 0);
}
// 楔角详图
const detailOp = S.wedgeExt > 0.5 ? Math.min(1, (S.wedgeExt - 0.5) * 3) : 0;
refs.detailG.setAttribute('opacity', detailOp);
// 详图连接线跟随
if (refs.detailG.children.length > 0) {
const line = refs.detailG.querySelector('line');
if (line) {
line.setAttribute('y2', wcy);
}
}
refs.detailAngle.textContent = `α = ${CFG.wedgeAngle.toFixed(1)}°`;
refs.detailComp.textContent = `摩擦角 ρ ≈ ${CFG.frictionAngle}°`;
const isSelfLock = CFG.wedgeAngle < CFG.frictionAngle;
refs.detailJudge.textContent = isSelfLock ? 'α < ρ → ✓ 自锁成立' : 'α ≥ ρ → ✗ 无法自锁!';
refs.detailJudge.setAttribute('fill', isSelfLock ? '#00e676' : '#ff1744');
// 状态指示灯
if (S.powerOn) {
refs.indicator.setAttribute('fill', '#00e676');
refs.indText.textContent = 'POWER ON';
} else {
refs.indicator.setAttribute('fill', S.lockPulse > 0.3 ? '#ff6d00' : '#ff1744');
refs.indText.textContent = 'POWER OFF';
}
// 轮子旋转
if (S.mode === 'walking') {
refs.wheelL.setAttribute('transform', `rotate(${S.wheelAng},340,${COL_BOT+48})`);
refs.wheelR.setAttribute('transform', `rotate(${S.wheelAng},660,${COL_BOT+48})`);
} else {
refs.wheelL.setAttribute('transform', '');
refs.wheelR.setAttribute('transform', '');
}
// 导程标注
const ldOp = Math.abs(S.screwSpd) > 0.3 ? 1 : 0;
refs.leadMark.setAttribute('opacity', ldOp);
if (ldOp) {
const lmy = py + 16;
refs.leadMark.children[0].setAttribute('y1', lmy);
refs.leadMark.children[0].setAttribute('y2', lmy + 16);
refs.leadMark.children[1].setAttribute('y1', lmy);
refs.leadMark.children[2].setAttribute('y1', lmy + 16);
refs.leadMark.children[2].setAttribute('y2', lmy + 16);
refs.leadText.setAttribute('y', lmy + 12);
refs.leadText.textContent = `导程${CFG.lead}mm`;
}
// 电机轴颜色
refs.motorShaft.setAttribute('stroke', S.powerOn ? '#4fc3f7' : '#5a2a2a');
}
// ============ 更新状态面板 ============
function updateStatus() {
const st = $('sState'), sp = $('sPos'), sr = $('sRpm'), sl = $('sLock'), pw = $('sPow');
// 状态
st.className = 'sv';
if (S.mode === 'idle') { st.textContent = '待机'; st.classList.add('idle'); }
else if (S.mode === 'lifting') { st.textContent = S.liftDir > 0 ? '下降中' : '上升中'; st.classList.add('run'); }
else if (S.mode === 'walking') { st.textContent = '行走中'; st.classList.add('run'); }
else if (S.mode === 'emergency' || S.mode === 'locked') { st.textContent = '断电自锁'; st.classList.add('lock'); }
// 位置
sp.textContent = Math.round((1 - S.platNorm) * 100) + '%';
// 转速
const rpm = Math.abs(S.screwSpd) * 30;
sr.textContent = rpm.toFixed(0) + ' RPM';
// 自锁
sl.className = 'sv';
if (S.wedgeExt < 0.1) { sl.textContent = '待命'; sl.classList.add('idle'); }
else if (S.wedgeExt < 0.9) { sl.textContent = '弹出中...'; sl.classList.add('lock'); }
else { sl.textContent = '已锁定'; sl.classList.add(S.powerOn ? 'safe' : 'danger'); }
// 供电
pw.className = 'sv';
pw.textContent = S.powerOn ? '正常' : '断电';
pw.classList.add(S.powerOn ? 'safe' : 'danger');
// 按钮状态
$('bUp').disabled = !S.powerOn;
$('bDn').disabled = !S.powerOn;
$('bEm').disabled = !S.powerOn;
$('bRs').disabled = S.powerOn;
$('bWk').disabled = !S.powerOn || S.mode === 'lifting';
}
// ============ 动画循环 ============
let lastT = 0;
function loop(t) {
const dt = Math.min((t - lastT) / 1000, 0.05);
lastT = t;
// 升降
if (S.mode === 'lifting' && S.powerOn) {
const spd = 0.25 * (CFG.lead / 5) * dt;
S.targetNorm += S.liftDir * spd;
S.targetNorm = Math.max(0, Math.min(1, S.targetNorm));
S.screwSpd += (S.liftDir * 5 - S.screwSpd) * 0.1;
// 到达极限停止
if (S.targetNorm <= 0 || S.targetNorm >= 1) {
S.liftDir = 0;
S.mode = 'idle';
}
} else {
S.screwSpd *= 0.92;
}
S.platNorm += (S.targetNorm - S.platNorm) * 0.12;
S.screwAng += S.screwSpd;
// 楔块
const wd = S.targetWedge - S.wedgeExt;
if (Math.abs(wd) > 0.001) {
if (S.targetWedge > S.wedgeExt) {
// 弹出 - 快速,带微小超调
S.wedgeExt += wd * 0.18;
} else {
// 收回 - 平滑
S.wedgeExt += wd * 0.08;
}
}
S.wedgeExt = Math.max(0, Math.min(1.03, S.wedgeExt));
// 微小超调后回弹
if (S.wedgeExt > 1 && S.targetWedge === 1) {
S.wedgeExt += (1 - S.wedgeExt) * 0.1;
}
// 自锁脉冲
if (S.wedgeExt > 0.9) {
S.lockPulse = 0.4 + 0.6 * Math.abs(Math.sin(t * 0.004));
} else {
S.lockPulse *= 0.94;
}
// 行走
if (S.mode === 'walking') {
S.wheelAng += 3;
S.walkT += dt;
if (S.walkT > 4) {
S.mode = 'idle';
S.walkT = 0;
}
}
// 断电闪光衰减
S.flashA *= 0.92;
// 紧急状态转锁定
if (S.mode === 'emergency') {
S.emergencyT += dt;
if (S.emergencyT > 0.15) {
S.targetWedge = 1;
S.mode = 'locked';
}
}
updateSVG();
updateStatus();
requestAnimationFrame(loop);
}
// ============ 控制事件 ============
$('bUp').addEventListener('mousedown', () => { if (S.powerOn) { S.mode = 'lifting'; S.liftDir = -1; } });
$('bUp').addEventListener('mouseup', () => { if (S.mode === 'lifting' && S.liftDir === -1) { S.mode = 'idle'; S.liftDir = 0; } });
$('bUp').addEventListener('mouseleave', () => { if (S.mode === 'lifting' && S.liftDir === -1) { S.mode = 'idle'; S.liftDir = 0; } });
$('bDn').addEventListener('mousedown', () => { if (S.powerOn) { S.mode = 'lifting'; S.liftDir = 1; } });
$('bDn').addEventListener('mouseup', () => { if (S.mode === 'lifting' && S.liftDir === 1) { S.mode = 'idle'; S.liftDir = 0; } });
$('bDn').addEventListener('mouseleave', () => { if (S.mode === 'lifting' && S.liftDir === 1) { S.mode = 'idle'; S.liftDir = 0; } });
$('bEm').addEventListener('click', () => {
if (!S.powerOn) return;
S.powerOn = false;
S.mode = 'emergency';
S.liftDir = 0;
S.screwSpd = 0;
S.flashA = 0.6;
S.emergencyT = 0;
});
$('bRs').addEventListener('click', () => {
if (S.powerOn) return;
S.powerOn = true;
S.targetWedge = 0;
S.mode = 'idle';
S.lockPulse = 0;
});
$('bWk').addEventListener('click', () => {
if (!S.powerOn || S.mode === 'lifting') return;
S.mode = 'walking';
S.walkT = 0;
});
// 参数滑块
$('rLead').addEventListener('input', (e) => {
CFG.lead = parseFloat(e.target.value);
$('vLead').textContent = CFG.lead.toFixed(1) + ' mm/转';
});
$('rAngle').addEventListener('input', (e) => {
CFG.wedgeAngle = parseFloat(e.target.value);
$('vAngle').textContent = CFG.wedgeAngle.toFixed(1) + `° (摩擦角 ρ≈${CFG.frictionAngle}°)`;
// 重绘详图
const dg = refs.detailG;
while (dg.children.length > 1) dg.removeChild(dg.lastChild);
drawDetail();
});
// 触摸支持
['bUp','bDn'].forEach(id => {
const dir = id === 'bUp' ? -1 : 1;
$(id).addEventListener('touchstart', (e) => { e.preventDefault(); if (S.powerOn) { S.mode = 'lifting'; S.liftDir = dir; } });
$(id).addEventListener('touchend', () => { if (S.mode === 'lifting' && S.liftDir === dir) { S.mode = 'idle'; S.liftDir = 0; } });
});
// ============ 启动 ============
init();
requestAnimationFrame(loop);
</script>
</body>
</html>
实现说明
这是一个高保真的 SVG 原理动画,完整展示了"行星滚柱丝杠 + 楔形自锁块"纯电动升降模组的工作机理,核心设计围绕 TRIZ 的 最终理想解(IFR) 思想展开:
视觉设计
- 深蓝工程蓝图风格:暗色背景 + 网格底纹 + 青蓝色结构线条,营造技术制图氛围
- 色彩编码:结构体用冷蓝灰色,运动部件用青色,安全核心(楔形块)用琥珀橙色高亮,锁定状态用红色脉冲警示
- 力箭头用三色区分:红色(负载力)、绿色(法向力)、黄色(摩擦力)
IFR 核心表达
- 断电即安全:点击"模拟断电"后,红色闪光 → 电机停转 → 弹簧瞬间弹出楔块 → 楔块卡入齿条槽 → 几何自锁成立,整个过程无需任何电力
- 力矢量可视化:自锁激活后自动显示法向力/摩擦力箭头,直观证明"α < ρ → 几何自锁"
- 楔角详图面板:右下角实时展示楔角 α 与摩擦角 ρ 的比较,拖动滑块可验证自锁条件
交互功能
- 按住上升/下降:丝杠旋转动画 + 螺母平移 + 滚柱公转 + 导程标注闪现
- 模拟断电:完整的断电→弹出→锁定时序动画,楔块带超调回弹效果
- 恢复供电:楔块平滑收回,系统恢复待命
- 行走演示:轮毂电机驱动轮子旋转
- 参数滑块:实时调节丝杠导程(影响升降速度)和楔形角(验证自锁边界条件)
积分规则:第一轮对话扣减6分,后续每轮扣4分
等待动画代码生成...
