<!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)">振动能量 → 锁紧动力 | 零附加元件 · 零胶水 · 零复杂工序</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分
等待动画代码生成...
