分享图
动画工坊
引擎就绪
<!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=Rajdhani:wght@400;600;700;900&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
<style>
  :root {
    --bg: #060b14;
    --panel: #0c1220;
    --border: #1a2744;
    --fg: #e2e8f0;
    --muted: #6b7fa3;
    --accent: #f0a500;
    --accent2: #ff4d4d;
    --teal: #2dd4bf;
    --copper: #d4884a;
    --steel: #6889aa;
    --lock-green: #22c55e;
  }
  *, *::before, *::after { margin:0; padding:0; box-sizing:border-box; }
  body {
    background: var(--bg);
    color: var(--fg);
    font-family: 'Noto Sans SC', sans-serif;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    overflow-x: hidden;
  }
  .page-title {
    font-family: 'Rajdhani', sans-serif;
    font-weight: 900;
    font-size: clamp(1.3rem, 3vw, 2rem);
    letter-spacing: 0.08em;
    color: var(--accent);
    text-transform: uppercase;
    margin: 24px 0 6px;
    text-align: center;
  }
  .page-sub {
    color: var(--muted);
    font-size: 0.88rem;
    margin-bottom: 14px;
    text-align: center;
    max-width: 680px;
    line-height: 1.6;
  }
  .svg-wrap {
    width: 96vw;
    max-width: 1200px;
    background: linear-gradient(145deg, #080e1c 0%, #0a1222 100%);
    border: 1px solid var(--border);
    border-radius: 14px;
    overflow: hidden;
    box-shadow: 0 0 60px rgba(240,165,0,0.06), 0 2px 20px rgba(0,0,0,0.5);
  }
  svg { display:block; width:100%; height:auto; }

  /* 控制面板 */
  .controls {
    width: 96vw;
    max-width: 1200px;
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    margin: 16px 0 32px;
    align-items: center;
    justify-content: center;
  }
  .ctrl-card {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 14px 22px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 200px;
  }
  .ctrl-label {
    font-size: 0.75rem;
    color: var(--muted);
    font-family: 'Rajdhani', sans-serif;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
  }
  .ctrl-value {
    font-family: 'Rajdhani', sans-serif;
    font-weight: 700;
    font-size: 1.1rem;
    color: var(--accent);
  }
  input[type=range] {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    height: 6px;
    border-radius: 3px;
    background: var(--border);
    outline: none;
  }
  input[type=range]::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 18px; height: 18px;
    border-radius: 50%;
    background: var(--accent);
    cursor: pointer;
    border: 2px solid var(--bg);
  }
  .btn {
    font-family: 'Rajdhani', sans-serif;
    font-weight: 700;
    font-size: 0.85rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    padding: 10px 24px;
    border: 1px solid var(--accent);
    background: transparent;
    color: var(--accent);
    border-radius: 6px;
    cursor: pointer;
    transition: all 0.2s;
  }
  .btn:hover { background: var(--accent); color: var(--bg); }
  .btn.active { background: var(--accent); color: var(--bg); }

  @media (prefers-reduced-motion: reduce) {
    svg * { animation: none !important; transition: none !important; }
  }
</style>
</head>
<body>

<div class="page-title">Floating Worm Shaft &mdash; Cone Wedge Hard-Lock</div>
<p class="page-sub">反向驱动力 → 轴向推力 → 锥面楔入 → 径向胀开抱死壳体 → 零能源硬锁定。有害力即锁定力,正是 IFR 最终理想解。</p>

<div class="svg-wrap">
<svg id="mech" viewBox="0 0 1200 700" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <!-- 渐变 -->
    <linearGradient id="gHousing" x1="0" y1="0" x2="0" y2="1">
      <stop offset="0%" stop-color="#1e2d44"/>
      <stop offset="100%" stop-color="#0f1a2a"/>
    </linearGradient>
    <linearGradient id="gHousingInner" x1="0" y1="0" x2="0" y2="1">
      <stop offset="0%" stop-color="#0d1520"/>
      <stop offset="100%" stop-color="#070c14"/>
    </linearGradient>
    <linearGradient id="gShaft" x1="0" y1="0" x2="1" y2="0">
      <stop offset="0%" stop-color="#c87a38"/>
      <stop offset="50%" stop-color="#daa060"/>
      <stop offset="100%" stop-color="#b06828"/>
    </linearGradient>
    <linearGradient id="gCone" x1="0" y1="0" x2="1" y2="0">
      <stop offset="0%" stop-color="#e8a855"/>
      <stop offset="100%" stop-color="#c07830"/>
    </linearGradient>
    <linearGradient id="gWedge" x1="0" y1="0" x2="0" y2="1">
      <stop offset="0%" stop-color="#7aa0c0"/>
      <stop offset="50%" stop-color="#506a84"/>
      <stop offset="100%" stop-color="#7aa0c0"/>
    </linearGradient>
    <linearGradient id="gSpring" x1="0" y1="0" x2="1" y2="0">
      <stop offset="0%" stop-color="#14b8a6"/>
      <stop offset="100%" stop-color="#2dd4bf"/>
    </linearGradient>
    <linearGradient id="gWheel" x1="0" y1="0" x2="0" y2="1">
      <stop offset="0%" stop-color="#8a6a3a"/>
      <stop offset="100%" stop-color="#5a4422"/>
    </linearGradient>
    <linearGradient id="gArrow" x1="0" y1="0" x2="1" y2="0">
      <stop offset="0%" stop-color="#ff4d4d"/>
      <stop offset="100%" stop-color="#ff8844"/>
    </linearGradient>
    <radialGradient id="gGlow" cx="50%" cy="50%" r="50%">
      <stop offset="0%" stop-color="#f0a500" stop-opacity="0.35"/>
      <stop offset="100%" stop-color="#f0a500" stop-opacity="0"/>
    </radialGradient>
    <radialGradient id="gGlowRed" cx="50%" cy="50%" r="50%">
      <stop offset="0%" stop-color="#ff4d4d" stop-opacity="0.25"/>
      <stop offset="100%" stop-color="#ff4d4d" stop-opacity="0"/>
    </radialGradient>
    <!-- 网格图案 -->
    <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
      <path d="M40 0 L0 0 0 40" fill="none" stroke="#1a2744" stroke-width="0.5" opacity="0.35"/>
    </pattern>
    <!-- 箭头标记 -->
    <marker id="arrowR" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
      <path d="M0,1 L9,5 L0,9 Z" fill="#ff4d4d"/>
    </marker>
    <marker id="arrowG" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse">
      <path d="M0,1 L9,5 L0,9 Z" fill="#22c55e"/>
    </marker>
    <marker id="arrowT" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
      <path d="M0,1 L9,5 L0,9 Z" fill="#2dd4bf"/>
    </marker>
    <!-- 滤镜 -->
    <filter id="fShadow" x="-10%" y="-10%" width="130%" height="130%">
      <feDropShadow dx="0" dy="2" stdDeviation="4" flood-color="#000" flood-opacity="0.5"/>
    </filter>
    <filter id="fGlow" x="-50%" y="-50%" width="200%" height="200%">
      <feGaussianBlur in="SourceGraphic" stdDeviation="8" result="blur"/>
      <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
    </filter>
  </defs>

  <!-- 背景网格 -->
  <rect width="1200" height="700" fill="#060b14"/>
  <rect width="1200" height="700" fill="url(#grid)"/>

  <!-- ============= 壳体 ============= -->
  <g id="housing">
    <!-- 壳体外壁 -->
    <rect x="120" y="90" width="960" height="440" rx="10" fill="url(#gHousing)" stroke="#2a3e5e" stroke-width="2.5" filter="url(#fShadow)"/>
    <!-- 壳体内腔 -->
    <rect x="148" y="118" width="904" height="384" rx="6" fill="url(#gHousingInner)" stroke="#162238" stroke-width="1"/>
    <!-- 左侧楔环座(壳体上的锥面) -->
    <path d="M158,200 L158,420 L210,395 L210,225 Z" fill="#1a2c44" stroke="#2a4060" stroke-width="1" opacity="0.6"/>
    <!-- 右侧弹簧座 -->
    <rect x="1010" y="240" width="30" height="140" rx="4" fill="#1e2d44" stroke="#2a4060" stroke-width="1"/>
  </g>

  <!-- ============= 楔环(分裂式,3 段) ============= -->
  <g id="wedgeGroup">
    <!-- 上楔块 -->
    <path id="wedgeTop" d="M215,230 L268,260 L268,288 L215,258 Z" fill="url(#gWedge)" stroke="#8ab0d0" stroke-width="1.5" stroke-linejoin="round"/>
    <!-- 下楔块 -->
    <path id="wedgeBot" d="M215,392 L268,362 L268,334 L215,364 Z" fill="url(#gWedge)" stroke="#8ab0d0" stroke-width="1.5" stroke-linejoin="round"/>
    <!-- 楔环裂缝标记(上) -->
    <line id="crackTop" x1="242" y1="244" x2="242" y2="273" stroke="#0d1520" stroke-width="2.5" opacity="0.7"/>
    <!-- 楔环裂缝标记(下) -->
    <line id="crackBot" x1="242" y1="348" x2="242" y2="377" stroke="#0d1520" stroke-width="2.5" opacity="0.7"/>
  </g>

  <!-- ============= 蜗杆轴组(可平移) ============= -->
  <g id="shaftGroup">
    <!-- 轴肩(弹簧左座) -->
    <rect x="790" y="260" width="14" height="100" rx="3" fill="#a06828" stroke="#c08030" stroke-width="1"/>

    <!-- 蜗杆轴主体 -->
    <rect x="268" y="286" width="522" height="48" rx="5" fill="url(#gShaft)" stroke="#c08030" stroke-width="1.2"/>

    <!-- 蜗杆螺纹线 -->
    <g id="threadLines" stroke="#8a5a20" stroke-width="1.8" opacity="0.7">
      <line x1="310" y1="286" x2="330" y2="334"/>
      <line x1="350" y1="286" x2="370" y2="334"/>
      <line x1="390" y1="286" x2="410" y2="334"/>
      <line x1="430" y1="286" x2="450" y2="334"/>
      <line x1="470" y1="286" x2="490" y2="334"/>
      <line x1="510" y1="286" x2="530" y2="334"/>
      <line x1="550" y1="286" x2="570" y2="334"/>
      <line x1="590" y1="286" x2="610" y2="334"/>
      <line x1="630" y1="286" x2="650" y2="334"/>
      <line x1="670" y1="286" x2="690" y2="334"/>
      <line x1="710" y1="286" x2="730" y2="334"/>
      <line x1="750" y1="286" x2="770" y2="334"/>
    </g>

    <!-- 圆锥面 -->
    <path id="cone" d="M268,286 L210,255 L210,365 L268,334 Z" fill="url(#gCone)" stroke="#d4944a" stroke-width="1.5" stroke-linejoin="round"/>

    <!-- 锥面高亮线 -->
    <line id="coneHL1" x1="268" y1="286" x2="210" y2="255" stroke="#f0c070" stroke-width="1" opacity="0.6"/>
    <line id="coneHL2" x1="268" y1="334" x2="210" y2="365" stroke="#f0c070" stroke-width="1" opacity="0.6"/>
  </g>

  <!-- ============= 弹簧 ============= -->
  <path id="spring" d="" fill="none" stroke="url(#gSpring)" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>

  <!-- ============= 蜗轮 ============= -->
  <g id="wheelGroup">
    <!-- 蜗轮齿圈 -->
    <circle cx="530" cy="490" r="98" fill="none" stroke="#6a5030" stroke-width="1" opacity="0.3"/>
    <circle cx="530" cy="490" r="80" fill="url(#gWheel)" stroke="#8a6a3a" stroke-width="2" opacity="0.85"/>
    <circle cx="530" cy="490" r="30" fill="#2a1e10" stroke="#5a4422" stroke-width="1.5"/>
    <!-- 蜗轮齿 -->
    <g id="wheelTeeth" fill="#7a5a2a" stroke="#9a7a4a" stroke-width="0.8">
    </g>
    <!-- 蜗轮辐条 -->
    <g stroke="#5a4422" stroke-width="3" opacity="0.5">
      <line x1="530" y1="460" x2="530" y2="430"/>
      <line x1="556" y1="475" x2="576" y2="455"/>
      <line x1="504" y1="475" x2="484" y2="455"/>
    </g>
  </g>

  <!-- ============= 力/箭头标注 ============= -->
  <g id="arrows">
    <!-- 反向驱动力箭头 -->
    <g id="reverseForceArrow" opacity="0">
      <line x1="660" y1="540" x2="540" y2="540" stroke="#ff4d4d" stroke-width="3.5" marker-end="url(#arrowR)"/>
      <text x="600" y="565" fill="#ff4d4d" font-family="Rajdhani, sans-serif" font-size="15" font-weight="700" text-anchor="middle">反向驱动力</text>
    </g>
    <!-- 轴向推力箭头 -->
    <g id="axialForceArrow" opacity="0">
      <line x1="340" y1="240" x2="220" y2="240" stroke="#ff8844" stroke-width="3" marker-end="url(#arrowR)"/>
      <text x="280" y="228" fill="#ff8844" font-family="Rajdhani, sans-serif" font-size="13" font-weight="600" text-anchor="middle">轴向推力</text>
    </g>
    <!-- 弹簧复位力箭头 -->
    <g id="springForceArrow" opacity="0">
      <line x1="940" y1="240" x2="1000" y2="240" stroke="#2dd4bf" stroke-width="2.5" marker-end="url(#arrowT)"/>
      <text x="970" y="228" fill="#2dd4bf" font-family="Rajdhani, sans-serif" font-size="12" font-weight="600" text-anchor="middle">弹簧复位</text>
    </g>
    <!-- 径向胀开箭头 -->
    <g id="radialArrow" opacity="0">
      <line x1="240" y1="240" x2="240" y2="195" stroke="#f0a500" stroke-width="2.5" marker-end="url(#arrowR)"/>
      <line x1="240" y1="408" x2="240" y2="450" stroke="#f0a500" stroke-width="2.5" marker-end="url(#arrowR)"/>
      <text x="265" y="190" fill="#f0a500" font-family="Rajdhani, sans-serif" font-size="12" font-weight="600">径向胀开</text>
    </g>
  </g>

  <!-- ============= 锁定光晕 ============= -->
  <ellipse id="lockGlow" cx="240" cy="310" rx="60" ry="80" fill="url(#gGlow)" opacity="0"/>

  <!-- ============= 标注文字 ============= -->
  <g id="labels" font-family="'Noto Sans SC', sans-serif" fill="#8899b0" font-size="12">
    <text x="240" y="170" text-anchor="middle" font-size="13" fill="#7aa0c0" font-weight="500">分裂式楔环</text>
    <line x1="240" y1="175" x2="240" y2="218" stroke="#7aa0c0" stroke-width="0.8" stroke-dasharray="3,3"/>
    <text x="210" y="398" text-anchor="middle" font-size="11">内锥面半角 8°</text>
    <text x="530" y="270" text-anchor="middle" font-size="13" fill="#c08030" font-weight="500">蜗杆轴</text>
    <text x="900" y="270" text-anchor="middle" font-size="12" fill="#2dd4bf">预紧弹簧 5N</text>
    <text x="530" y="610" text-anchor="middle" font-size="13" fill="#8a6a3a" font-weight="500">蜗轮</text>
    <text x="155" y="315" text-anchor="middle" font-size="11" fill="#4a6080" writing-mode="tb">壳体</text>
  </g>

  <!-- ============= 状态指示器 ============= -->
  <g id="stateIndicator" transform="translate(940, 120)">
    <rect x="0" y="0" width="190" height="100" rx="8" fill="#0c1220" stroke="#1a2744" stroke-width="1.5" opacity="0.92"/>
    <text id="stateLabel" x="95" y="30" text-anchor="middle" font-family="Rajdhani, sans-serif" font-size="16" font-weight="700" fill="#6b7fa3">FREE ROTATION</text>
    <text id="stateDesc" x="95" y="52" text-anchor="middle" font-family="'Noto Sans SC', sans-serif" font-size="12" fill="#6b7fa3">自由旋转</text>
    <circle id="stateDot" cx="30" cy="75" r="6" fill="#22c55e"/>
    <text id="stateParam" x="45" y="80" font-family="Rajdhani, sans-serif" font-size="12" fill="#4a6080">Δx = 0.0 mm</text>
  </g>

  <!-- ============= 旋转方向指示 ============= -->
  <g id="rotIndicator" transform="translate(660, 310)">
    <path id="rotArc" d="M-20,-18 A22,22 0 1,1 20,-18" fill="none" stroke="#22c55e" stroke-width="2" marker-end="url(#arrowG)" opacity="0.6"/>
    <text id="rotDirText" x="0" y="16" text-anchor="middle" font-family="Rajdhani, sans-serif" font-size="11" fill="#22c55e" opacity="0.7">CCW</text>
  </g>

</svg>
</div>

<!-- 控制面板 -->
<div class="controls">
  <div class="ctrl-card">
    <span class="ctrl-label">反向负载力 (N)</span>
    <input type="range" id="forceSlider" min="0" max="100" value="50"/>
    <span class="ctrl-value" id="forceVal">50 N</span>
  </div>
  <div class="ctrl-card">
    <span class="ctrl-label">预紧弹簧力 (N)</span>
    <input type="range" id="springSlider" min="1" max="20" value="5"/>
    <span class="ctrl-value" id="springVal">5 N</span>
  </div>
  <div class="ctrl-card">
    <span class="ctrl-label">动画速度</span>
    <input type="range" id="speedSlider" min="0.3" max="3" step="0.1" value="1"/>
    <span class="ctrl-value" id="speedVal">1.0x</span>
  </div>
  <button class="btn" id="modeBtn" aria-label="切换手动/自动模式">切换手动模式</button>
  <button class="btn" id="lockBtn" style="display:none" aria-label="手动触发锁定">触发锁定</button>
  <button class="btn" id="unlockBtn" style="display:none" aria-label="手动解锁">解锁复位</button>
</div>

<script>
(function(){
  /* ===== 常量 ===== */
  const SHAFT_FREE_X = 30;   // 自由态蜗杆偏移(像素)
  const SHAFT_LOCK_X = -12;  // 锁定态蜗杆偏移
  const WEDGE_FREE_Y = 0;    // 楔块自由态纵向偏移
  const WEDGE_EXPAND_Y = 18; // 楔块胀开纵向偏移
  const SPRING_FREE_W = 190; // 弹簧自由长度
  const SPRING_LOCK_W = 148; // 弹簧压缩长度
  const SPRING_COILS = 8;

  /* ===== 动画阶段定义 ===== */
  const PHASES = [
    { name:'forward',    dur:2800 },  // 正常旋转
    { name:'rev_start',  dur:1200 },  // 反向力出现
    { name:'locking',    dur:1400 },  // 锥面楔入
    { name:'locked',     dur:2600 },  // 硬锁定保持
    { name:'unlocking',  dur:1200 },  // 解锁
    { name:'returning',  dur:1200 },  // 复位
  ];

  /* ===== DOM 引用 ===== */
  const svg        = document.getElementById('mech');
  const shaftG     = document.getElementById('shaftGroup');
  const wedgeTop   = document.getElementById('wedgeTop');
  const wedgeBot   = document.getElementById('wedgeBot');
  const crackTop   = document.getElementById('crackTop');
  const crackBot   = document.getElementById('crackBot');
  const springPath = document.getElementById('spring');
  const lockGlow   = document.getElementById('lockGlow');
  const revArrow   = document.getElementById('reverseForceArrow');
  const axArrow    = document.getElementById('axialForceArrow');
  const sprArrow   = document.getElementById('springForceArrow');
  const radArrow   = document.getElementById('radialArrow');
  const stateLabel = document.getElementById('stateLabel');
  const stateDesc  = document.getElementById('stateDesc');
  const stateDot   = document.getElementById('stateDot');
  const stateParam = document.getElementById('stateParam');
  const rotArc     = document.getElementById('rotArc');
  const rotDirText = document.getElementById('rotDirText');
  const forceSlider = document.getElementById('forceSlider');
  const springSlider= document.getElementById('springSlider');
  const speedSlider = document.getElementById('speedSlider');
  const forceVal   = document.getElementById('forceVal');
  const springVal  = document.getElementById('springVal');
  const speedVal   = document.getElementById('speedVal');
  const modeBtn    = document.getElementById('modeBtn');
  const lockBtn    = document.getElementById('lockBtn');
  const unlockBtn  = document.getElementById('unlockBtn');

  /* ===== 生成蜗轮齿 ===== */
  (function buildTeeth(){
    const g = document.getElementById('wheelTeeth');
    const cx=530, cy=490, r=82, n=24;
    for(let i=0;i<n;i++){
      const a = (i/n)*Math.PI*2;
      const x1 = cx + Math.cos(a)*r;
      const y1 = cy + Math.sin(a)*r;
      const x2 = cx + Math.cos(a)*(r+12);
      const y2 = cy + Math.sin(a)*(r+12);
      const da = 0.06;
      const x3 = cx + Math.cos(a+da)*(r+12);
      const y3 = cy + Math.sin(a+da)*(r+12);
      const x4 = cx + Math.cos(a+da)*r;
      const y4 = cy + Math.sin(a+da)*r;
      const p = document.createElementNS('http://www.w3.org/2000/svg','polygon');
      p.setAttribute('points',`${x1},${y1} ${x2},${y2} ${x3},${y3} ${x4},${y4}`);
      g.appendChild(p);
    }
  })();

  /* ===== 工具函数 ===== */
  function lerp(a,b,t){ return a+(b-a)*t; }
  function easeInOut(t){ return t<0.5? 2*t*t : -1+(4-2*t)*t; }
  function easeOut(t){ return 1-Math.pow(1-t,3); }
  function clamp(v,lo,hi){ return Math.max(lo,Math.min(hi,v)); }

  /* ===== 弹簧路径生成 ===== */
  function springD(x1, x2, y, amp){
    const n = SPRING_COILS;
    const lead = 12;
    const sx = x1+lead, ex = x2-lead;
    let d = `M${x1},${y} L${sx},${y}`;
    const dx = (ex-sx)/(n*2);
    for(let i=0;i<n*2;i++){
      const px = sx+dx*(i+1);
      const py = y + ((i%2===0)? -amp : amp);
      d += ` L${px},${py}`;
    }
    d += ` L${ex},${y} L${x2},${y}`;
    return d;
  }

  /* ===== 动画状态 ===== */
  let autoMode = true;
  let animTime = 0;
  let lastTs = 0;
  let speed = 1;
  let manualLock = false;
  let rotAngle = 0;
  let wheelAngle = 0;

  /* ===== 滑块事件 ===== */
  forceSlider.addEventListener('input', ()=>{ forceVal.textContent = forceSlider.value+' N'; });
  springSlider.addEventListener('input', ()=>{ springVal.textContent = springSlider.value+' N'; });
  speedSlider.addEventListener('input', ()=>{ speed = parseFloat(speedSlider.value); speedVal.textContent = speed.toFixed(1)+'x'; });
  modeBtn.addEventListener('click', ()=>{
    autoMode = !autoMode;
    modeBtn.textContent = autoMode ? '切换手动模式' : '切换自动模式';
    modeBtn.classList.toggle('active', !autoMode);
    lockBtn.style.display = autoMode ? 'none' : 'inline-block';
    unlockBtn.style.display = autoMode ? 'none' : 'inline-block';
    if(autoMode){ animTime=0; manualLock=false; }
  });
  lockBtn.addEventListener('click', ()=>{ manualLock=true; });
  unlockBtn.addEventListener('click', ()=>{ manualLock=false; });

  /* ===== 计算当前动画参数 ===== */
  function computeAuto(t){
    // 计算当前阶段
    let totalDur = PHASES.reduce((s,p)=>s+p.dur,0);
    let ct = t % totalDur;
    let phaseIdx = 0;
    let phaseT = 0;
    let acc = 0;
    for(let i=0;i<PHASES.length;i++){
      if(ct < acc+PHASES[i].dur){
        phaseIdx = i;
        phaseT = (ct-acc)/PHASES[i].dur;
        break;
      }
      acc += PHASES[i].dur;
    }

    let shaftX, wedgeY, springW, glowOp;
    let showRev=false, showAx=false, showSpr=false, showRad=false;
    let stateName, stateDescT, dotColor, isForward=true;

    switch(phaseIdx){
      case 0: // forward - 正常旋转
        shaftX = SHAFT_FREE_X;
        wedgeY = WEDGE_FREE_Y;
        springW = SPRING_FREE_W;
        glowOp = 0;
        stateName = 'FREE ROTATION';
        stateDescT = '自由旋转 · 弹簧定位';
        dotColor = '#22c55e';
        break;
      case 1: // rev_start - 反向力出现
        shaftX = lerp(SHAFT_FREE_X, SHAFT_FREE_X*0.3, easeInOut(phaseT));
        wedgeY = WEDGE_FREE_Y;
        springW = lerp(SPRING_FREE_W, SPRING_FREE_W - 15, easeInOut(phaseT));
        glowOp = 0;
        showRev = true; showAx = true;
        isForward = false;
        stateName = 'REVERSE LOAD';
        stateDescT = '反向受力 · 轴向推力增大';
        dotColor = '#ff4d4d';
        break;
      case 2: // locking - 锥面楔入
        shaftX = lerp(SHAFT_FREE_X*0.3, SHAFT_LOCK_X, easeInOut(phaseT));
        wedgeY = lerp(WEDGE_FREE_Y, WEDGE_EXPAND_Y, easeOut(phaseT));
        springW = lerp(SPRING_FREE_W-15, SPRING_LOCK_W, easeInOut(phaseT));
        glowOp = lerp(0, 0.7, easeOut(phaseT));
        showRev = true; showAx = true; showRad = phaseT>0.3;
        isForward = false;
        stateName = 'LOCKING';
        stateDescT = '锥面楔入 · 径向胀开';
        dotColor = '#f0a500';
        break;
      case 3: // locked - 硬锁定
        shaftX = SHAFT_LOCK_X;
        wedgeY = WEDGE_EXPAND_Y;
        springW = SPRING_LOCK_W;
        glowOp = 0.7 + Math.sin(phaseT*Math.PI*4)*0.15;
        showRev = true; showRad = true;
        isForward = false;
        stateName = 'HARD LOCKED';
        stateDescT = '机械硬锁定 · 零间隙';
        dotColor = '#f0a500';
        break;
      case 4: // unlocking - 解锁
        shaftX = lerp(SHAFT_LOCK_X, SHAFT_FREE_X*0.5, easeInOut(phaseT));
        wedgeY = lerp(WEDGE_EXPAND_Y, WEDGE_FREE_Y, easeOut(phaseT));
        springW = lerp(SPRING_LOCK_W, SPRING_FREE_W-8, easeInOut(phaseT));
        glowOp = lerp(0.7, 0, easeInOut(phaseT));
        showSpr = true;
        isForward = false;
        stateName = 'UNLOCKING';
        stateDescT = '弹簧复位 · 脱离楔环';
        dotColor = '#2dd4bf';
        break;
      case 5: // returning - 复位
        shaftX = lerp(SHAFT_FREE_X*0.5, SHAFT_FREE_X, easeOut(phaseT));
        wedgeY = WEDGE_FREE_Y;
        springW = lerp(SPRING_FREE_W-8, SPRING_FREE_W, easeOut(phaseT));
        glowOp = 0;
        showSpr = phaseT<0.5;
        stateName = 'FREE ROTATION';
        stateDescT = '复位完成 · 自由旋转';
        dotColor = '#22c55e';
        break;
    }

    return { shaftX, wedgeY, springW, glowOp, showRev, showAx, showSpr, showRad, stateName, stateDescT, dotColor, isForward, phaseIdx };
  }

  function computeManual(){
    const t = manualLock ? 1 : 0;
    const et = easeInOut(clamp(t,0,1));
    return {
      shaftX: lerp(SHAFT_FREE_X, SHAFT_LOCK_X, et),
      wedgeY: lerp(WEDGE_FREE_Y, WEDGE_EXPAND_Y, et),
      springW: lerp(SPRING_FREE_W, SPRING_LOCK_W, et),
      glowOp: et * 0.7,
      showRev: manualLock, showAx: manualLock, showSpr: !manualLock, showRad: manualLock,
      stateName: manualLock ? 'HARD LOCKED' : 'FREE ROTATION',
      stateDescT: manualLock ? '机械硬锁定' : '自由旋转',
      dotColor: manualLock ? '#f0a500' : '#22c55e',
      isForward: !manualLock,
      phaseIdx: manualLock ? 3 : 0,
    };
  }

  /* ===== 渲染 ===== */
  function render(st){
    // 蜗杆平移
    shaftG.setAttribute('transform', `translate(${st.shaftX},0)`);

    // 楔块胀开
    const wty = -st.wedgeY;
    const wby = st.wedgeY;
    // 上楔块
    const wtOrig = 'M215,230 L268,260 L268,288 L215,258 Z';
    const wtD = `M215,${230-wty} L268,${260-wty} L268,${288-wty} L215,${258-wty} Z`;
    wedgeTop.setAttribute('d', wtD);
    // 下楔块
    const wbD = `M215,${392+wby} L268,${362+wby} L268,${334+wby} L215,${364+wby} Z`;
    wedgeBot.setAttribute('d', wbD);
    // 裂缝
    crackTop.setAttribute('y1', 244-wty);
    crackTop.setAttribute('y2', 273-wty);
    crackBot.setAttribute('y1', 348+wby);
    crackBot.setAttribute('y2', 377+wby);

    // 楔块颜色随胀开变化
    const expandFrac = st.wedgeY / WEDGE_EXPAND_Y;
    if(expandFrac > 0.5){
      wedgeTop.setAttribute('fill', '#8aaccc');
      wedgeBot.setAttribute('fill', '#8aaccc');
      wedgeTop.setAttribute('stroke', '#b0d4f0');
      wedgeBot.setAttribute('stroke', '#b0d4f0');
    } else {
      wedgeTop.setAttribute('fill', 'url(#gWedge)');
      wedgeBot.setAttribute('fill', 'url(#gWedge)');
      wedgeTop.setAttribute('stroke', '#8ab0d0');
      wedgeBot.setAttribute('stroke', '#8ab0d0');
    }

    // 弹簧
    const springX1 = 804 + st.shaftX;
    const springX2 = 1010;
    const actualW = springX2 - springX1;
    const amp = 16;
    springPath.setAttribute('d', springD(springX1, springX2, 310, amp));
    // 弹簧颜色随压缩变化
    if(actualW < SPRING_FREE_W - 10){
      springPath.setAttribute('stroke', '#f59e0b');
      springPath.setAttribute('stroke-width', '5');
    } else {
      springPath.setAttribute('stroke', 'url(#gSpring)');
      springPath.setAttribute('stroke-width', '4');
    }

    // 锁定光晕
    lockGlow.setAttribute('opacity', st.glowOp);

    // 箭头显隐
    revArrow.setAttribute('opacity', st.showRev ? 1 : 0);
    axArrow.setAttribute('opacity', st.showAx ? 1 : 0);
    sprArrow.setAttribute('opacity', st.showSpr ? 0.8 : 0);
    radArrow.setAttribute('opacity', st.showRad ? 1 : 0);

    // 反向力箭头动画(脉冲)
    if(st.showRev){
      const pulse = 0.8 + Math.sin(animTime*0.006)*0.2;
      revArrow.setAttribute('opacity', pulse);
    }

    // 状态指示器
    stateLabel.textContent = st.stateName;
    stateDesc.textContent = st.stateDescT;
    stateDot.setAttribute('fill', st.dotColor);
    const dxMm = ((SHAFT_FREE_X - st.shaftX) / (SHAFT_FREE_X - SHAFT_LOCK_X) * 2.5).toFixed(1);
    stateParam.textContent = `Δx = ${dxMm} mm`;

    // 旋转方向
    if(st.isForward){
      rotArc.setAttribute('stroke', '#22c55e');
      rotArc.setAttribute('d', 'M-20,-18 A22,22 0 1,1 20,-18');
      rotDirText.textContent = 'CCW';
      rotDirText.setAttribute('fill', '#22c55e');
      rotArc.setAttribute('marker-end', 'url(#arrowG)');
    } else {
      rotArc.setAttribute('stroke', '#ff4d4d');
      rotArc.setAttribute('d', 'M20,-18 A22,22 0 1,0 -20,-18');
      rotDirText.textContent = 'CW';
      rotDirText.setAttribute('fill', '#ff4d4d');
      rotArc.setAttribute('marker-end', 'url(#arrowR)');
    }
    const rotOp = (st.phaseIdx===3) ? 0.2 : 0.6;
    rotArc.setAttribute('opacity', rotOp);
    rotDirText.setAttribute('opacity', rotOp);
  }

  /* ===== 主动画循环 ===== */
  function tick(ts){
    if(!lastTs) lastTs = ts;
    const dt = ts - lastTs;
    lastTs = ts;
    animTime += dt * speed;

    // 旋转蜗轮
    const st0 = autoMode ? computeAuto(animTime) : computeManual();
    const rotSpeed = st0.isForward ? 0.03 : (st0.phaseIdx===3 ? 0 : -0.02);
    wheelAngle += rotSpeed * speed;
    const wheelG = document.getElementById('wheelGroup');
    wheelG.querySelector('g#wheelTeeth').setAttribute('transform', `rotate(${wheelAngle} 530 490)`);

    // 螺纹动画(水平偏移模拟旋转)
    rotAngle += rotSpeed * speed * 18;
    const threadG = document.getElementById('threadLines');
    const threadShift = ((rotAngle % 40) + 40) % 40;
    threadG.setAttribute('transform', `translate(${-threadShift},0)`);
    // 循环补位:复制螺纹
    const lines = threadG.querySelectorAll('line');
    lines.forEach((l,i)=>{
      let x1 = 310 + i*40 - threadShift;
      let x2 = 330 + i*40 - threadShift;
      if(x1 < 268){ x1 += 520; x2 += 520; }
      if(x2 > 790){ x1 -= 520; x2 -= 520; }
      l.setAttribute('x1', clamp(x1,270,790));
      l.setAttribute('x2', clamp(x2,270,790));
    });

    const st = autoMode ? computeAuto(animTime) : computeManual();
    render(st);
    requestAnimationFrame(tick);
  }

  /* ===== 启动 ===== */
  requestAnimationFrame(tick);
})();
</script>
</body>
</html>

实现说明

本动画以工业蓝图风格的深色横截面视图,完整展示"浮动蜗杆轴 + 锥面楔块"硬锁定的核心原理,严格遵循 IFR 思想——直接呈现理想解状态,突出有害力→锁定力的资源转化:

视觉设计

  • 深海军蓝背景 + 精密网格营造工程制图氛围
  • 铜色蜗杆轴、钢蓝楔环、青绿弹簧三色分明,一眼识别功能分区
  • 锁定瞬间出现琥珀色光晕脉冲,锁定状态楔环变亮,视觉聚焦核心创新点
  • 红色力箭头 → 橙色轴向推力 → 金色径向胀开,用色彩链引导"有害力转化为锁定力"的逻辑

动画循环(6 阶段自动播放)

  1. 自由旋转 → 2. 反向力出现 → 3. 锥面楔入·径向胀开 → 4. 硬锁定保持 → 5. 弹簧复位·解锁 → 6. 回归自由,无缝循环

交互控制

  • 反向负载力 / 弹簧预紧力 / 动画速度三个滑块可实时调节
  • 手动模式下可点击"触发锁定"和"解锁复位"按钮,手动控制完整锁定-解锁过程
  • 右上角状态面板实时显示当前阶段、位移量

IFR 体现

  • 动画不展示"传统摩擦自锁失效"的对比,直接聚焦理想解工作原理
  • 红色反向力箭头与金色径向胀开箭头同时出现,直观传达"驱动力即锁定力"的核心矛盾化解
积分规则:第一轮对话扣减8分,后续每轮扣6分