分享图
A
动画渲染工坊
就绪
<!DOCTYPE html>
<html lang="zh">
<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=Instrument+Serif:ital@0;1&family=IBM+Plex+Mono:wght@300;400;500;600&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<style>
:root{--bg:#0B1120;--fg:#E0E8F0;--muted:#5A6B7F;--accent:#FF6B35;--accent2:#00E676;--card:#111B2E;--border:#1C2B42;--steel:#7B8FA3;--copper:#D4A574;--sleeve:#2D8A5E;--heat:#FFB627;--danger:#FF4757}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--fg);font-family:'IBM Plex Mono',monospace;min-height:100vh;overflow-x:hidden}
.title-font{font-family:'Instrument Serif',serif}
body::before{content:'';position:fixed;inset:0;background:radial-gradient(ellipse at 25% 40%,rgba(255,107,53,.04) 0%,transparent 50%),radial-gradient(ellipse at 75% 60%,rgba(0,230,118,.03) 0%,transparent 50%),radial-gradient(ellipse at 50% 80%,rgba(212,165,116,.03) 0%,transparent 40%);pointer-events:none;z-index:0}
@keyframes blobA{0%,100%{transform:translate(0,0) scale(1)}50%{transform:translate(40px,-30px) scale(1.15)}}
@keyframes blobB{0%,100%{transform:translate(0,0) scale(1)}50%{transform:translate(-30px,40px) scale(1.1)}}
.blob{position:fixed;border-radius:50%;filter:blur(80px);pointer-events:none;z-index:0}
.blob-a{width:300px;height:300px;background:rgba(255,107,53,.06);top:20%;left:10%;animation:blobA 12s ease-in-out infinite}
.blob-b{width:250px;height:250px;background:rgba(0,230,118,.04);bottom:15%;right:10%;animation:blobB 15s ease-in-out infinite}
input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:4px;background:var(--border);border-radius:2px;outline:none}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;border-radius:50%;background:var(--accent);cursor:pointer;border:2px solid var(--bg);box-shadow:0 0 10px rgba(255,107,53,.4)}
input[type=range]::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:var(--accent);cursor:pointer;border:2px solid var(--bg)}
.info-card{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px 16px;transition:border-color .3s,box-shadow .3s}
.info-card:hover{border-color:rgba(255,107,53,.4);box-shadow:0 0 20px rgba(255,107,53,.06)}
.step-card{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:10px 14px;transition:all .4s;flex:1;min-width:0}
.step-card.active{border-color:var(--accent);box-shadow:0 0 16px rgba(255,107,53,.15)}
.step-card.active .step-icon{color:var(--accent)}
.step-card.locked{border-color:var(--accent2);box-shadow:0 0 16px rgba(0,230,118,.15)}
.step-card.locked .step-icon{color:var(--accent2)}
.arrow-connector{color:var(--muted);font-size:18px;flex-shrink:0;opacity:.5}
@keyframes fadeUp{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:translateY(0)}}
.fade-in{animation:fadeUp .5s ease-out forwards}
.fd1{animation-delay:.05s;opacity:0}.fd2{animation-delay:.1s;opacity:0}.fd3{animation-delay:.15s;opacity:0}.fd4{animation-delay:.2s;opacity:0}.fd5{animation-delay:.25s;opacity:0}
.glow-green{filter:drop-shadow(0 0 6px rgba(0,230,118,.6))}
@keyframes pulseGreen{0%,100%{opacity:.7}50%{opacity:1}}
.pulse-green{animation:pulseGreen 2s ease-in-out infinite}
::-webkit-scrollbar{width:5px}::-webkit-scrollbar-track{background:var(--bg)}::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px}
@media(max-width:1024px){.main-layout{flex-direction:column!important}.ctrl-panel{max-width:100%!important;width:100%!important}}
</style>
</head>
<body class="relative">
<div class="blob blob-a"></div>
<div class="blob blob-b"></div>

<!-- 顶部标题 -->
<header class="relative z-10 pt-6 pb-4 px-6 border-b border-[--border]" style="background:linear-gradient(180deg,rgba(11,17,32,.9),transparent)">
  <div class="max-w-7xl mx-auto flex items-end gap-4 flex-wrap">
    <h1 class="title-font text-4xl md:text-5xl tracking-tight" style="color:var(--fg)">自锁螺栓</h1>
    <div class="pb-1">
      <span class="text-xs font-medium tracking-widest uppercase" style="color:var(--accent)">IFR 最终理想解</span>
      <p class="text-xs mt-0.5" style="color:var(--muted)">振动能量 → 锁紧动力 &nbsp;|&nbsp; 零附加元件 · 零胶水 · 零复杂工序</p>
    </div>
  </div>
</header>

<!-- 主内容 -->
<main class="relative z-10 max-w-7xl mx-auto px-4 py-6">
  <div class="main-layout flex gap-6" style="flex-direction:row">
    
    <!-- SVG 动画区域 -->
    <div class="flex-1 min-w-0 fade-in fd1">
      <div class="info-card p-2 md:p-4" style="background:linear-gradient(135deg,#0D1628,#111D33)">
        <svg id="main-svg" viewBox="0 0 700 920" style="width:100%;height:auto;display:block" xmlns="http://www.w3.org/2000/svg">
          <defs>
            <!-- 渐变 -->
            <linearGradient id="steelGrad" x1="0" y1="0" x2="1" y2="1">
              <stop offset="0%" stop-color="#6B7D91"/><stop offset="50%" stop-color="#8B9DAF"/><stop offset="100%" stop-color="#5A6B7F"/>
            </linearGradient>
            <linearGradient id="copperGrad" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%" stop-color="#E0B88A"/><stop offset="50%" stop-color="#D4A574"/><stop offset="100%" stop-color="#B87A4B"/>
            </linearGradient>
            <linearGradient id="sleeveGrad" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%" stop-color="#35A06A"/><stop offset="50%" stop-color="#2D8A5E"/><stop offset="100%" stop-color="#1B5E3B"/>
            </linearGradient>
            <linearGradient id="plateGrad" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%" stop-color="#3E4E62"/><stop offset="100%" stop-color="#2A3648"/>
            </linearGradient>
            <linearGradient id="nutGrad" x1="0" y1="0" x2="1" y2="1">
              <stop offset="0%" stop-color="#5A6B7F"/><stop offset="100%" stop-color="#4A5A6E"/>
            </linearGradient>
            <!-- 剖面线 -->
            <pattern id="hatch" width="7" height="7" patternTransform="rotate(45)" patternUnits="userSpaceOnUse">
              <line x1="0" y1="0" x2="0" y2="7" stroke="rgba(255,255,255,.06)" stroke-width=".8"/>
            </pattern>
            <!-- 网格 -->
            <pattern id="grid" width="25" height="25" patternUnits="userSpaceOnUse">
              <path d="M 25 0 L 0 0 0 25" fill="none" stroke="rgba(255,255,255,.02)" stroke-width=".5"/>
            </pattern>
            <!-- 发光滤镜 -->
            <filter id="glowOrange" x="-50%" y="-50%" width="200%" height="200%">
              <feGaussianBlur in="SourceGraphic" stdDeviation="4" result="blur"/>
              <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
            </filter>
            <filter id="glowGreen" x="-50%" y="-50%" width="200%" height="200%">
              <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur"/>
              <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
            </filter>
            <filter id="heatGlow" x="-50%" y="-50%" width="200%" height="200%">
              <feGaussianBlur in="SourceGraphic" stdDeviation="3"/>
            </filter>
          </defs>

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

          <!-- 截面标记 -->
          <text x="52" y="46" fill="rgba(255,255,255,.2)" font-size="13" font-family="IBM Plex Mono" font-weight="600">A</text>
          <line x1="44" y1="52" x2="44" y2="880" stroke="rgba(255,255,255,.1)" stroke-width="1" stroke-dasharray="8 5"/>
          <text x="52" y="895" fill="rgba(255,255,255,.2)" font-size="13" font-family="IBM Plex Mono" font-weight="600">A</text>
          <text x="120" y="895" fill="rgba(255,255,255,.12)" font-size="10" font-family="IBM Plex Mono">纵截面 A-A</text>

          <!-- ===== 振动组 (平板+螺母+下段螺杆) ===== -->
          <g id="vibrating-group">
            <!-- 上被夹紧件 -->
            <rect x="160" y="350" width="380" height="145" rx="3" fill="url(#plateGrad)" stroke="rgba(255,255,255,.08)" stroke-width="1"/>
            <rect x="160" y="350" width="380" height="145" rx="3" fill="url(#hatch)"/>
            <!-- 下被夹紧件 -->
            <rect x="160" y="495" width="380" height="145" rx="3" fill="url(#plateGrad)" stroke="rgba(255,255,255,.08)" stroke-width="1"/>
            <rect x="160" y="495" width="380" height="145" rx="3" fill="url(#hatch)"/>
            <!-- 间隙线 -->
            <line x1="170" y1="495" x2="530" y2="495" stroke="rgba(255,255,255,.15)" stroke-width=".5"/>
            <!-- 孔 -->
            <rect x="304" y="350" width="92" height="290" fill="var(--bg)" opacity=".9"/>
            
            <!-- 下段螺杆 (全直径穿过平板) -->
            <rect x="310" y="350" width="80" height="295" rx="1" fill="url(#steelGrad)" stroke="rgba(255,255,255,.06)" stroke-width=".5"/>
            
            <!-- 螺纹啮合区 (螺栓外螺纹) -->
            <g id="bolt-threads"></g>
            
            <!-- 螺母 -->
            <rect x="270" y="645" width="160" height="65" rx="4" fill="url(#nutGrad)" stroke="rgba(255,255,255,.1)" stroke-width="1"/>
            <rect x="270" y="645" width="160" height="65" rx="4" fill="url(#hatch)"/>
            <!-- 螺母内螺纹 -->
            <g id="nut-threads"></g>
            <!-- 螺母孔 -->
            <rect x="310" y="645" width="80" height="65" fill="var(--bg)" opacity=".7"/>

            <!-- 螺纹稳定指示 (绿光环) -->
            <g id="thread-indicator">
              <rect x="304" y="570" width="92" height="80" rx="4" fill="none" stroke="rgba(0,230,118,.25)" stroke-width="2" filter="url(#glowGreen)"/>
              <rect x="304" y="570" width="92" height="80" rx="4" fill="rgba(0,230,118,.04)"/>
            </g>
          </g>

          <!-- ===== 螺栓头 (相对固定) ===== -->
          <g id="bolt-head-group">
            <rect x="268" y="65" width="164" height="75" rx="5" fill="url(#steelGrad)" stroke="rgba(255,255,255,.12)" stroke-width="1"/>
            <!-- 螺栓头底部承载面 -->
            <line x1="272" y1="140" x2="428" y2="140" stroke="rgba(255,255,255,.2)" stroke-width="1.5"/>
            <!-- 顶部倒角 -->
            <path d="M 272 70 L 268 65 L 432 65 L 428 70" fill="rgba(255,255,255,.04)" stroke="rgba(255,255,255,.08)" stroke-width=".5"/>
          </g>

          <!-- ===== 细颈 (动态弯曲路径) ===== -->
          <path id="neck-path" fill="url(#copperGrad)" stroke="rgba(255,255,255,.1)" stroke-width=".5"/>

          <!-- ===== 阻尼套筒 (动态弯曲) ===== -->
          <path id="sleeve-left" fill="url(#sleeveGrad)" stroke="rgba(255,255,255,.08)" stroke-width=".5"/>
          <path id="sleeve-right" fill="url(#sleeveGrad)" stroke="rgba(255,255,255,.08)" stroke-width=".5"/>

          <!-- ===== 摩擦粒子组 ===== -->
          <g id="particle-group"></g>

          <!-- ===== 标注 ===== -->
          <g id="annotations" font-family="IBM Plex Mono" font-size="10">
            <!-- 细颈标注 -->
            <g id="anno-neck">
              <line x1="378" y1="220" x2="500" y2="220" stroke="rgba(212,165,116,.4)" stroke-width=".7" stroke-dasharray="4 3"/>
              <circle cx="378" cy="220" r="2" fill="rgba(212,165,116,.6)"/>
              <text x="505" y="218" fill="#D4A574" font-weight="500">细颈弹性杆</text>
              <text x="505" y="232" fill="rgba(212,165,116,.6)" font-size="9">d = 0.7D</text>
            </g>
            <!-- 套筒标注 -->
            <g id="anno-sleeve">
              <line x1="310" y1="280" x2="195" y2="280" stroke="rgba(45,138,94,.4)" stroke-width=".7" stroke-dasharray="4 3"/>
              <circle cx="310" cy="280" r="2" fill="rgba(45,138,94,.6)"/>
              <text x="100" y="278" fill="#2D8A5E" font-weight="500">高阻尼套筒</text>
              <text x="100" y="292" fill="rgba(45,138,94,.6)" font-size="9">t = 2mm</text>
            </g>
            <!-- 螺纹稳定标注 -->
            <g id="anno-thread">
              <line x1="396" y1="610" x2="520" y2="610" stroke="rgba(0,230,118,.4)" stroke-width=".7" stroke-dasharray="4 3"/>
              <circle cx="396" cy="610" r="2" fill="rgba(0,230,118,.6)"/>
              <text x="525" y="608" fill="#00E676" font-weight="500">螺纹稳定区</text>
              <text x="525" y="622" fill="rgba(0,230,118,.6)" font-size="9">法向力恒定</text>
            </g>
            <!-- 颈长标注 -->
            <g id="anno-length">
              <line x1="440" y1="140" x2="460" y2="140" stroke="rgba(255,255,255,.15)" stroke-width=".5"/>
              <line x1="440" y1="350" x2="460" y2="350" stroke="rgba(255,255,255,.15)" stroke-width=".5"/>
              <line x1="450" y1="145" x2="450" y2="345" stroke="rgba(255,255,255,.15)" stroke-width=".5"/>
              <text x="465" y="250" fill="rgba(255,255,255,.25)" font-size="9" transform="rotate(90,465,250)">L = 3d</text>
            </g>
            <!-- 预紧力箭头 -->
            <g id="preload-arrows" opacity=".3">
              <line x1="350" y1="55" x2="350" y2="40" stroke="#FF6B35" stroke-width="1.5" marker-end="url(#arrowOrange)"/>
              <line x1="350" y1="720" x2="350" y2="735" stroke="#FF6B35" stroke-width="1.5"/>
              <text x="358" y="48" fill="rgba(255,107,53,.5)" font-size="8">预紧力 F</text>
            </g>
          </g>

          <!-- ===== 振动位移箭头 (动态) ===== -->
          <g id="vibration-arrows" opacity="0">
            <g id="vib-arrow-left">
              <polygon points="140,422 155,415 155,429" fill="var(--danger)" opacity=".7"/>
              <line x1="155" y1="422" x2="175" y2="422" stroke="var(--danger)" stroke-width="1.5" opacity=".5"/>
            </g>
            <g id="vib-arrow-right">
              <polygon points="560,422 545,415 545,429" fill="var(--danger)" opacity=".7"/>
              <line x1="545" y1="422" x2="525" y2="422" stroke="var(--danger)" stroke-width="1.5" opacity=".5"/>
            </g>
            <text id="vib-label" x="350" y="830" text-anchor="middle" fill="rgba(255,71,87,.5)" font-size="10" font-family="IBM Plex Mono">振动位移</text>
          </g>

          <!-- ===== 热能耗散波纹 (动态) ===== -->
          <g id="heat-waves"></g>

          <!-- 实时数值 -->
          <g font-family="IBM Plex Mono" font-size="10">
            <text id="disp-val" x="30" y="422" fill="rgba(255,107,53,.6)" font-size="9">Δx = 0 μm</text>
            <text id="energy-val" x="30" y="440" fill="rgba(255,182,39,.5)" font-size="9">耗散: 0 mJ</text>
          </g>
        </svg>
      </div>
    </div>

    <!-- 控制面板 -->
    <div class="ctrl-panel flex flex-col gap-4" style="width:280px;max-width:280px;flex-shrink:0">
      <!-- 振幅控制 -->
      <div class="info-card fade-in fd2">
        <div class="flex items-center justify-between mb-2">
          <span class="text-xs font-medium" style="color:var(--accent)"><i class="fas fa-wave-square mr-1"></i>振动振幅</span>
          <span id="amp-display" class="text-xs" style="color:var(--muted)">18 px</span>
        </div>
        <input type="range" id="amp-slider" min="0" max="40" value="18" step="1">
        <div class="flex justify-between mt-1"><span class="text-[9px]" style="color:var(--muted)">静止</span><span class="text-[9px]" style="color:var(--muted)">极限</span></div>
      </div>

      <!-- 频率控制 -->
      <div class="info-card fade-in fd3">
        <div class="flex items-center justify-between mb-2">
          <span class="text-xs font-medium" style="color:var(--accent)"><i class="fas fa-bolt mr-1"></i>振动频率</span>
          <span id="freq-display" class="text-xs" style="color:var(--muted)">1.5 Hz</span>
        </div>
        <input type="range" id="freq-slider" min="0.3" max="5" value="1.5" step="0.1">
        <div class="flex justify-between mt-1"><span class="text-[9px]" style="color:var(--muted)">低频</span><span class="text-[9px]" style="color:var(--muted)">高频</span></div>
      </div>

      <!-- 播放控制 -->
      <div class="info-card fade-in fd3 flex items-center gap-3">
        <button id="play-btn" class="w-9 h-9 rounded-full flex items-center justify-center transition-all" style="background:var(--accent);color:var(--bg)" aria-label="播放/暂停">
          <i class="fas fa-pause text-sm"></i>
        </button>
        <button id="reset-btn" class="w-9 h-9 rounded-full flex items-center justify-center transition-all border" style="border-color:var(--border);color:var(--muted)" aria-label="重置">
          <i class="fas fa-undo text-sm"></i>
        </button>
        <button id="force-btn" class="w-9 h-9 rounded-full flex items-center justify-center transition-all border" style="border-color:var(--border);color:var(--muted)" aria-label="力线显示">
          <i class="fas fa-arrows-alt text-sm"></i>
        </button>
        <div class="text-[9px]" style="color:var(--muted)">播放 / 重置 / 力线</div>
      </div>

      <!-- 实时指标 -->
      <div class="info-card fade-in fd4">
        <div class="text-xs font-medium mb-3" style="color:var(--fg)"><i class="fas fa-chart-line mr-1" style="color:var(--accent)"></i>实时状态</div>
        <div class="space-y-3">
          <!-- 位移 -->
          <div>
            <div class="flex justify-between text-[10px] mb-1"><span style="color:var(--muted)">相对位移</span><span id="disp-percent" style="color:var(--accent)">0%</span></div>
            <div class="h-1.5 rounded-full overflow-hidden" style="background:var(--border)"><div id="disp-bar" class="h-full rounded-full transition-all" style="width:0%;background:var(--accent)"></div></div>
          </div>
          <!-- 能量耗散 -->
          <div>
            <div class="flex justify-between text-[10px] mb-1"><span style="color:var(--muted)">阻尼耗散</span><span id="heat-percent" style="color:var(--heat)">0%</span></div>
            <div class="h-1.5 rounded-full overflow-hidden" style="background:var(--border)"><div id="heat-bar" class="h-full rounded-full transition-all" style="width:0%;background:var(--heat)"></div></div>
          </div>
          <!-- 螺纹稳定度 -->
          <div>
            <div class="flex justify-between text-[10px] mb-1"><span style="color:var(--muted)">螺纹稳定度</span><span id="stab-percent" style="color:var(--accent2)">100%</span></div>
            <div class="h-1.5 rounded-full overflow-hidden" style="background:var(--border)"><div id="stab-bar" class="h-full rounded-full transition-all" style="width:100%;background:var(--accent2)"></div></div>
          </div>
        </div>
      </div>

      <!-- 原理说明 -->
      <div class="info-card fade-in fd5">
        <div class="text-xs font-medium mb-2" style="color:var(--fg)"><i class="fas fa-lightbulb mr-1" style="color:var(--copper)"></i>IFR 核心机理</div>
        <p class="text-[10px] leading-relaxed" style="color:var(--muted)">
          细颈杆将弯曲刚度降至原来的 <span style="color:var(--copper)">24%</span>(0.7⁴),使微米级位移集中于细颈段弹性弯曲,而非传递至螺纹啮合区。阻尼套筒通过界面摩擦将振动动能转化为热能,实现<span style="color:var(--accent2)">自稳定锁紧</span>——振动越强,阻尼耗散越大,螺纹法向力越稳定。
        </p>
      </div>

      <!-- 失效边界 -->
      <div class="info-card fade-in fd5" style="border-color:rgba(255,71,87,.2)">
        <div class="text-xs font-medium mb-1" style="color:var(--danger)"><i class="fas fa-exclamation-triangle mr-1"></i>失效边界</div>
        <p class="text-[10px] leading-relaxed" style="color:var(--muted)">
          振幅超出细颈弹性极限 → 疲劳断裂;被夹紧件过薄 → 无空间车削细颈。
        </p>
      </div>
    </div>
  </div>

  <!-- 机理链 -->
  <div class="mt-6 fade-in fd5">
    <div class="flex items-center gap-2 flex-wrap justify-center">
      <div class="step-card" id="step-0">
        <div class="flex items-center gap-2 mb-1"><i class="fas fa-wave-square step-icon text-xs" style="color:var(--muted)"></i><span class="text-[10px] font-medium">高频振动</span></div>
        <p class="text-[9px]" style="color:var(--muted)">微米级相对位移</p>
      </div>
      <i class="fas fa-chevron-right arrow-connector"></i>
      <div class="step-card" id="step-1">
        <div class="flex items-center gap-2 mb-1"><i class="fas fa-bezier-curve step-icon text-xs" style="color:var(--muted)"></i><span class="text-[10px] font-medium">细颈弯曲</span></div>
        <p class="text-[9px]" style="color:var(--muted)">刚度降至 0.7⁴D</p>
      </div>
      <i class="fas fa-chevron-right arrow-connector"></i>
      <div class="step-card" id="step-2">
        <div class="flex items-center gap-2 mb-1"><i class="fas fa-fire step-icon text-xs" style="color:var(--muted)"></i><span class="text-[10px] font-medium">套筒摩擦</span></div>
        <p class="text-[9px]" style="color:var(--muted)">界面阻尼耗散</p>
      </div>
      <i class="fas fa-chevron-right arrow-connector"></i>
      <div class="step-card" id="step-3">
        <div class="flex items-center gap-2 mb-1"><i class="fas fa-temperature-high step-icon text-xs" style="color:var(--muted)"></i><span class="text-[10px] font-medium">热能耗散</span></div>
        <p class="text-[9px]" style="color:var(--muted)">动能 → 热能</p>
      </div>
      <i class="fas fa-chevron-right arrow-connector"></i>
      <div class="step-card" id="step-4">
        <div class="flex items-center gap-2 mb-1"><i class="fas fa-lock step-icon text-xs" style="color:var(--muted)"></i><span class="text-[10px] font-medium">螺纹自锁</span></div>
        <p class="text-[9px]" style="color:var(--muted)">法向力恒定</p>
      </div>
    </div>
  </div>
</main>

<script>
/* ===== 常量 ===== */
const CX = 350;           // 螺栓中心 X
const NECK_TOP = 140;     // 细颈顶部 Y
const NECK_BOT = 350;     // 细颈底部 Y
const NECK_W = 56;        // 细颈宽度 (0.7D)
const NECK_L = CX - NECK_W / 2;  // 322
const NECK_R = CX + NECK_W / 2;  // 378
const SL_WALL = 13;       // 套筒壁厚
const SL_OUTER_L = NECK_L - SL_WALL;  // 309
const SL_OUTER_R = NECK_R + SL_WALL;  // 391
const NECK_H = NECK_BOT - NECK_TOP;
const CP1_OFF = NECK_H * 0.36;  // 贝塞尔控制点偏移
const CP2_OFF = NECK_H * 0.64;

/* ===== 状态 ===== */
let amplitude = 18;
let frequency = 1.5;
let isPlaying = true;
let showForces = false;
let time = 0;
let totalEnergy = 0;
let particles = [];
const MAX_PARTICLES = 50;
const PARTICLE_POOL = [];

/* ===== DOM ===== */
let neckPath, sleeveLeftPath, sleeveRightPath, vibratingGroup;
let particleGroup, heatWavesGroup, vibArrowsGroup;
let boltThreadsGroup, nutThreadsGroup, threadIndicator;

/* ===== 初始化 ===== */
function init() {
  neckPath = document.getElementById('neck-path');
  sleeveLeftPath = document.getElementById('sleeve-left');
  sleeveRightPath = document.getElementById('sleeve-right');
  vibratingGroup = document.getElementById('vibrating-group');
  particleGroup = document.getElementById('particle-group');
  heatWavesGroup = document.getElementById('heat-waves');
  vibArrowsGroup = document.getElementById('vibration-arrows');
  boltThreadsGroup = document.getElementById('bolt-threads');
  nutThreadsGroup = document.getElementById('nut-threads');
  threadIndicator = document.getElementById('thread-indicator');

  // 生成螺纹
  generateThreads();
  // 初始化粒子池
  initParticlePool();
  // 设置控制器
  setupControls();
  // 初始绘制
  updateNeckPath(0);
  updateSleevePaths(0);
  // 开始动画
  requestAnimationFrame(animate);
}

/* ===== 生成螺纹剖面 ===== */
function generateThreads() {
  const toothCount = 10;
  const yStart = 565;
  const yEnd = 645;
  const toothH = 7;
  const spacing = (yEnd - yStart) / toothCount;
  let boltPath = '';
  let nutPath = '';

  for (let i = 0; i < toothCount; i++) {
    const y = yStart + i * spacing;
    // 螺栓外螺纹 (左侧)
    boltPath += `M ${310},${y + spacing * 0.15} L ${310 - toothH},${y + spacing * 0.35} L ${310},${y + spacing * 0.55} `;
    // 螺栓外螺纹 (右侧)
    boltPath += `M ${390},${y + spacing * 0.15} L ${390 + toothH},${y + spacing * 0.35} L ${390},${y + spacing * 0.55} `;
    // 螺母内螺纹 (左侧)
    nutPath += `M ${310},${y + spacing * 0.15} L ${310 + toothH * 0.7},${y + spacing * 0.35} L ${310},${y + spacing * 0.55} `;
    // 螺母内螺纹 (右侧)
    nutPath += `M ${390},${y + spacing * 0.15} L ${390 - toothH * 0.7},${y + spacing * 0.35} L ${390},${y + spacing * 0.55} `;
  }

  const boltEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  boltEl.setAttribute('d', boltPath);
  boltEl.setAttribute('fill', 'none');
  boltEl.setAttribute('stroke', 'rgba(139,157,175,.5)');
  boltEl.setAttribute('stroke-width', '1');
  boltThreadsGroup.appendChild(boltEl);

  const nutEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
  nutEl.setAttribute('d', nutPath);
  nutEl.setAttribute('fill', 'none');
  nutEl.setAttribute('stroke', 'rgba(139,157,175,.35)');
  nutEl.setAttribute('stroke-width', '1');
  nutThreadsGroup.appendChild(nutEl);
}

/* ===== 粒子池 ===== */
function initParticlePool() {
  for (let i = 0; i < MAX_PARTICLES; i++) {
    const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    circle.setAttribute('r', '0');
    circle.setAttribute('fill', '#FFB627');
    circle.setAttribute('opacity', '0');
    particleGroup.appendChild(circle);
    PARTICLE_POOL.push({ el: circle, active: false, x: 0, y: 0, vx: 0, vy: 0, life: 0, maxLife: 0, size: 0 });
  }
}

function spawnParticle(x, y, vx, vy) {
  for (let p of PARTICLE_POOL) {
    if (!p.active) {
      p.active = true;
      p.x = x; p.y = y;
      p.vx = vx; p.vy = vy;
      p.life = 1;
      p.maxLife = 0.6 + Math.random() * 0.5;
      p.size = 2 + Math.random() * 2.5;
      return;
    }
  }
}

function updateParticles(dt) {
  for (let p of PARTICLE_POOL) {
    if (!p.active) {
      p.el.setAttribute('opacity', '0');
      continue;
    }
    p.life -= dt / p.maxLife;
    if (p.life <= 0) { p.active = false; p.el.setAttribute('opacity', '0'); continue; }
    p.x += p.vx * dt;
    p.y += p.vy * dt;
    p.vy += 15 * dt; // 微重力
    const alpha = p.life * 0.8;
    const sz = Math.max(0.1, p.size * p.life);
    const r = Math.round(255);
    const g = Math.round(120 + 75 * p.life);
    const b = Math.round(39 * p.life);
    p.el.setAttribute('cx', p.x.toFixed(1));
    p.el.setAttribute('cy', p.y.toFixed(1));
    p.el.setAttribute('r', sz.toFixed(1));
    p.el.setAttribute('fill', `rgb(${r},${g},${b})`);
    p.el.setAttribute('opacity', alpha.toFixed(2));
  }
}

/* ===== 路径更新 ===== */
function updateNeckPath(dx) {
  const d = `M ${NECK_L},${NECK_TOP} C ${NECK_L},${NECK_TOP + CP1_OFF} ${NECK_L + dx * 0.7},${NECK_TOP + CP2_OFF} ${NECK_L + dx},${NECK_BOT} L ${NECK_R + dx},${NECK_BOT} C ${NECK_R + dx * 0.7},${NECK_TOP + CP2_OFF} ${NECK_R},${NECK_TOP + CP1_OFF} ${NECK_R},${NECK_TOP} Z`;
  neckPath.setAttribute('d', d);
}

function updateSleevePaths(dx) {
  // 左壁
  const dl = `M ${SL_OUTER_L},${NECK_TOP} C ${SL_OUTER_L},${NECK_TOP + CP1_OFF} ${SL_OUTER_L + dx * 0.7},${NECK_TOP + CP2_OFF} ${SL_OUTER_L + dx},${NECK_BOT} L ${NECK_L + dx},${NECK_BOT} C ${NECK_L + dx * 0.7},${NECK_TOP + CP2_OFF} ${NECK_L},${NECK_TOP + CP1_OFF} ${NECK_L},${NECK_TOP} Z`;
  sleeveLeftPath.setAttribute('d', dl);
  // 右壁
  const dr = `M ${NECK_R},${NECK_TOP} C ${NECK_R},${NECK_TOP + CP1_OFF} ${NECK_R + dx * 0.7},${NECK_TOP + CP2_OFF} ${NECK_R + dx},${NECK_BOT} L ${SL_OUTER_R + dx},${NECK_BOT} C ${SL_OUTER_R + dx * 0.7},${NECK_TOP + CP2_OFF} ${SL_OUTER_R},${NECK_TOP + CP1_OFF} ${SL_OUTER_R},${NECK_TOP} Z`;
  sleeveRightPath.setAttribute('d', dr);
}

/* ===== 热波纹 ===== */
let heatWaves = [];
function spawnHeatWave(x, y) {
  const ellipse = document.createElementNS('http://www.w3.org/2000/svg', 'ellipse');
  ellipse.setAttribute('cx', x);
  ellipse.setAttribute('cy', y);
  ellipse.setAttribute('rx', '4');
  ellipse.setAttribute('ry', '4');
  ellipse.setAttribute('fill', 'none');
  ellipse.setAttribute('stroke', 'rgba(255,182,39,0.3)');
  ellipse.setAttribute('stroke-width', '1');
  heatWavesGroup.appendChild(ellipse);
  heatWaves.push({ el: ellipse, x, y, r: 4, life: 1 });
  if (heatWaves.length > 8) {
    const old = heatWaves.shift();
    old.el.remove();
  }
}

function updateHeatWaves(dt) {
  for (let i = heatWaves.length - 1; i >= 0; i--) {
    const w = heatWaves[i];
    w.life -= dt * 1.2;
    w.r += dt * 35;
    if (w.life <= 0) { w.el.remove(); heatWaves.splice(i, 1); continue; }
    w.el.setAttribute('rx', w.r.toFixed(1));
    w.el.setAttribute('ry', (w.r * 0.6).toFixed(1));
    w.el.setAttribute('stroke', `rgba(255,182,39,${(w.life * 0.25).toFixed(2)})`);
    w.el.setAttribute('stroke-width', (w.life * 1.5).toFixed(1));
  }
}

/* ===== 动画循环 ===== */
let lastTime = 0;
let spawnAccum = 0;
let waveAccum = 0;

function animate(timestamp) {
  const dt = Math.min((timestamp - lastTime) / 1000, 0.05) || 0.016;
  lastTime = timestamp;

  if (isPlaying) {
    time += dt;
  }

  const dx = amplitude * Math.sin(2 * Math.PI * frequency * time);
  const vel = amplitude * 2 * Math.PI * frequency * Math.cos(2 * Math.PI * frequency * time);
  const absVel = Math.abs(vel);

  // 更新路径
  updateNeckPath(dx);
  updateSleevePaths(dx);

  // 更新振动组
  vibratingGroup.setAttribute('transform', `translate(${dx.toFixed(2)}, 0)`);

  // 细颈弯曲高亮:弯曲越大,铜色越亮
  const bendRatio = Math.abs(dx) / Math.max(1, amplitude);
  const copperBright = 0.7 + bendRatio * 0.3;
  neckPath.style.filter = bendRatio > 0.3 ? `drop-shadow(0 0 ${4 + bendRatio * 6}px rgba(212,165,116,${bendRatio * 0.4}))` : 'none';

  // 生成摩擦粒子 (速度越大,摩擦越强)
  if (isPlaying && absVel > 5) {
    spawnAccum += dt * absVel * 0.15;
    while (spawnAccum > 1) {
      spawnAccum -= 1;
      const side = Math.random() > 0.5 ? 1 : -1;
      const t = 0.2 + Math.random() * 0.6;
      const py = NECK_TOP + t * NECK_H;
      // 粒子从套筒-细颈界面产生
      const px = CX + side * (NECK_W / 2 + Math.random() * SL_WALL * 0.5) + dx * t;
      const speed = 20 + Math.random() * 40;
      const angle = side > 0 ? (Math.random() * 0.6 - 0.3) : (Math.PI + Math.random() * 0.6 - 0.3);
      spawnParticle(px, py, Math.cos(angle) * speed, Math.sin(angle) * speed - 10);
    }
  }

  // 生成热波纹
  if (isPlaying && absVel > 15) {
    waveAccum += dt * absVel * 0.02;
    while (waveAccum > 1) {
      waveAccum -= 1;
      const side = Math.random() > 0.5 ? 1 : -1;
      const t = 0.3 + Math.random() * 0.4;
      const py = NECK_TOP + t * NECK_H;
      const px = CX + side * (NECK_W / 2 + SL_WALL * 0.5) + dx * t;
      spawnHeatWave(px, py);
    }
  }

  // 更新粒子
  updateParticles(dt);
  updateHeatWaves(dt);

  // 振动箭头
  const arrowOpacity = Math.min(1, absVel / 30) * 0.6;
  vibArrowsGroup.setAttribute('opacity', arrowOpacity.toFixed(2));
  const arrowDir = vel > 0 ? 1 : -1;
  // 左箭头位置
  const leftArrow = document.getElementById('vib-arrow-left');
  const rightArrow = document.getElementById('vib-arrow-right');
  if (leftArrow && rightArrow) {
    leftArrow.setAttribute('transform', `translate(${arrowDir * 3}, 0) scale(${arrowDir > 0 ? 1 : -1}, 1)`);
    rightArrow.setAttribute('transform', `translate(${-arrowDir * 3}, 0) scale(${arrowDir > 0 ? -1 : 1}, 1)`);
  }

  // 螺纹稳定指示器脉冲
  const threadGlow = 0.15 + bendRatio * 0.1;
  threadIndicator.setAttribute('opacity', (0.5 + threadGlow).toFixed(2));

  // 力线显示
  if (showForces) {
    document.getElementById('preload-arrows').setAttribute('opacity', '0.5');
  } else {
    document.getElementById('preload-arrows').setAttribute('opacity', '0.15');
  }

  // 累计能量
  if (isPlaying) {
    totalEnergy += absVel * absVel * dt * 0.0001;
  }

  // 更新实时数值
  const dispUm = Math.abs(dx * 2.5).toFixed(0); // 缩放为微米
  document.getElementById('disp-val').textContent = `Δx = ${dispUm} μm`;
  document.getElementById('energy-val').textContent = `耗散: ${totalEnergy.toFixed(1)} mJ`;

  // 更新控制面板指标
  const dispPct = Math.min(100, (Math.abs(dx) / Math.max(1, amplitude)) * 100);
  const heatPct = Math.min(100, (absVel / (amplitude * 2 * Math.PI * frequency + 0.01)) * 100);
  document.getElementById('disp-percent').textContent = `${dispPct.toFixed(0)}%`;
  document.getElementById('disp-bar').style.width = `${dispPct}%`;
  document.getElementById('heat-percent').textContent = `${heatPct.toFixed(0)}%`;
  document.getElementById('heat-bar').style.width = `${heatPct}%`;
  // 螺纹稳定度始终很高(IFR效果)
  const stabPct = Math.max(88, 100 - bendRatio * 8);
  document.getElementById('stab-percent').textContent = `${stabPct.toFixed(0)}%`;
  document.getElementById('stab-bar').style.width = `${stabPct}%`;

  // 更新机理链高亮
  updateMechanismChain(bendRatio, absVel);

  requestAnimationFrame(animate);
}

/* ===== 机理链高亮 ===== */
function updateMechanismChain(bendRatio, absVel) {
  const steps = [
    document.getElementById('step-0'),
    document.getElementById('step-1'),
    document.getElementById('step-2'),
    document.getElementById('step-3'),
    document.getElementById('step-4')
  ];

  // 重置所有
  steps.forEach(s => { s.className = 'step-card'; });

  // 高亮当前活跃步骤
  if (amplitude > 0) {
    steps[0].classList.add('active'); // 振动始终活跃
  }
  if (bendRatio > 0.2) {
    steps[1].classList.add('active'); // 弯曲
  }
  if (absVel > 10) {
    steps[2].classList.add('active'); // 摩擦
    steps[3].classList.add('active'); // 耗散
  }
  // 螺纹稳定始终亮绿
  steps[4].classList.add('locked');
}

/* ===== 控制器 ===== */
function setupControls() {
  const ampSlider = document.getElementById('amp-slider');
  const freqSlider = document.getElementById('freq-slider');
  const playBtn = document.getElementById('play-btn');
  const resetBtn = document.getElementById('reset-btn');
  const forceBtn = document.getElementById('force-btn');

  ampSlider.addEventListener('input', function() {
    amplitude = parseFloat(this.value);
    document.getElementById('amp-display').textContent = `${amplitude} px`;
  });

  freqSlider.addEventListener('input', function() {
    frequency = parseFloat(this.value);
    document.getElementById('freq-display').textContent = `${frequency.toFixed(1)} Hz`;
  });

  playBtn.addEventListener('click', function() {
    isPlaying = !isPlaying;
    this.innerHTML = isPlaying ? '<i class="fas fa-pause text-sm"></i>' : '<i class="fas fa-play text-sm"></i>';
  });

  resetBtn.addEventListener('click', function() {
    time = 0;
    totalEnergy = 0;
    amplitude = 18;
    frequency = 1.5;
    ampSlider.value = 18;
    freqSlider.value = 1.5;
    document.getElementById('amp-display').textContent = '18 px';
    document.getElementById('freq-display').textContent = '1.5 Hz';
    isPlaying = true;
    playBtn.innerHTML = '<i class="fas fa-pause text-sm"></i>';
    // 清除粒子和波纹
    particles = [];
    for (let p of PARTICLE_POOL) { p.active = false; p.el.setAttribute('opacity', '0'); }
    for (let w of heatWaves) { w.el.remove(); }
    heatWaves = [];
  });

  forceBtn.addEventListener('click', function() {
    showForces = !showForces;
    this.style.borderColor = showForces ? 'var(--accent)' : 'var(--border)';
    this.style.color = showForces ? 'var(--accent)' : 'var(--muted)';
  });
}

/* ===== 启动 ===== */
document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>

实现说明

这是一个完整的 IFR 自锁螺栓原理演示动画,核心实现要点如下:

SVG 交叉剖面动画

  • 螺栓总成以纵截面形式呈现,包含螺栓头、细颈弹性杆(铜色)、高阻尼套筒(绿色)、被夹紧件(带剖面线)、螺纹啮合区和螺母
  • 细颈与套筒使用三次贝塞尔曲线动态生成路径,模拟悬臂梁弯曲形态——顶部固定(零斜率约束),底部随振动位移偏移
  • 振动组(平板+螺母+下段螺杆)通过 transform: translate(dx) 整体平移,确保螺纹区始终对齐——这正是 IFR 的核心视觉叙事

粒子与热波纹系统

  • 采用对象池(50 个预创建 SVG circle)管理摩擦粒子,避免频繁 DOM 创建/销毁
  • 粒子生成速率与振动速度 |v| 成正比,物理直觉正确:速度越大,摩擦越剧烈
  • 热波纹使用动态椭圆扩散+渐隐,直观展示"动能→热能"转化过程

交互控制

  • 振幅/频率滑块实时调节振动参数
  • 播放/暂停/重置按钮控制动画状态
  • 力线切换按钮显示预紧力方向
  • 实时指标面板:相对位移、阻尼耗散、螺纹稳定度(始终 ≥88%,体现 IFR 特性)

机理链可视化

  • 底部五步机理链根据动画状态实时高亮:振动→弯曲→摩擦→耗散→自锁
  • 弯曲活跃时细颈发光增强,摩擦活跃时粒子与波纹加密,螺纹稳定始终绿色锁定
积分规则:第一轮对话扣减6分,后续每轮扣4分