分享图
A
动画渲染工坊
就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>行星滚柱丝杠 · IFR原理演示</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=IBM+Plex+Mono:wght@300;400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
  theme: {
    extend: {
      fontFamily: { display: ['Syne','sans-serif'], mono: ['IBM Plex Mono','monospace'] }
    }
  }
}
</script>
<style>
:root {
  --bg:#070b12;--surface:#0d1219;--card:#111827;--border:#1c2536;
  --fg:#e2e8f0;--muted:#64748b;--accent:#00e5a0;--accent2:#00ffc8;
  --metal:#3d4a5c;--metal-light:#6b7d94;--danger:#ff4757;--force:#ff6b35;
}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'IBM Plex Mono',monospace;background:var(--bg);color:var(--fg);min-height:100vh;overflow-x:hidden}
.font-display{font-family:'Syne',sans-serif}
body::before{content:'';position:fixed;inset:0;background:radial-gradient(ellipse at 25% 40%,rgba(0,229,160,.04),transparent 65%),radial-gradient(ellipse at 75% 60%,rgba(0,255,200,.02),transparent 55%);pointer-events:none;z-index:0}
.card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:20px}
.glow-text{color:var(--accent2);text-shadow:0 0 20px rgba(0,255,200,.3)}
.slider-track{-webkit-appearance:none;appearance:none;width:100%;height:6px;border-radius:3px;background:var(--border);outline:none}
.slider-track::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:18px;height:18px;border-radius:50%;background:var(--accent);cursor:pointer;box-shadow:0 0 10px rgba(0,229,160,.5)}
.slider-track::-moz-range-thumb{width:18px;height:18px;border-radius:50%;background:var(--accent);cursor:pointer;border:none}
.btn{padding:8px 18px;border-radius:8px;border:1px solid var(--border);background:var(--surface);color:var(--fg);cursor:pointer;font-family:inherit;font-size:13px;transition:all .2s}
.btn:hover{border-color:var(--accent);color:var(--accent);box-shadow:0 0 12px rgba(0,229,160,.15)}
.btn.active{background:rgba(0,229,160,.12);border-color:var(--accent);color:var(--accent)}
.seq-step{display:flex;align-items:center;gap:6px;padding:4px 10px;border-radius:6px;font-size:11px;background:rgba(255,255,255,.03);border:1px solid transparent;transition:all .4s}
.seq-step.lit{border-color:var(--accent);background:rgba(0,229,160,.1);color:var(--accent2)}
.seq-dot{width:6px;height:6px;border-radius:50%;background:var(--muted);transition:all .4s}
.seq-step.lit .seq-dot{background:var(--accent);box-shadow:0 0 8px var(--accent)}
@keyframes pulse{0%,100%{opacity:.6}50%{opacity:1}}
.pulse-anim{animation:pulse 2s ease-in-out infinite}
@media(prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
</style>
</head>
<body>

<!-- 背景网格 -->
<svg style="position:fixed;inset:0;width:100%;height:100%;pointer-events:none;z-index:0;opacity:.03">
<defs><pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="#fff" stroke-width=".5"/>
</pattern></defs>
<rect width="100%" height="100%" fill="url(#grid)"/>
</svg>

<div class="relative z-10 max-w-[1440px] mx-auto px-4 py-6">

  <!-- 头部 -->
  <header class="mb-6">
    <div class="flex items-end gap-4 flex-wrap">
      <h1 class="font-display font-extrabold text-3xl md:text-4xl tracking-tight" style="color:var(--fg)">
        行星滚柱丝杠
      </h1>
      <span class="font-display font-semibold text-sm md:text-base tracking-wide" style="color:var(--accent)">
        PLANETARY ROLLER SCREW
      </span>
    </div>
    <p class="mt-2 text-sm" style="color:var(--muted)">
      高推力直线执行机构 · 最终理想解(IFR)原理演示 &mdash; 线接触破除"大推力 vs 小体积"物理矛盾
    </p>
  </header>

  <!-- 主内容网格 -->
  <div class="grid gap-4" style="grid-template-columns:1fr 300px;grid-template-rows:auto auto">

    <!-- 截面动画 (Hero) -->
    <div class="card row-span-1" style="min-height:480px">
      <div class="flex items-center gap-2 mb-3">
        <span class="inline-block w-2 h-2 rounded-full" style="background:var(--accent)"></span>
        <span class="text-xs font-semibold tracking-wider" style="color:var(--accent)">端面截面 · 实时运转</span>
      </div>
      <div class="flex justify-center">
        <svg id="crossSVG" viewBox="0 0 500 500" style="width:100%;max-width:480px;height:auto"></svg>
      </div>
    </div>

    <!-- 参数面板 -->
    <div class="card flex flex-col gap-4">
      <div>
        <div class="text-xs mb-2" style="color:var(--muted)">核心参数</div>
        <div class="space-y-2">
          <div class="flex justify-between text-sm"><span>丝杠导程</span><span class="glow-text font-semibold">5 mm</span></div>
          <div class="flex justify-between text-sm"><span>滚柱接触角</span><span class="glow-text font-semibold">45°</span></div>
          <div class="flex justify-between text-sm"><span>滚柱数量</span><span class="glow-text font-semibold">8</span></div>
          <div class="flex justify-between text-sm"><span>接触类型</span><span class="glow-text font-semibold">线接触</span></div>
        </div>
      </div>
      <hr style="border-color:var(--border)">
      <div>
        <div class="text-xs mb-2" style="color:var(--muted)">实时数据</div>
        <div class="space-y-2">
          <div class="flex justify-between text-sm"><span>输出推力</span><span id="dataForce" style="color:var(--force)" class="font-semibold">4000 N</span></div>
          <div class="flex justify-between text-sm"><span>滚柱应力</span><span id="dataStress" class="font-semibold" style="color:var(--accent)">低</span></div>
          <div class="flex justify-between text-sm"><span>螺母位移</span><span id="dataDisp" class="font-semibold">0.0 mm</span></div>
          <div class="flex justify-between text-sm"><span>运行状态</span><span id="dataStatus" class="font-semibold" style="color:var(--accent)">运转中</span></div>
        </div>
      </div>
      <hr style="border-color:var(--border)">
      <div>
        <div class="text-xs mb-2" style="color:var(--muted)">IFR 核心洞察</div>
        <p class="text-xs leading-relaxed" style="color:var(--muted)">
          传统滚珠丝杠为<span style="color:var(--danger)">点接触</span>,接触面积仅 ~0.01mm²;行星滚柱丝杠为<span style="color:var(--accent2)">线接触</span>,面积达 ~0.1mm²,<span class="font-semibold" style="color:var(--accent)">提升 10 倍以上</span>。同等推力下,接触应力降低一个数量级,在极小体积内即可承载 4000N+。
        </p>
      </div>
      <div>
        <div class="text-xs mb-2" style="color:var(--muted)">适用边界</div>
        <p class="text-xs leading-relaxed" style="color:var(--muted)">
          行程 &le; 500mm(避免细长丝杠临界转速共振);安装平行度要求 &le; 0.02mm/m。
        </p>
      </div>
    </div>

    <!-- 侧视剖面 -->
    <div class="card">
      <div class="flex items-center gap-2 mb-3">
        <span class="inline-block w-2 h-2 rounded-full" style="background:var(--force)"></span>
        <span class="text-xs font-semibold tracking-wider" style="color:var(--force)">纵剖示意 · 运动转换</span>
      </div>
      <div class="flex justify-center">
        <svg id="sideSVG" viewBox="0 0 680 200" style="width:100%;max-width:660px;height:auto"></svg>
      </div>
      <!-- 动作时序 -->
      <div class="mt-4 flex flex-wrap gap-2" id="seqBar">
        <div class="seq-step" data-step="0"><span class="seq-dot"></span>接收指令</div>
        <div class="seq-step" data-step="1"><span class="seq-dot"></span>输出扭矩</div>
        <div class="seq-step" data-step="2"><span class="seq-dot"></span>丝杠旋转</div>
        <div class="seq-step" data-step="3"><span class="seq-dot"></span>螺母推出</div>
        <div class="seq-step" data-step="4"><span class="seq-dot"></span>力传感反馈</div>
        <div class="seq-step" data-step="5"><span class="seq-dot"></span>保压/换向</div>
      </div>
    </div>

    <!-- 接触应力对比 -->
    <div class="card">
      <div class="text-xs mb-3 font-semibold tracking-wider" style="color:var(--muted)">接触应力对比</div>
      <svg id="compareSVG" viewBox="0 0 260 220" style="width:100%;max-width:260px;height:auto;margin:0 auto;display:block"></svg>
    </div>
  </div>

  <!-- 控制栏 -->
  <div class="card mt-4">
    <div class="flex flex-wrap items-center gap-6">
      <div class="flex-1 min-w-[200px]">
        <label class="text-xs block mb-1" style="color:var(--muted)">推力 <span id="forceLabel" class="font-semibold" style="color:var(--force)">4000 N</span></label>
        <input type="range" id="forceSlider" min="0" max="6000" value="4000" step="100" class="slider-track w-full">
      </div>
      <div class="flex-1 min-w-[160px]">
        <label class="text-xs block mb-1" style="color:var(--muted)">动画速度 <span id="speedLabel" class="font-semibold" style="color:var(--accent)">1.0x</span></label>
        <input type="range" id="speedSlider" min="0" max="30" value="10" step="1" class="slider-track w-full">
      </div>
      <div class="flex gap-2">
        <button id="playBtn" class="btn active" aria-label="播放/暂停"><i class="fas fa-pause"></i></button>
        <button id="dirBtn" class="btn" aria-label="换向"><i class="fas fa-exchange-alt"></i></button>
      </div>
    </div>
  </div>

</div>

<script>
/* ===== 全局状态 ===== */
const S = {
  time: 0, screwAngle: 0, playing: true, speed: 1.0,
  force: 4000, dir: 1, nutPos: 0.5
};

/* ===== 常量 ===== */
const CX = 250, CY = 250; // 截面中心
const NUT_OR = 215, NUT_IR = 192; // 螺母外径/内径
const ORBIT_R = 152; // 滚柱轨道半径
const ROLLER_R = 33; // 滚柱半径
const SCREW_OR = 115, SCREW_IR = 75; // 丝杠外径/内径
const N_ROLLERS = 8;
const SCREW_TEETH = 24, ROLLER_TEETH = 12, NUT_TEETH = 32;

/* ===== SVG 辅助 ===== */
const NS = 'http://www.w3.org/2000/svg';
function svgEl(tag, attrs, parent) {
  const el = document.createElementNS(NS, tag);
  for (const [k, v] of Object.entries(attrs || {})) el.setAttribute(k, v);
  if (parent) parent.appendChild(el);
  return el;
}

/* 生成齿轮路径 */
function gearPath(cx, cy, baseR, tipR, n, rot) {
  const pts = [];
  for (let i = 0; i < n; i++) {
    const a = (i / n) * Math.PI * 2 + rot;
    const hw = (0.35 / n) * Math.PI * 2;
    pts.push([cx + baseR * Math.cos(a - hw * 1.1), cy + baseR * Math.sin(a - hw * 1.1)]);
    pts.push([cx + tipR * Math.cos(a - hw * 0.5), cy + tipR * Math.sin(a - hw * 0.5)]);
    pts.push([cx + tipR * Math.cos(a + hw * 0.5), cy + tipR * Math.sin(a + hw * 0.5)]);
    pts.push([cx + baseR * Math.cos(a + hw * 1.1), cy + baseR * Math.sin(a + hw * 1.1)]);
  }
  return 'M ' + pts.map(p => p[0].toFixed(2) + ' ' + p[1].toFixed(2)).join(' L ') + ' Z';
}

/* ===== 截面视图构建 ===== */
const crossEls = {};
function buildCrossSection() {
  const svg = document.getElementById('crossSVG');
  svg.innerHTML = '';

  // 定义滤镜
  const defs = svgEl('defs', {}, svg);
  // 发光滤镜
  const fGlow = svgEl('filter', { id: 'glow', x: '-50%', y: '-50%', width: '200%', height: '200%' }, defs);
  svgEl('feGaussianBlur', { in: 'SourceGraphic', stdDeviation: '4', result: 'blur' }, fGlow);
  const fMerge = svgEl('feMerge', {}, fGlow);
  svgEl('feMergeNode', { in: 'blur' }, fMerge);
  svgEl('feMergeNode', { in: 'SourceGraphic' }, fMerge);

  // 强发光
  const fGlow2 = svgEl('filter', { id: 'glowStrong', x: '-50%', y: '-50%', width: '200%', height: '200%' }, defs);
  svgEl('feGaussianBlur', { in: 'SourceGraphic', stdDeviation: '8', result: 'blur' }, fGlow2);
  const fMerge2 = svgEl('feMerge', {}, fGlow2);
  svgEl('feMergeNode', { in: 'blur' }, fMerge2);
  svgEl('feMergeNode', { in: 'SourceGraphic' }, fMerge2);

  // 中心十字参考线
  svgEl('line', { x1: CX - 230, y1: CY, x2: CX + 230, y2: CY, stroke: '#1c2536', 'stroke-width': 0.5, 'stroke-dasharray': '4,6' }, svg);
  svgEl('line', { x1: CX, y1: CY - 230, x2: CX, y2: CY + 230, stroke: '#1c2536', 'stroke-width': 0.5, 'stroke-dasharray': '4,6' }, svg);

  // 螺母外壳(静态)
  svgEl('circle', { cx: CX, cy: CY, r: NUT_OR, fill: 'none', stroke: '#2a3444', 'stroke-width': 22 }, svg);
  // 螺母内齿
  crossEls.nutTeeth = svgEl('path', {
    d: gearPath(CX, CY, NUT_IR, NUT_IR + 10, NUT_TEETH, 0),
    fill: '#2a3444', stroke: '#3d4a5c', 'stroke-width': 0.5
  }, svg);

  // 保持架(动态)
  crossEls.cage = svgEl('circle', {
    cx: CX, cy: CY, r: ORBIT_R, fill: 'none', stroke: '#4a5568',
    'stroke-width': 1.5, 'stroke-dasharray': '8,12', opacity: 0.5
  }, svg);

  // 接触高亮组(在滚柱下面绘制,产生光晕效果)
  crossEls.contactGroup = svgEl('g', {}, svg);

  // 丝杠组(动态旋转)
  crossEls.screwGroup = svgEl('g', {}, svg);
  // 丝杠齿
  crossEls.screwTeeth = svgEl('path', {
    d: gearPath(CX, CY, SCREW_IR, SCREW_OR, SCREW_TEETH, 0),
    fill: '#2d3748', stroke: '#4a5568', 'stroke-width': 0.5
  }, crossEls.screwGroup);
  // 丝杠轴心
  svgEl('circle', { cx: CX, cy: CY, r: SCREW_IR - 5, fill: '#1a202c', stroke: '#2d3748', 'stroke-width': 1 }, crossEls.screwGroup);
  // 丝杠中心孔
  svgEl('circle', { cx: CX, cy: CY, r: 25, fill: '#0d1219', stroke: '#2d3748', 'stroke-width': 1 }, crossEls.screwGroup);
  // 键槽标记
  svgEl('rect', { x: CX - 4, y: CY - 25, width: 8, height: 12, fill: '#2d3748', rx: 1 }, crossEls.screwGroup);

  // 滚柱轨道组(公转)
  crossEls.rollerOrbitGroup = svgEl('g', {}, svg);
  crossEls.rollers = [];
  crossEls.rollerTeeth = [];

  for (let i = 0; i < N_ROLLERS; i++) {
    const angle = (i / N_ROLLERS) * Math.PI * 2;
    const rx = CX + ORBIT_R * Math.cos(angle);
    const ry = CY + ORBIT_R * Math.sin(angle);

    const rg = svgEl('g', {}, crossEls.rollerOrbitGroup);

    // 滚柱齿(外)
    const outerTeeth = svgEl('path', {
      d: gearPath(rx, ry, ROLLER_R - 6, ROLLER_R, ROLLER_TEETH, 0),
      fill: '#1a3a3a', stroke: '#00e5a0', 'stroke-width': 0.5
    }, rg);
    // 滚柱体
    svgEl('circle', { cx: rx, cy: ry, r: ROLLER_R - 8, fill: '#0f2828', stroke: '#1a4a4a', 'stroke-width': 0.5 }, rg);
    // 滚柱中心
    svgEl('circle', { cx: rx, cy: ry, r: 4, fill: '#00e5a0', opacity: 0.3 }, rg);

    crossEls.rollers.push({ group: rg, outerTeeth, cx: rx, cy: ry, baseAngle: angle });
  }

  // 标注
  svgEl('text', { x: CX, y: CY + NUT_OR + 28, 'text-anchor': 'middle', fill: '#64748b', 'font-size': 11, 'font-family': 'IBM Plex Mono' }, svg).textContent = '螺母 (Nut)';
  svgEl('text', { x: CX, y: CY + 6, 'text-anchor': 'middle', fill: '#64748b', 'font-size': 10, 'font-family': 'IBM Plex Mono' }, svg).textContent = '丝杠';

  // 接触角标注弧线
  crossEls.angleArc = svgEl('path', {
    d: '', fill: 'none', stroke: '#fbbf24', 'stroke-width': 1, 'stroke-dasharray': '3,3', opacity: 0
  }, svg);
  crossEls.angleLabel = svgEl('text', {
    x: 0, y: 0, fill: '#fbbf24', 'font-size': 10, 'font-family': 'IBM Plex Mono', opacity: 0
  }, svg);
}

/* ===== 侧视图构建 ===== */
const sideEls = {};
function buildSideView() {
  const svg = document.getElementById('sideSVG');
  svg.innerHTML = '';

  const yMid = 95;

  // 电机
  svgEl('rect', { x: 8, y: yMid - 38, width: 72, height: 76, rx: 6, fill: '#1a202c', stroke: '#3d4a5c', 'stroke-width': 1.5 }, svg);
  svgEl('text', { x: 44, y: yMid + 4, 'text-anchor': 'middle', fill: '#64748b', 'font-size': 13, 'font-weight': 600, 'font-family': 'Syne' }, svg).textContent = 'M';

  // 电机轴
  svgEl('rect', { x: 80, y: yMid - 6, width: 30, height: 12, rx: 2, fill: '#4a5568' }, svg);

  // 联轴器
  svgEl('rect', { x: 108, y: yMid - 12, width: 10, height: 24, rx: 3, fill: '#2d3748', stroke: '#00e5a0', 'stroke-width': 1 }, svg);
  svgEl('rect', { x: 120, y: yMid - 12, width: 10, height: 24, rx: 3, fill: '#2d3748', stroke: '#00e5a0', 'stroke-width': 1 }, svg);
  // 弹性元件
  svgEl('line', { x1: 118, y1: yMid - 8, x2: 120, y2: yMid + 8, stroke: '#00e5a0', 'stroke-width': 1.5 }, svg);

  // 丝杠轴
  sideEls.screwShaft = svgEl('g', {}, svg);
  svgEl('rect', { x: 130, y: yMid - 10, width: 420, height: 20, rx: 3, fill: '#2d3748', stroke: '#4a5568', 'stroke-width': 1 }, sideEls.screwShaft);

  // 丝杠螺纹线(动态)
  sideEls.screwThreads = svgEl('g', {}, sideEls.screwShaft);
  for (let i = 0; i < 28; i++) {
    svgEl('line', {
      x1: 138 + i * 15, y1: yMid - 10,
      x2: 145 + i * 15, y2: yMid + 10,
      stroke: '#5a6a80', 'stroke-width': 1, opacity: 0.6
    }, sideEls.screwThreads);
  }

  // 螺母组(可滑动)
  sideEls.nutGroup = svgEl('g', {}, svg);
  // 螺母外壳
  svgEl('rect', { x: 0, y: yMid - 35, width: 90, height: 70, rx: 4, fill: '#1a2838', stroke: '#3d5a6c', 'stroke-width': 1.5 }, sideEls.nutGroup);
  // 螺母内腔
  svgEl('rect', { x: 4, y: yMid - 12, width: 82, height: 24, rx: 2, fill: '#0d1219' }, sideEls.nutGroup);

  // 滚柱(在螺母内可见)
  sideEls.sideRollers = [];
  for (let i = 0; i < 4; i++) {
    const ry = yMid - 24 + i * 16;
    if (Math.abs(ry - yMid) < 8) continue;
    const rc = svgEl('circle', { cx: 45, cy: ry, r: 6, fill: '#0f2828', stroke: '#00e5a0', 'stroke-width': 1 }, sideEls.nutGroup);
    sideEls.sideRollers.push(rc);
  }

  // 直线导轨
  svgEl('rect', { x: 130, y: yMid + 40, width: 420, height: 6, rx: 2, fill: '#1a202c', stroke: '#2d3748', 'stroke-width': 1 }, svg);
  svgEl('rect', { x: 130, y: yMid + 52, width: 420, height: 6, rx: 2, fill: '#1a202c', stroke: '#2d3748', 'stroke-width': 1 }, svg);

  // 导轨滑块
  sideEls.sliderBlock = svgEl('g', {}, svg);
  svgEl('rect', { x: 0, y: yMid + 36, width: 50, height: 28, rx: 3, fill: '#2d3748', stroke: '#4a5568', 'stroke-width': 1 }, sideEls.sliderBlock);
  svgEl('rect', { x: 0, y: yMid + 48, width: 50, height: 16, rx: 2, fill: '#2d3748', stroke: '#4a5568', 'stroke-width': 1 }, sideEls.sliderBlock);

  // 推力箭头
  sideEls.forceArrow = svgEl('g', {}, svg);
  svgEl('line', { x1: 0, y1: yMid, x2: 70, y2: yMid, stroke: '#ff6b35', 'stroke-width': 3 }, sideEls.forceArrow);
  svgEl('polygon', { points: '70,-8 86,0 70,8', fill: '#ff6b35', transform: `translate(0,${yMid})` }, sideEls.forceArrow);
  sideEls.forceLabel = svgEl('text', {
    x: 80, y: yMid - 14, fill: '#ff6b35', 'font-size': 12, 'font-weight': 600, 'font-family': 'IBM Plex Mono'
  }, sideEls.forceArrow);
  sideEls.forceLabel.textContent = '4000N';

  // 缸体轮廓
  svgEl('rect', { x: 125, y: yMid - 48, width: 430, height: 108, rx: 6, fill: 'none', stroke: '#1c2536', 'stroke-width': 1, 'stroke-dasharray': '6,4' }, svg);
  svgEl('text', { x: 340, y: yMid + 70, 'text-anchor': 'middle', fill: '#3d4a5c', 'font-size': 10, 'font-family': 'IBM Plex Mono' }, svg).textContent = '缸体';

  // 力传感器标记
  sideEls.sensor = svgEl('g', {}, svg);
  svgEl('rect', { x: 0, y: yMid - 42, width: 24, height: 12, rx: 2, fill: '#1a202c', stroke: '#fbbf24', 'stroke-width': 1 }, sideEls.sensor);
  svgEl('text', { x: 12, y: yMid - 34, 'text-anchor': 'middle', fill: '#fbbf24', 'font-size': 7, 'font-family': 'IBM Plex Mono' }, sideEls.sensor).textContent = 'F';
}

/* ===== 接触对比构建 ===== */
function buildCompare() {
  const svg = document.getElementById('compareSVG');
  svg.innerHTML = '';

  // 左侧:滚珠丝杠
  svgEl('text', { x: 65, y: 18, 'text-anchor': 'middle', fill: '#ff4757', 'font-size': 11, 'font-weight': 600, 'font-family': 'Syne' }, svg).textContent = '滚珠丝杠';

  // 滚道
  svgEl('path', { d: 'M 20,60 Q 65,35 110,60', fill: 'none', stroke: '#3d4a5c', 'stroke-width': 3 }, svg);
  svgEl('path', { d: 'M 20,100 Q 65,125 110,100', fill: 'none', stroke: '#3d4a5c', 'stroke-width': 3 }, svg);
  // 滚珠
  svgEl('circle', { cx: 65, cy: 80, r: 14, fill: '#2d3748', stroke: '#5a6a80', 'stroke-width': 1.5 }, svg);
  // 点接触
  compareEls.ballContactTop = svgEl('circle', { cx: 65, cy: 67, r: 3, fill: '#ff4757', filter: 'url(#glow)' }, svg);
  compareEls.ballContactBot = svgEl('circle', { cx: 65, cy: 93, r: 3, fill: '#ff4757', filter: 'url(#glow)' }, svg);
  // 标注
  svgEl('text', { x: 65, y: 145, 'text-anchor': 'middle', fill: '#ff4757', 'font-size': 10, 'font-family': 'IBM Plex Mono' }, svg).textContent = '点接触';
  svgEl('text', { x: 65, y: 160, 'text-anchor': 'middle', fill: '#64748b', 'font-size': 9, 'font-family': 'IBM Plex Mono' }, svg).textContent = '~0.01mm²';

  // 应力条
  svgEl('rect', { x: 20, y: 172, width: 90, height: 8, rx: 3, fill: '#1a202c' }, svg);
  compareEls.ballStressBar = svgEl('rect', { x: 20, y: 172, width: 72, height: 8, rx: 3, fill: '#ff4757' }, svg);
  compareEls.ballStressLabel = svgEl('text', { x: 65, y: 196, 'text-anchor': 'middle', fill: '#ff4757', 'font-size': 9, 'font-weight': 600, 'font-family': 'IBM Plex Mono' }, svg);

  // 右侧:滚柱丝杠
  svgEl('text', { x: 195, y: 18, 'text-anchor': 'middle', fill: '#00e5a0', 'font-size': 11, 'font-weight': 600, 'font-family': 'Syne' }, svg).textContent = '滚柱丝杠';

  // 滚道
  svgEl('path', { d: 'M 150,60 Q 195,35 240,60', fill: 'none', stroke: '#3d4a5c', 'stroke-width': 3 }, svg);
  svgEl('path', { d: 'M 150,100 Q 195,125 240,100', fill: 'none', stroke: '#3d4a5c', 'stroke-width': 3 }, svg);
  // 滚柱
  svgEl('rect', { x: 183, y: 63, width: 24, height: 34, rx: 4, fill: '#0f2828', stroke: '#00e5a0', 'stroke-width': 1.5 }, svg);
  // 线接触
  compareEls.rollerContactTop = svgEl('line', { x1: 187, y1: 66, x2: 203, y2: 66, stroke: '#00ffc8', 'stroke-width': 3, 'stroke-linecap': 'round', filter: 'url(#glow)' }, svg);
  compareEls.rollerContactBot = svgEl('line', { x1: 187, y1: 94, x2: 203, y2: 94, stroke: '#00ffc8', 'stroke-width': 3, 'stroke-linecap': 'round', filter: 'url(#glow)' }, svg);
  // 标注
  svgEl('text', { x: 195, y: 145, 'text-anchor': 'middle', fill: '#00e5a0', 'font-size': 10, 'font-family': 'IBM Plex Mono' }, svg).textContent = '线接触';
  svgEl('text', { x: 195, y: 160, 'text-anchor': 'middle', fill: '#64748b', 'font-size': 9, 'font-family': 'IBM Plex Mono' }, svg).textContent = '~0.1mm²';

  // 应力条
  svgEl('rect', { x: 150, y: 172, width: 90, height: 8, rx: 3, fill: '#1a202c' }, svg);
  compareEls.rollerStressBar = svgEl('rect', { x: 150, y: 172, width: 15, height: 8, rx: 3, fill: '#00e5a0' }, svg);
  compareEls.rollerStressLabel = svgEl('text', { x: 195, y: 196, 'text-anchor': 'middle', fill: '#00e5a0', 'font-size': 9, 'font-weight': 600, 'font-family': 'IBM Plex Mono' }, svg);

  // 对比箭头
  svgEl('text', { x: 130, y: 84, 'text-anchor': 'middle', fill: '#fbbf24', 'font-size': 14, 'font-weight': 800, 'font-family': 'Syne' }, svg).textContent = 'VS';
}
const compareEls = {};

/* ===== 更新函数 ===== */
function updateCrossSection() {
  const sa = S.screwAngle * Math.PI / 180;
  const orbitA = S.screwAngle * 0.35 * Math.PI / 180;

  // 丝杠旋转
  crossEls.screwTeeth.setAttribute('d', gearPath(CX, CY, SCREW_IR, SCREW_OR, SCREW_TEETH, sa));
  crossEls.screwGroup.setAttribute('transform', `rotate(${S.screwAngle * 0.2}, ${CX}, ${CY})`);

  // 保持架旋转
  crossEls.cage.setAttribute('transform', `rotate(${S.screwAngle * 0.35}, ${CX}, ${CY})`);

  // 螺母内齿微调(螺母不旋转,但齿形随丝杠匹配)
  crossEls.nutTeeth.setAttribute('d', gearPath(CX, CY, NUT_IR, NUT_IR + 10, NUT_TEETH, 0));

  // 滚柱公转 + 自转
  crossEls.contactGroup.innerHTML = '';
  const selfRot = -S.screwAngle * 1.8;

  for (let i = 0; i < N_ROLLERS; i++) {
    const baseA = (i / N_ROLLERS) * Math.PI * 2;
    const currentA = baseA + orbitA;
    const rx = CX + ORBIT_R * Math.cos(currentA);
    const ry = CY + ORBIT_R * Math.sin(currentA);

    const roller = crossEls.rollers[i];
    roller.group.setAttribute('transform', `translate(${rx - roller.cx}, ${ry - roller.cy})`);
    roller.outerTeeth.setAttribute('d', gearPath(rx, ry, ROLLER_R - 6, ROLLER_R, ROLLER_TEETH, selfRot * Math.PI / 180));

    // 接触高亮:滚柱-丝杠
    const contactAngleS = currentA;
    const csX = CX + (SCREW_OR + 2) * Math.cos(contactAngleS);
    const csY = CY + (SCREW_OR + 2) * Math.sin(contactAngleS);
    const perpAngle = contactAngleS + Math.PI / 2;
    const lineLen = 14;
    const glowIntensity = Math.min(1, S.force / 5000);

    svgEl('line', {
      x1: csX - lineLen * Math.cos(perpAngle), y1: csY - lineLen * Math.sin(perpAngle),
      x2: csX + lineLen * Math.cos(perpAngle), y2: csY + lineLen * Math.sin(perpAngle),
      stroke: '#00ffc8', 'stroke-width': 2.5 + glowIntensity * 2,
      'stroke-linecap': 'round', opacity: 0.4 + glowIntensity * 0.6,
      filter: 'url(#glow)'
    }, crossEls.contactGroup);

    // 接触高亮:滚柱-螺母
    const cnX = CX + (NUT_IR - 2) * Math.cos(contactAngleS);
    const cnY = CY + (NUT_IR - 2) * Math.sin(contactAngleS);
    svgEl('line', {
      x1: cnX - lineLen * Math.cos(perpAngle), y1: cnY - lineLen * Math.sin(perpAngle),
      x2: cnX + lineLen * Math.cos(perpAngle), y2: cnY + lineLen * Math.sin(perpAngle),
      stroke: '#00ffc8', 'stroke-width': 2.5 + glowIntensity * 2,
      'stroke-linecap': 'round', opacity: 0.4 + glowIntensity * 0.6,
      filter: 'url(#glow)'
    }, crossEls.contactGroup);
  }

  // 接触角标注(在第一个滚柱处)
  if (S.force > 500) {
    const a0 = orbitA;
    const labelR = ORBIT_R + ROLLER_R + 22;
    const lx = CX + labelR * Math.cos(a0);
    const ly = CY + labelR * Math.sin(a0);
    crossEls.angleLabel.setAttribute('x', lx);
    crossEls.angleLabel.setAttribute('y', ly);
    crossEls.angleLabel.setAttribute('opacity', 0.8);
    crossEls.angleLabel.textContent = '45°';
    crossEls.angleArc.setAttribute('opacity', 0.5);
  } else {
    crossEls.angleLabel.setAttribute('opacity', 0);
    crossEls.angleArc.setAttribute('opacity', 0);
  }
}

function updateSideView() {
  const nutTravel = 140;
  const baseX = 240;
  const nutX = baseX + (S.nutPos - 0.5) * nutTravel * S.dir;
  const sliderX = nutX - 10;
  const arrowX = nutX + 90;

  sideEls.nutGroup.setAttribute('transform', `translate(${nutX - baseX}, 0)`);
  sideEls.sliderBlock.setAttribute('transform', `translate(${sliderX - baseX + 20}, 0)`);
  sideEls.forceArrow.setAttribute('transform', `translate(${arrowX - baseX - 90}, 0)`);
  sideEls.sensor.setAttribute('transform', `translate(${nutX - baseX - 12}, 0)`);

  // 螺纹线动画
  const threadOffset = (S.screwAngle * 0.5) % 15;
  sideEls.screwThreads.setAttribute('transform', `translate(${threadOffset}, 0)`);

  // 力箭头标签
  sideEls.forceLabel.textContent = S.force + 'N';

  // 力箭头大小随推力变化
  const arrowScale = 0.5 + (S.force / 6000) * 0.8;
  sideEls.forceArrow.setAttribute('opacity', Math.min(1, S.force / 500));

  // 力传感器闪烁
  const sensorPulse = Math.sin(S.time * 4) * 0.3 + 0.7;
  sideEls.sensor.setAttribute('opacity', S.force > 200 ? sensorPulse : 0.3);
}

function updateCompare() {
  const forceRatio = S.force / 6000;
  // 滚珠丝杠应力条
  const ballStressW = Math.min(90, 10 + forceRatio * 85);
  compareEls.ballStressBar.setAttribute('width', ballStressW);
  const ballColor = forceRatio < 0.4 ? '#fbbf24' : forceRatio < 0.7 ? '#ff8c00' : '#ff4757';
  compareEls.ballStressBar.setAttribute('fill', ballColor);
  compareEls.ballStressLabel.textContent = forceRatio > 0.65 ? '高应力 ⚠' : '中应力';

  // 滚柱丝杠应力条
  const rollerStressW = Math.min(90, 3 + forceRatio * 15);
  compareEls.rollerStressBar.setAttribute('width', rollerStressW);
  compareEls.rollerStressLabel.textContent = forceRatio > 0.8 ? '低应力 ✓' : '极低 ✓';

  // 接触点发光强度
  const glowR = 2 + forceRatio * 4;
  compareEls.ballContactTop.setAttribute('r', glowR);
  compareEls.ballContactBot.setAttribute('r', glowR);
  compareEls.rollerContactTop.setAttribute('stroke-width', 2 + forceRatio * 3);
  compareEls.rollerContactBot.setAttribute('stroke-width', 2 + forceRatio * 3);
}

function updateDataPanel() {
  document.getElementById('dataForce').textContent = S.force + ' N';
  document.getElementById('dataForce').style.color = S.force > 4500 ? '#ff4757' : S.force > 3000 ? '#ff6b35' : '#fbbf24';

  const stressLevel = S.force > 4500 ? '中' : '低';
  document.getElementById('dataStress').textContent = stressLevel;
  document.getElementById('dataStress').style.color = S.force > 4500 ? '#fbbf24' : '#00e5a0';

  const disp = (S.nutPos * 50).toFixed(1);
  document.getElementById('dataDisp').textContent = disp + ' mm';
  document.getElementById('dataStatus').textContent = S.playing ? '运转中' : '已暂停';
  document.getElementById('dataStatus').style.color = S.playing ? '#00e5a0' : '#64748b';
}

function updateSequence() {
  const cyclePos = (S.time * 0.3) % 6;
  const steps = document.querySelectorAll('#seqBar .seq-step');
  steps.forEach((step, i) => {
    const dist = Math.abs(cyclePos - i);
    step.classList.toggle('lit', dist < 0.8 || dist > 5.2);
  });
}

/* ===== 动画主循环 ===== */
let lastTime = 0;
function animate(timestamp) {
  const dt = Math.min((timestamp - lastTime) / 1000, 0.05);
  lastTime = timestamp;

  if (S.playing) {
    S.time += dt * S.speed;
    S.screwAngle += 90 * S.speed * dt * S.dir;
    S.nutPos = 0.5 + 0.5 * Math.sin(S.time * 0.8);
  }

  updateCrossSection();
  updateSideView();
  updateCompare();
  updateDataPanel();
  updateSequence();

  requestAnimationFrame(animate);
}

/* ===== 控件绑定 ===== */
function setupControls() {
  const forceSlider = document.getElementById('forceSlider');
  const speedSlider = document.getElementById('speedSlider');
  const playBtn = document.getElementById('playBtn');
  const dirBtn = document.getElementById('dirBtn');

  forceSlider.addEventListener('input', () => {
    S.force = parseInt(forceSlider.value);
    document.getElementById('forceLabel').textContent = S.force + ' N';
  });

  speedSlider.addEventListener('input', () => {
    S.speed = parseFloat(speedSlider.value) / 10;
    document.getElementById('speedLabel').textContent = S.speed.toFixed(1) + 'x';
  });

  playBtn.addEventListener('click', () => {
    S.playing = !S.playing;
    playBtn.innerHTML = S.playing ? '<i class="fas fa-pause"></i>' : '<i class="fas fa-play"></i>';
    playBtn.classList.toggle('active', S.playing);
  });

  dirBtn.addEventListener('click', () => {
    S.dir *= -1;
    dirBtn.classList.toggle('active');
  });
}

/* ===== 初始化 ===== */
function init() {
  buildCrossSection();
  buildSideView();
  buildCompare();
  setupControls();
  requestAnimationFrame(animate);
}

init();
</script>
</body>
</html>

实现要点说明:

核心视觉——端面截面动画

  • 中央丝杠带齿轮状齿形(24齿),随时间旋转;8颗滚柱以行星方式公转并自转,齿形实时重绘以表现旋向;螺母外圈及内齿固定不动。
  • 每颗滚柱与丝杠、螺母的接触位置绘制 发光线段#00ffc8 + SVG feGaussianBlur 滤镜),线段宽度与亮度随推力滑块动态变化——推力越大,接触线越亮越粗,直观传达"线接触大面积承载"的 IFR 核心思想。
  • 第一个滚柱旁标注 45° 接触角,推力 >500N 时淡入。

侧视剖面示意

  • 从左至右:伺服电机 → 弹性联轴器 → 丝杠轴(螺纹线随旋转平移)→ 螺母(含可见滚柱)→ 推力箭头(大小/颜色随力值变化)→ 力传感器标记(脉冲闪烁表示闭环反馈)。
  • 螺母、导轨滑块、力箭头均随正弦周期左右滑动,展示旋转→直线运动转换。
  • 底部6步动作时序条按循环依次点亮。

接触应力对比面板

  • 左侧滚珠丝杠:上下两个红色发光点,应力条随推力增长迅速变红变长,超 4000N 时标注"高应力 ⚠"。
  • 右侧行星滚柱丝杠:上下两条青绿发光线,应力条始终极短极绿,标注"极低 ✓"——直观呈现 10 倍接触面积差异。

交互控制

  • 推力滑块 0–6000N:实时影响接触高亮强度、应力条长度/颜色、力箭头标注。
  • 速度滑块 0–3x:控制旋转与直线运动速率。
  • 播放/暂停、换向按钮:可随时冻结观察或反转运动方向。
积分规则:第一轮对话扣减6分,后续每轮扣4分