分享图
A
动画渲染工坊
就绪
<!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>保障。楔形角 α &lt; 摩擦角 ρ → 断电即自锁,<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 核心表达

  1. 断电即安全:点击"模拟断电"后,红色闪光 → 电机停转 → 弹簧瞬间弹出楔块 → 楔块卡入齿条槽 → 几何自锁成立,整个过程无需任何电力
  2. 力矢量可视化:自锁激活后自动显示法向力/摩擦力箭头,直观证明"α < ρ → 几何自锁"
  3. 楔角详图面板:右下角实时展示楔角 α 与摩擦角 ρ 的比较,拖动滑块可验证自锁条件

交互功能

  • 按住上升/下降:丝杠旋转动画 + 螺母平移 + 滚柱公转 + 导程标注闪现
  • 模拟断电:完整的断电→弹出→锁定时序动画,楔块带超调回弹效果
  • 恢复供电:楔块平滑收回,系统恢复待命
  • 行走演示:轮毂电机驱动轮子旋转
  • 参数滑块:实时调节丝杠导程(影响升降速度)和楔形角(验证自锁边界条件)
积分规则:第一轮对话扣减6分,后续每轮扣4分