分享图
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=Syne:wght@400;600;700;800&family=DM+Mono:wght@300;400;500&display=swap" rel="stylesheet">
<style>
:root{--bg:#070b12;--fg:#dfe6f0;--muted:#4a5670;--accent:#00ffa3;--accent2:#ff6b35;--hinge:#ff2d78;--card:#0c1018;--border:#162030;--force:#4db8ff}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--fg);font-family:'DM Mono',monospace;min-height:100vh;display:flex;flex-direction:column;align-items:center;overflow-x:hidden}
.wrap{width:100%;max-width:1440px;padding:16px 20px}
.hdr{text-align:center;margin-bottom:14px}
.hdr h1{font-family:'Syne',sans-serif;font-weight:800;font-size:clamp(1.3rem,3vw,2.1rem);letter-spacing:-.02em;line-height:1.2}
.hdr h1 em{font-style:normal;color:var(--accent)}
.hdr p{color:var(--muted);font-size:.78rem;margin-top:6px;max-width:740px;margin-inline:auto;line-height:1.65}
.svg-box{width:100%;background:var(--card);border:1px solid var(--border);border-radius:10px;overflow:hidden}
.svg-box svg{width:100%;height:auto;display:block}
.ctrls{display:flex;gap:16px;margin-top:14px;flex-wrap:wrap;justify-content:center}
.cg{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:12px 16px;min-width:200px;flex:1;max-width:280px}
.cg label{display:block;font-size:.68rem;color:var(--muted);margin-bottom:4px;text-transform:uppercase;letter-spacing:.12em}
.cg .val{font-family:'Syne',sans-serif;font-weight:700;font-size:1.25rem;color:var(--accent);margin-bottom:6px}
input[type=range]{-webkit-appearance:none;width:100%;height:3px;background:var(--border);border-radius:2px;outline:none}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:14px;height:14px;border-radius:50%;background:var(--accent);cursor:pointer;box-shadow:0 0 8px var(--accent)}
.btn-row{display:flex;gap:8px;align-items:flex-end;padding-bottom:4px}
.btn{font-family:'DM Mono',monospace;font-size:.75rem;padding:7px 14px;border:1px solid var(--border);background:var(--card);color:var(--fg);border-radius:5px;cursor:pointer;transition:.2s}
.btn:hover{border-color:var(--accent);color:var(--accent)}
.btn.on{background:var(--accent);color:var(--bg);border-color:var(--accent)}
.leg{display:flex;gap:16px;margin-top:12px;justify-content:center;flex-wrap:wrap}
.leg span{display:flex;align-items:center;gap:6px;font-size:.7rem;color:var(--muted)}
.leg i{width:9px;height:9px;border-radius:50%;display:inline-block}
@media(max-width:700px){.ctrls{flex-direction:column;align-items:center}.cg{max-width:100%}}
</style>
</head>
<body>
<div class="wrap">
  <div class="hdr">
    <h1>弹性蛇行机构 · <em>最终理想解</em>原理动画</h1>
    <p>偏心曲柄驱动弹性底盘铰链,以整车柔性扭动替代刚性转向——振幅与周期均可由螺钉微调,实现可调S型蛇行轨迹</p>
  </div>
  <div class="svg-box">
    <svg id="svg" viewBox="0 0 1200 720" xmlns="http://www.w3.org/2000/svg"></svg>
  </div>
  <div class="ctrls">
    <div class="cg"><label>偏心距 Eccentric Offset</label><div class="val" id="vOff">4.0 mm</div><input type="range" id="sOff" min="0" max="8" step="0.5" value="4"></div>
    <div class="cg"><label>铰链刚度 Hinge Stiffness</label><div class="val" id="vStf">0.50 N·m/rad</div><input type="range" id="sStf" min="0.1" max="2.0" step="0.05" value="0.5"></div>
    <div class="cg"><label>动画速度</label><div class="val" id="vSpd">1.0x</div><input type="range" id="sSpd" min="0.2" max="3" step="0.1" value="1"></div>
    <div class="cg btn-row"><button class="btn on" id="bPlay">播放</button><button class="btn" id="bRst">重置</button></div>
  </div>
  <div class="leg">
    <span><i style="background:#00ffa3"></i>曲柄-连杆驱动</span>
    <span><i style="background:#ff2d78"></i>弹性铰链(核心创新)</span>
    <span><i style="background:#ff6b35"></i>S型蛇行轨迹</span>
    <span><i style="background:#4db8ff"></i>侧向推力矢量</span>
  </div>
</div>

<script>
const NS='http://www.w3.org/2000/svg';
const svg=document.getElementById('svg');

/* ─── 状态 ─── */
let crankAngle=0, playing=true, spd=1;
let eccOff=4, stiff=0.5;
let lastTs=0;

/* ─── 工具 ─── */
function el(tag,attr,parent){
  const e=document.createElementNS(NS,tag);
  for(const k in attr) e.setAttribute(k,attr[k]);
  if(parent) parent.appendChild(e);
  return e;
}
function clr(c,a){return c.replace(')',`,${a})`).replace('rgb','rgba')}

/* ─── 计算参数 ─── */
function oscAmp(){return(eccOff/8)*(0.5/Math.max(stiff,0.1))}
function maxRearAngle(){return oscAmp()*0.42}  // 弧度,最大约24°
function trailAmpPx(){return oscAmp()*90}       // 俯视轨迹振幅像素
function crankVisR(){return 12+eccOff*2.5}      // 曲柄视觉半径

/* ─── 构建 SVG ─── */
function build(){
  svg.innerHTML='';

  /* defs */
  const defs=el('defs',{},svg);

  // 辉光滤镜
  const f1=el('filter',{id:'glow',x:'-80%',y:'-80%',width:'260%',height:'260%'},defs);
  el('feGaussianBlur',{stdDeviation:'5',result:'b'},f1);
  const fm1=el('feMerge',{},f1);el('feMergeNode',{in:'b'},fm1);el('feMergeNode',{in:'SourceGraphic'},fm1);

  const f2=el('filter',{id:'glowBig',x:'-120%',y:'-120%',width:'340%',height:'340%'},defs);
  el('feGaussianBlur',{stdDeviation:'10',result:'b'},f2);
  const fm2=el('feMerge',{},f2);el('feMergeNode',{in:'b'},fm2);el('feMergeNode',{in:'SourceGraphic'},fm2);

  const f3=el('filter',{id:'glowSm',x:'-60%',y:'-60%',width:'220%',height:'220%'},defs);
  el('feGaussianBlur',{stdDeviation:'3',result:'b'},f3);
  const fm3=el('feMerge',{},f3);el('feMergeNode',{in:'b'},fm3);el('feMergeNode',{in:'SourceGraphic'},fm3);

  // 渐变
  const g1=el('linearGradient',{id:'gTrail',x1:'0%',y1:'0%',x2:'100%',y2:'0%'},defs);
  el('stop',{offset:'0%','stop-color':'#ff6b35','stop-opacity':'0'},g1);
  el('stop',{offset:'70%','stop-color':'#ff6b35','stop-opacity':'0.5'},g1);
  el('stop',{offset:'100%','stop-color':'#ff6b35','stop-opacity':'0.85'},g1);

  const g2=el('linearGradient',{id:'gTraj',x1:'0%',y1:'0%',x2:'100%',y2:'0%'},defs);
  el('stop',{offset:'0%','stop-color':'#ff6b35','stop-opacity':'0.15'},g2);
  el('stop',{offset:'50%','stop-color':'#ff6b35','stop-opacity':'0.7'},g2);
  el('stop',{offset:'100%','stop-color':'#ff6b35','stop-opacity':'0.15'},g2);

  const g3=el('radialGradient',{id:'gHinge',cx:'50%',cy:'50%',r:'50%'},defs);
  el('stop',{offset:'0%','stop-color':'#ff2d78','stop-opacity':'0.6'},g3);
  el('stop',{offset:'100%','stop-color':'#ff2d78','stop-opacity':'0'},g3);

  // 箭头标记
  const m1=el('marker',{id:'arrForce',markerWidth:'8',markerHeight:'6',refX:'8',refY:'3',orient:'auto'},defs);
  el('path',{d:'M0,0 L8,3 L0,6 Z',fill:'#4db8ff'},m1);

  const m2=el('marker',{id:'arrAccent',markerWidth:'7',markerHeight:'5',refX:'7',refY:'2.5',orient:'auto'},defs);
  el('path',{d:'M0,0 L7,2.5 L0,5 Z',fill:'#00ffa3'},m2);

  /* ─── 背景网格 ─── */
  const bgG=el('g',{id:'bgGrid'},svg);
  for(let x=0;x<=1200;x+=40) el('line',{x1:x,y1:0,x2:x,y2:720,stroke:'#0e1520','stroke-width':'0.5'},bgG);
  for(let y=0;y<=720;y+=40) el('line',{x1:0,y1:y,x2:1200,y2:y,stroke:'#0e1520','stroke-width':'0.5'},bgG);

  /* ─── 面板分隔线 ─── */
  el('line',{x1:600,y1:20,x2:600,y2:440,stroke:'#1a2838','stroke-width':'1','stroke-dasharray':'4,4'},svg);
  el('line',{x1:20,y1:455,x2:1180,y2:455,stroke:'#1a2838','stroke-width':'1','stroke-dasharray':'4,4'},svg);

  /* ─── 面板标签 ─── */
  const lb1=el('text',{x:300,y:28,fill:'#5a6a82','font-family':"'Syne',sans-serif",'font-size':'13','font-weight':'600','text-anchor':'middle','letter-spacing':'0.08em'},svg);
  lb1.textContent='俯视蛇行动作';

  const lb2=el('text',{x:900,y:28,fill:'#5a6a82','font-family':"'Syne',sans-serif",'font-size':'13','font-weight':'600','text-anchor':'middle','letter-spacing':'0.08em'},svg);
  lb2.textContent='曲柄-连杆-铰链 机构原理';

  const lb3=el('text',{x:600,y:472,fill:'#5a6a82','font-family':"'Syne',sans-serif",'font-size':'13','font-weight':'600','text-anchor':'middle','letter-spacing':'0.08em'},svg);
  lb3.textContent='S型轨迹预览 · 参数敏感性';

  /* ─── 左面板:俯视蛇行 ─── */
  const topG=el('g',{id:'topView'},svg);

  // 跑道线
  el('line',{x1:40,y1:140,x2:570,y2:140,stroke:'#162030','stroke-width':'1','stroke-dasharray':'6,6'},topG);
  el('line',{x1:40,y1:340,x2:570,y2:340,stroke:'#162030','stroke-width':'1','stroke-dasharray':'6,6'},topG);

  // 锥桶(静态,会随时间滚动)
  const coneG=el('g',{id:'cones'},topG);

  // 轨迹路径
  const trailP=el('polyline',{id:'trailPath',fill:'none',stroke:'url(#gTrail)','stroke-width':'3','stroke-linecap':'round','stroke-linejoin':'round'},topG);

  // 力矢量组
  const forceG=el('g',{id:'forceArrows'},topG);

  // 车辆组
  const vehG=el('g',{id:'vehicle'},topG);

  // 前段底盘
  const frontCh=el('rect',{x:-70,y:-12,width:70,height:24,rx:6,fill:'#182838',stroke:'#2a4060','stroke-width':'1.5'},vehG);

  // 前轮(万向轮)
  el('circle',{cx:40,cy:0,r:5,fill:'#3a5068',stroke:'#5a7090','stroke-width':'1'},vehG);

  // 弹性铰链辉光
  el('circle',{cx:0,cy:0,r:18,fill:'url(#gHinge)'},vehG);

  // 后段底盘组(可旋转)
  const rearG=el('g',{id:'rearChassis'},vehG);

  const rearCh=el('rect',{x:0,y:-12,width:75,height:24,rx:5,fill:'#182838',stroke:'#2a4060','stroke-width':'1.5'},rearG);
  // 后轮
  el('rect',{x:55,y:-18,width:14,height:6,rx:2,fill:'#3a5068',stroke:'#5a7090','stroke-width':'1'},rearG);
  el('rect',{x:55,y:12,width:14,height:6,rx:2,fill:'#3a5068',stroke:'#5a7090','stroke-width':'1'},rearG);

  // 铰链圆点
  const hingeC=el('circle',{cx:0,cy:0,r:5,fill:'#ff2d78',filter:'url(#glow)'},vehG);

  // 弹簧标记(在铰链处)
  const springP=el('path',{id:'spring',fill:'none',stroke:'#ff2d78','stroke-width':'1.5','stroke-linecap':'round'},vehG);

  // 重物与绳
  const weightG=el('g',{id:'weightGroup'},vehG);
  el('rect',{x:-20,y:-35,width:16,height:14,rx:2,fill:'#2a3a52',stroke:'#4a6a8a','stroke-width':'1'},weightG);
  el('line',{x1:-12,y1:-21,x2:-12,y2:-12,stroke:'#4a6a8a','stroke-width':'1'},weightG);
  const wArrow=el('path',{d:'M-12,-40 L-12,-48 M-15,-44 L-12,-48 L-9,-44',fill:'none',stroke:'#00ffa3','stroke-width':'1.2'},weightG);

  /* ─── 右面板:机构原理 ─── */
  const mechG=el('g',{id:'mechView'},svg);

  // 主轴中心位置
  const SX=870,SY=160;

  // 底盘基准线
  el('line',{x1:720,y1:310,x2:1100,y2:310,stroke:'#0e1825','stroke-width':'1','stroke-dasharray':'3,5'},mechG);

  // 前段底盘(固定)
  const mFront=el('rect',{x:890,y:294,width:150,height:32,rx:6,fill:'#182838',stroke:'#2a4060','stroke-width':'1.5'},mechG);
  el('text',{x:965,y:314,fill:'#5a7a9a','font-size':'10','text-anchor':'middle','font-family':"'DM Mono',monospace"},mechG).textContent='前段底盘';

  // 铰链辉光
  el('circle',{cx:890,cy:310,r:22,fill:'url(#gHinge)'},mechG);

  // 后段底盘组(可旋转)
  const mRearG=el('g',{id:'mRearChassis'},mechG);
  const mRear=el('rect',{x:810,y:294,width:80,height:32,rx:5,fill:'#182838',stroke:'#2a4060','stroke-width':'1.5'},mRearG);
  el('text',{x:850,y:314,fill:'#5a7a9a','font-size':'10','text-anchor':'middle','font-family':"'DM Mono',monospace"},mRearG).textContent='后段';

  // 后轮(机构视图)
  el('rect',{x:812,y:286,width:12,height:6,rx:1.5,fill:'#3a5068',stroke:'#5a7090','stroke-width':'0.8'},mRearG);
  el('rect',{x:812,y:328,width:12,height:6,rx:1.5,fill:'#3a5068',stroke:'#5a7090','stroke-width':'0.8'},mRearG);

  // 铰链圆点
  const mHinge=el('circle',{cx:890,cy:310,r:6,fill:'#ff2d78',filter:'url(#glow)'},mechG);

  // 弹簧符号
  const mSpring=el('path',{id:'mSpringPath',fill:'none',stroke:'#ff2d78','stroke-width':'1.8','stroke-linecap':'round'},mechG);

  // 主轴
  const shaftC=el('circle',{cx:SX,cy:SY,r:14,fill:'none',stroke:'#2a4060','stroke-width':'1.5'},mechG);
  const shaftDot=el('circle',{cx:SX,cy:SY,r:3,fill:'#00ffa3'},mechG);
  el('text',{x:SX,y:SY-22,fill:'#5a7a9a','font-size':'9','text-anchor':'middle','font-family':"'DM Mono',monospace"},mechG).textContent='主轴';

  // 曲柄臂
  const crankArm=el('line',{id:'crankArm',x1:SX,y1:SY,x2:SX,y2:SY,stroke:'#00ffa3','stroke-width':'2.5','stroke-linecap':'round'},mechG);

  // 曲柄销
  const crankPin=el('circle',{id:'crankPin',cx:SX,cy:SY,r:4.5,fill:'#00ffa3',filter:'url(#glowSm)'},mechG);

  // 曲柄轨迹圆
  const crankOrbit=el('circle',{id:'crankOrbit',cx:SX,cy:SY,r:crankVisR(),fill:'none',stroke:'#00ffa3','stroke-width':'0.6','stroke-dasharray':'3,3',opacity:'0.4'},mechG);

  // 连杆
  const connRod=el('line',{id:'connRod',x1:SX,y1:SY,x2:SX,y2:SY,stroke:'#00ffa3','stroke-width':'2','stroke-linecap':'round',opacity:'0.85'},mechG);

  // 连杆连接点(后段尾部)
  const rodJoint=el('circle',{id:'rodJoint',cx:820,cy:310,r:3.5,fill:'#00ffa3',filter:'url(#glowSm)'},mechG);

  // 重物
  const mWeight=el('g',{id:'mWeight'},mechG);
  el('rect',{x:SX-10,y:SY-60,width:20,height:20,rx:3,fill:'#2a3a52',stroke:'#4a6a8a','stroke-width':'1'},mWeight);
  el('line',{x1:SX,y1:SY-40,x2:SX,y2:SY-14,stroke:'#4a6a8a','stroke-width':'1.2'},mWeight);
  el('text',{x:SX,y:SY-66,fill:'#6a8aaa','font-size':'9','text-anchor':'middle','font-family':"'DM Mono',monospace"},mWeight).textContent='重物 ↓';

  // 偏心距标注
  const eccLabel=el('g',{id:'eccLabel'},mechG);
  const eccLine1=el('line',{id:'eccLine1',x1:SX,y1:SY,x2:SX,y2:SY,stroke:'#ffcc00','stroke-width':'0.8','stroke-dasharray':'2,2'},eccLabel);
  const eccLine2=el('line',{id:'eccLine2',x1:SX,y1:SY,x2:SX,y2:SY,stroke:'#ffcc00','stroke-width':'0.8','stroke-dasharray':'2,2'},eccLabel);
  const eccText=el('text',{id:'eccText',x:SX,y:SY,fill:'#ffcc00','font-size':'10','font-family':"'DM Mono',monospace"},eccLabel);

  // 角度弧线
  const angleArc=el('path',{id:'angleArc',fill:'none',stroke:'#ff2d78','stroke-width':'1.5','stroke-dasharray':'3,2',opacity:'0.7'},mechG);
  const angleText=el('text',{id:'angleText',x:890,y:370,fill:'#ff2d78','font-size':'11','text-anchor':'middle','font-family':"'DM Mono',monospace"},mechG);

  // 侧向力箭头(机构视图)
  const mForceArr=el('line',{id:'mForceArr',x1:820,y1:310,x2:820,y2:310,stroke:'#4db8ff','stroke-width':'2','marker-end':'url(#arrForce)'},mechG);
  const mForceText=el('text',{id:'mForceText',x:820,y:310,fill:'#4db8ff','font-size':'9','font-family':"'DM Mono',monospace"},mechG);

  // 旋转方向箭头
  const rotArc=el('path',{id:'rotArc',fill:'none',stroke:'#00ffa3','stroke-width':'1',opacity:'0.5'},mechG);

  /* ─── 底部:S轨迹预览 ─── */
  const trajG=el('g',{id:'trajView'},svg);

  // 中心线
  el('line',{x1:60,y1:600,x2:1140,y2:600,stroke:'#162030','stroke-width':'1','stroke-dasharray':'4,6'},trajG);

  // 锥桶位置
  const trajCones=el('g',{},trajG);

  // S轨迹路径
  const trajPath=el('path',{id:'trajPath',fill:'none',stroke:'url(#gTraj)','stroke-width':'3','stroke-linecap':'round'},trajG);

  // 振幅标注
  const ampLine1=el('line',{id:'ampLine1',x1:0,y1:0,x2:0,y2:0,stroke:'#ff6b35','stroke-width':'0.8','stroke-dasharray':'3,3',opacity:'0.6'},trajG);
  const ampLine2=el('line',{id:'ampLine2',x1:0,y1:0,x2:0,y2:0,stroke:'#ff6b35','stroke-width':'0.8','stroke-dasharray':'3,3',opacity:'0.6'},trajG);
  const ampText=el('text',{id:'ampText',x:0,y:0,fill:'#ff6b35','font-size':'11','text-anchor':'middle','font-family':"'DM Mono',monospace"},trajG);

  // 参数信息文本
  const infoText=el('text',{id:'infoText',x:1130,y:695,fill:'#3a5068','font-size':'10','text-anchor':'end','font-family':"'DM Mono',monospace"},trajG);

  // 动态车辆指示点
  const trajDot=el('circle',{id:'trajDot',cx:60,cy:600,r:4,fill:'#ff6b35',filter:'url(#glowSm)'},trajG);
}

/* ─── 弹簧路径生成 ─── */
function springPath(x1,y1,x2,y2,coils,stiffness){
  const dx=x2-x1,dy=y2-y1;
  const len=Math.sqrt(dx*dx+dy*dy);
  if(len<1)return`M${x1},${y1}`;
  const nx=-dy/len,ny=dx/len;
  const amp=Math.max(3, 12/Math.max(stiffness,0.2));
  let d=`M${x1},${y1}`;
  const steps=coils*2;
  for(let i=1;i<=steps;i++){
    const t=i/(steps+1);
    const px=x1+dx*t, py=y1+dy*t;
    const side=(i%2===0?1:-1)*amp;
    d+=` L${px+nx*side},${py+ny*side}`;
  }
  d+=` L${x2},${y2}`;
  return d;
}

/* ─── 阶跃锥桶 ─── */
let conePositions=[];
function initCones(){
  conePositions=[];
  for(let i=0;i<8;i++){
    conePositions.push({worldX:i*160+80, side:(i%2===0)?-1:1});
  }
}
initCones();

/* ─── 动画帧 ─── */
function animate(ts){
  if(!lastTs) lastTs=ts;
  const dt=Math.min((ts-lastTs)/1000, 0.05);
  lastTs=ts;

  if(playing){
    crankAngle+=dt*spd*2.5;
  }

  const rearA=maxRearAngle()*Math.sin(crankAngle);
  const tAmp=trailAmpPx();

  /* ── 俯视车辆 ── */
  const vx=300, vy=240+tAmp*Math.sin(crankAngle);
  const heading=Math.atan2(tAmp*Math.cos(crankAngle)*2.5*spd, 80)*0.3;

  const veh=document.getElementById('vehicle');
  veh.setAttribute('transform',`translate(${vx},${vy}) rotate(${heading*180/Math.PI})`);

  const rear=document.getElementById('rearChassis');
  rear.setAttribute('transform',`rotate(${rearA*180/Math.PI})`);

  // 弹簧
  const sp=document.getElementById('spring');
  const sAmp=Math.max(2,8/Math.max(stiff,0.2));
  sp.setAttribute('d',springPath(0,-10,0,10,3,stiff));
  sp.setAttribute('transform',`scale(${1+Math.abs(rearA)*2},1)`);

  // 轨迹
  let pts='';
  const tLen=280;
  for(let i=0;i<=tLen;i++){
    const t=crankAngle-(i*0.025);
    const px=vx-i*1.6;
    const py=240+tAmp*Math.sin(t);
    pts+=`${px},${py} `;
  }
  document.getElementById('trailPath').setAttribute('points',pts.trim());

  // 锥桶
  const coneG=document.getElementById('cones');
  coneG.innerHTML='';
  const scrollX=(crankAngle*40*spd)%160;
  for(let i=0;i<8;i++){
    const cx=580-((i*160+scrollX)%1280);
    if(cx<30||cx>580) continue;
    const cy=conePositions[i].side>0?350:130;
    const tri=`M${cx},${cy} L${cx-6},${cy+12} L${cx+6},${cy+12} Z`;
    el('path',{d:tri,fill:'#ff6b35',opacity:'0.7'},coneG);
    el('circle',{cx,cy:y=cy+14,r:2,fill:'#ff6b35',opacity:'0.3'},coneG);
  }

  // 侧向力箭头
  const fG=document.getElementById('forceArrows');
  fG.innerHTML='';
  const fDir=rearA>0?1:-1;
  const fMag=Math.abs(rearA)*120;
  if(fMag>5){
    const rwx=vx+55*Math.cos(heading+rearA);
    const rwy=vy+55*Math.sin(heading+rearA);
    el('line',{x1:rwx,y1:rwy,x2:rwx+fDir*fMag*0.5,y2:rwy,'marker-end':'url(#arrForce)',stroke:'#4db8ff','stroke-width':'2',opacity:'0.7'},fG);
  }

  /* ── 机构视图 ── */
  const SX=870,SY=160;
  const cR=crankVisR();
  const pinX=SX+cR*Math.cos(crankAngle);
  const pinY=SY+cR*Math.sin(crankAngle);

  document.getElementById('crankArm').setAttribute('x2',pinX);
  document.getElementById('crankArm').setAttribute('y2',pinY);
  document.getElementById('crankPin').setAttribute('cx',pinX);
  document.getElementById('crankPin').setAttribute('cy',pinY);
  document.getElementById('crankOrbit').setAttribute('r',cR);

  // 后段底盘旋转
  const mRearG=document.getElementById('mRearChassis');
  mRearG.setAttribute('transform',`rotate(${rearA*180/Math.PI},890,310)`);

  // 连杆:从曲柄销到后段尾部
  const tailBaseX=820, tailBaseY=310;
  const cosR=Math.cos(rearA), sinR=Math.sin(rearA);
  const tailX=890+(tailBaseX-890)*cosR-(tailBaseY-310)*sinR;
  const tailY=310+(tailBaseX-890)*sinR+(tailBaseY-310)*cosR;

  document.getElementById('connRod').setAttribute('x1',pinX);
  document.getElementById('connRod').setAttribute('y1',pinY);
  document.getElementById('connRod').setAttribute('x2',tailX);
  document.getElementById('connRod').setAttribute('y2',tailY);
  document.getElementById('rodJoint').setAttribute('cx',tailX);
  document.getElementById('rodJoint').setAttribute('cy',tailY);

  // 机构弹簧
  const mSp=document.getElementById('mSpringPath');
  mSp.setAttribute('d',springPath(890,294,890,280,3,stiff));

  // 偏心距标注
  if(eccOff>0){
    document.getElementById('eccLine1').setAttribute('x1',SX);
    document.getElementById('eccLine1').setAttribute('y1',SY);
    document.getElementById('eccLine1').setAttribute('x2',pinX);
    document.getElementById('eccLine1').setAttribute('y2',pinY);
    const mx=(SX+pinX)/2, my=(SY+pinY)/2-12;
    document.getElementById('eccText').setAttribute('x',mx);
    document.getElementById('eccText').setAttribute('y',my);
    document.getElementById('eccText').textContent=`e=${eccOff.toFixed(1)}`;
  }

  // 角度弧线
  if(Math.abs(rearA)>0.01){
    const arcR=40;
    const startA=Math.PI;
    const endA=Math.PI+rearA;
    const x1=890+arcR*Math.cos(startA), y1=310+arcR*Math.sin(startA);
    const x2=890+arcR*Math.cos(endA), y2=310+arcR*Math.sin(endA);
    const largeArc=Math.abs(rearA)>Math.PI?1:0;
    const sweep=rearA>0?1:0;
    document.getElementById('angleArc').setAttribute('d',
      `M${x1},${y1} A${arcR},${arcR} 0 ${largeArc},${sweep} ${x2},${y2}`);
    document.getElementById('angleText').textContent=`θ=${(rearA*180/Math.PI).toFixed(1)}°`;
  } else {
    document.getElementById('angleArc').setAttribute('d','');
    document.getElementById('angleText').textContent='';
  }

  // 侧向力箭头(机构视图)
  const flX=tailX, flY=tailY;
  const fDirM=rearA>0?1:-1;
  const fMagM=Math.abs(rearA)*80;
  if(fMagM>3){
    document.getElementById('mForceArr').setAttribute('x1',flX);
    document.getElementById('mForceArr').setAttribute('y1',flY);
    document.getElementById('mForceArr').setAttribute('x2',flX);
    document.getElementById('mForceArr').setAttribute('y2',flY+fDirM*fMagM);
    document.getElementById('mForceText').setAttribute('x',flX+14);
    document.getElementById('mForceText').setAttribute('y',flY+fDirM*fMagM*0.5);
    document.getElementById('mForceText').textContent='F侧';
  } else {
    document.getElementById('mForceArr').setAttribute('x1',0);
    document.getElementById('mForceArr').setAttribute('x2',0);
    document.getElementById('mForceText').textContent='';
  }

  // 旋转方向弧线
  const rotR=24;
  const rotStart=crankAngle-0.3;
  const rotEnd=crankAngle+0.8;
  const rx1=SX+rotR*Math.cos(rotStart), ry1=SY+rotR*Math.sin(rotStart);
  const rx2=SX+rotR*Math.cos(rotEnd), ry2=SY+rotR*Math.sin(rotEnd);
  document.getElementById('rotArc').setAttribute('d',
    `M${rx1},${ry1} A${rotR},${rotR} 0 0,1 ${rx2},${ry2}`);

  /* ── S轨迹预览 ── */
  const tCenterY=600;
  const tStartX=80, tEndX=1120;
  let d=`M${tStartX},${tCenterY}`;
  for(let x=tStartX;x<=tEndX;x+=2){
    const phase=(x-tStartX)/(tEndX-tStartX)*5*Math.PI;
    const y=tCenterY+tAmp*Math.sin(phase);
    d+=` L${x},${y}`;
  }
  document.getElementById('trajPath').setAttribute('d',d);

  // 轨迹上的移动点
  const dotPhase=((crankAngle*0.8)%(5*Math.PI));
  const dotX=tStartX+dotPhase/(5*Math.PI)*(tEndX-tStartX);
  const dotY=tCenterY+tAmp*Math.sin(dotPhase);
  document.getElementById('trajDot').setAttribute('cx',dotX);
  document.getElementById('trajDot').setAttribute('cy',dotY);

  // 振幅标注线
  const ampX=tStartX+(tEndX-tStartX)*0.12;
  document.getElementById('ampLine1').setAttribute('x1',ampX);
  document.getElementById('ampLine1').setAttribute('y1',tCenterY);
  document.getElementById('ampLine1').setAttribute('x2',ampX);
  document.getElementById('ampLine1').setAttribute('y2',tCenterY-tAmp);
  document.getElementById('ampLine2').setAttribute('x1',ampX-8);
  document.getElementById('ampLine2').setAttribute('y1',tCenterY);
  document.getElementById('ampLine2').setAttribute('x2',ampX+8);
  document.getElementById('ampLine2').setAttribute('y2',tCenterY);
  document.getElementById('ampText').setAttribute('x',ampX+18);
  document.getElementById('ampText').setAttribute('y',tCenterY-tAmp/2+4);
  document.getElementById('ampText').textContent=`A=${tAmp.toFixed(0)}px`;

  // 信息文本
  document.getElementById('infoText').textContent=
    `偏心距 ${eccOff.toFixed(1)}mm | 刚度 ${stiff.toFixed(2)} N·m/rad | 振幅系数 ${oscAmp().toFixed(2)}`;

  // 锥桶(轨迹视图)
  const tcG=svg.querySelector('#trajView > g');
  if(tcG) tcG.innerHTML='';
  for(let i=0;i<5;i++){
    const cx=tStartX+(tEndX-tStartX)*(i+0.5)/5;
    const side=i%2===0?-1:1;
    const cy=tCenterY+side*(tAmp+35);
    if(tcG){
      el('path',{d:`M${cx},${cy-8} L${cx-5},${cy+4} L${cx+5},${cy+4} Z`,fill:'#ff6b35',opacity:'0.4'},tcG);
    }
  }

  requestAnimationFrame(animate);
}

/* ─── 控件绑定 ─── */
document.getElementById('sOff').addEventListener('input',function(){
  eccOff=parseFloat(this.value);
  document.getElementById('vOff').textContent=eccOff.toFixed(1)+' mm';
});
document.getElementById('sStf').addEventListener('input',function(){
  stiff=parseFloat(this.value);
  document.getElementById('vStf').textContent=stiff.toFixed(2)+' N·m/rad';
});
document.getElementById('sSpd').addEventListener('input',function(){
  spd=parseFloat(this.value);
  document.getElementById('vSpd').textContent=spd.toFixed(1)+'x';
});
document.getElementById('bPlay').addEventListener('click',function(){
  playing=!playing;
  this.textContent=playing?'播放':'暂停';
  this.classList.toggle('on',playing);
});
document.getElementById('bRst').addEventListener('click',function(){
  crankAngle=0;
  lastTs=0;
});

/* ─── 启动 ─── */
build();
requestAnimationFrame(animate);
</script>
</body>
</html>

这个动画实现了以下核心要素:

IFR 理想解聚焦展示

  • 直接展示弹性蛇行机构在理想工作状态下的运作,无前后对比。整车柔性扭动替代刚性转向的核心矛盾消解过程一目了然。

三个联动视图

  1. 俯视蛇行动作(左面板):车辆在跑道上蛇行绕锥,底盘铰链处可见柔性扭动,橙色渐变轨迹清晰呈现S型路径,蓝色力矢量箭头指示后轮侧向推力方向。
  2. 曲柄-连杆-铰链机构原理(右面板):主轴旋转 → 偏心曲柄推动连杆 → 后段底盘绕铰链摆动,完整传动链实时联动。铰链以品红色辉光+弹簧符号双重标示为"核心创新点",偏心距用黄色标注随曲柄销动态移动。
  3. S型轨迹预览(底部):参数变化即时反映到轨迹振幅上,振幅标注线直观显示当前幅值。

交互控制

  • 偏心距滑块(0~8mm):增大偏心距 → 曲柄圆变大 → 摆角增加 → S轨迹振幅增大
  • 铰链刚度滑块(0.10~2.00 N·m/rad):增大刚度 → 弹簧变硬 → 摆角减小 → S轨迹振幅减小
  • 速度与播放控制:可暂停观察机构细节

视觉引导设计

  • 弹性铰链始终以品红色辉光标记(#ff2d78 + glow滤镜),引导视线聚焦破除矛盾的关键
  • 弹簧振幅随刚度参数实时变化(刚度大→弹簧扁,刚度小→弹簧振幅大)
  • 角度弧线与度数标注让铰链摆角量化可读
  • 侧向力箭头仅在有实质摆角时出现,强化"甩尾产生侧推力"的因果链
积分规则:第一轮对话扣减6分,后续每轮扣4分