分享图
A
动画渲染工坊
就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>螺纹主轴锥面顶开机构 — IFR原理动画</title>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700;900&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{
  --bg:#060a14;--card:#0c1222;--border:#1a2744;
  --fg:#dfe6f0;--muted:#5a6e8a;
  --accent:#e89830;--accent-glow:#e8983060;
  --steel:#4a8db8;--steel-dk:#2e6080;--steel-lt:#7ec0e8;
  --cut:#3de88a;--cut-glow:#3de88a50;
  --spring:#a0d830;--workpiece:#8896aa;
  --danger:#f04848;
}
body{
  background:var(--bg);color:var(--fg);
  font-family:'Noto Sans SC',sans-serif;
  min-height:100vh;display:flex;flex-direction:column;align-items:center;
  overflow-x:hidden;
  background-image:
    radial-gradient(ellipse 900px 500px at 50% 40%,#0e1a30 0%,transparent 70%),
    radial-gradient(ellipse 400px 300px at 80% 20%,#1a1028 0%,transparent 60%);
}
header{
  text-align:center;padding:28px 20px 10px;width:100%;max-width:1300px;
}
header h1{
  font-family:'IBM Plex Mono',monospace;font-weight:700;font-size:clamp(22px,3vw,34px);
  letter-spacing:2px;color:var(--fg);
  background:linear-gradient(90deg,var(--steel-lt),var(--accent),var(--steel-lt));
  -webkit-background-clip:text;-webkit-text-fill-color:transparent;
  background-clip:text;
}
header p{
  font-size:clamp(12px,1.4vw,16px);color:var(--muted);margin-top:6px;
  font-family:'IBM Plex Mono',monospace;font-weight:300;letter-spacing:1px;
}
.main-wrap{
  width:100%;max-width:1340px;padding:10px 16px 30px;flex:1;
  display:flex;flex-direction:column;gap:14px;
}
.svg-container{
  position:relative;width:100%;
  background:var(--card);border:1px solid var(--border);
  border-radius:14px;overflow:hidden;
  box-shadow:0 0 60px #0008,inset 0 0 80px #0a122088;
}
.svg-container svg{display:block;width:100%;height:auto;}
.controls-bar{
  display:flex;flex-wrap:wrap;align-items:center;gap:16px;
  background:var(--card);border:1px solid var(--border);border-radius:12px;
  padding:14px 22px;
}
.ctrl-group{display:flex;align-items:center;gap:8px;}
.ctrl-group label{
  font-family:'IBM Plex Mono',monospace;font-size:12px;
  color:var(--muted);white-space:nowrap;min-width:48px;
}
.btn{
  background:#182438;border:1px solid var(--border);border-radius:8px;
  color:var(--fg);padding:8px 18px;cursor:pointer;font-size:14px;
  font-family:'IBM Plex Mono',monospace;transition:all .2s;
  display:flex;align-items:center;gap:6px;
}
.btn:hover{background:#1e3050;border-color:var(--steel-dk);}
.btn.active{background:var(--steel-dk);border-color:var(--steel);color:#fff;}
input[type=range]{
  -webkit-appearance:none;appearance:none;height:6px;border-radius:3px;
  background:#182438;outline:none;cursor:pointer;min-width:100px;
}
input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;width:16px;height:16px;
  border-radius:50%;background:var(--steel);border:2px solid var(--steel-lt);
  cursor:pointer;
}
.phase-badge{
  font-family:'IBM Plex Mono',monospace;font-size:13px;font-weight:600;
  padding:5px 14px;border-radius:6px;letter-spacing:1px;
  transition:all .3s;
}
.phase-idle{background:#182438;color:var(--muted);border:1px solid var(--border);}
.phase-advance{background:#1a2a44;color:var(--steel-lt);border:1px solid var(--steel-dk);}
.phase-chamfer{background:#2a2010;color:var(--accent);border:1px solid #806020;}
.phase-retract{background:#1a2020;color:var(--cut);border:1px solid #206040;}
.info-row{
  display:flex;flex-wrap:wrap;gap:12px;
}
.info-card{
  flex:1;min-width:200px;background:var(--card);border:1px solid var(--border);
  border-radius:10px;padding:14px 18px;
}
.info-card h3{
  font-family:'IBM Plex Mono',monospace;font-size:11px;font-weight:600;
  color:var(--muted);text-transform:uppercase;letter-spacing:1.5px;margin-bottom:8px;
}
.info-card .val{
  font-family:'IBM Plex Mono',monospace;font-size:22px;font-weight:700;
}
.val-amber{color:var(--accent);}
.val-steel{color:var(--steel-lt);}
.val-green{color:var(--cut);}
.info-card p{font-size:12px;color:var(--muted);margin-top:4px;line-height:1.5;}
@ifr-tag{
  display:inline-block;background:var(--accent);color:#000;font-size:10px;
  font-weight:700;padding:2px 6px;border-radius:3px;letter-spacing:1px;
  font-family:'IBM Plex Mono',monospace;vertical-align:middle;
}
.legend{
  display:flex;flex-wrap:wrap;gap:14px;padding:4px 0;
}
.legend-item{display:flex;align-items:center;gap:5px;font-size:12px;color:var(--muted);font-family:'IBM Plex Mono',monospace;}
.legend-dot{width:10px;height:10px;border-radius:2px;}
@media(max-width:700px){
  .controls-bar{gap:10px;padding:10px 14px;}
  .info-card{min-width:140px;}
}
</style>
</head>
<body>

<header>
  <h1>螺纹主轴 + 锥面顶开机构</h1>
  <p>IFR: 单电机 → 旋转切削 + 螺旋进给 + 径向张合 &nbsp;|&nbsp; 一体化解矛盾</p>
</header>

<div class="main-wrap">
  <div class="svg-container">
    <svg id="mechSvg" viewBox="0 0 1200 680" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <!-- 网格图案 -->
        <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
          <path d="M40 0L0 0 0 40" fill="none" stroke="#0f1a2a" stroke-width="0.5"/>
        </pattern>
        <!-- 主轴渐变 -->
        <linearGradient id="spindleGrad" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#6ab8e0"/>
          <stop offset="40%" stop-color="#4a90b8"/>
          <stop offset="100%" stop-color="#2a6080"/>
        </linearGradient>
        <!-- 锥体渐变(高亮) -->
        <linearGradient id="coneGrad" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#f0b848"/>
          <stop offset="40%" stop-color="#e89830"/>
          <stop offset="100%" stop-color="#b06810"/>
        </linearGradient>
        <!-- 锥体发光 -->
        <filter id="coneGlow" x="-40%" y="-40%" width="180%" height="180%">
          <feGaussianBlur stdDeviation="8" result="blur"/>
          <feFlood flood-color="#e89830" flood-opacity="0.5" result="color"/>
          <feComposite in="color" in2="blur" operator="in" result="glow"/>
          <feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
        </filter>
        <!-- 切削发光 -->
        <filter id="cutGlow" x="-50%" y="-50%" width="200%" height="200%">
          <feGaussianBlur stdDeviation="4" result="blur"/>
          <feFlood flood-color="#3de88a" flood-opacity="0.6" result="color"/>
          <feComposite in="color" in2="blur" operator="in" result="glow"/>
          <feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
        </filter>
        <!-- 弹簧渐变 -->
        <linearGradient id="springGrad" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#c0f040"/>
          <stop offset="100%" stop-color="#70a020"/>
        </linearGradient>
        <!-- 基座渐变 -->
        <linearGradient id="baseGrad" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stop-color="#1e2e48"/>
          <stop offset="100%" stop-color="#0e1828"/>
        </linearGradient>
        <!-- 工件渐变 -->
        <linearGradient id="wpGrad" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%" stop-color="#a0a8b8"/>
          <stop offset="100%" stop-color="#7888a0"/>
        </linearGradient>
        <!-- 主轴裁剪 -->
        <clipPath id="spindleClip">
          <rect id="spindleClipRect" x="260" y="305" width="600" height="70" rx="4"/>
        </clipPath>
        <!-- 前视图裁剪 -->
        <clipPath id="frontClip">
          <circle cx="1060" cy="160" r="110"/>
        </clipPath>
      </defs>

      <!-- 背景网格 -->
      <rect width="1200" height="680" fill="#080e1a"/>
      <rect width="1200" height="680" fill="url(#grid)" opacity="0.6"/>

      <!-- ========== 工件(角码)========== -->
      <g id="workpiece">
        <rect x="60" y="260" width="120" height="22" rx="2" fill="url(#wpGrad)" stroke="#9aa8b8" stroke-width="1.2"/>
        <rect x="158" y="260" width="22" height="120" rx="2" fill="url(#wpGrad)" stroke="#9aa8b8" stroke-width="1.2"/>
        <line x1="158" y1="282" x2="180" y2="282" stroke="#b0b8c8" stroke-width="0.6" stroke-dasharray="3,3"/>
        <!-- 角码标注 -->
        <text x="100" y="252" fill="#8896aa" font-family="IBM Plex Mono" font-size="11" text-anchor="middle">角码(工件)</text>
      </g>

      <!-- ========== 固定机座 ========== -->
      <g id="fixedBase">
        <rect x="830" y="240" width="200" height="200" rx="6" fill="url(#baseGrad)" stroke="#2a3e5a" stroke-width="1.5"/>
        <!-- 内螺纹示意 -->
        <g opacity="0.5">
          <line x1="850" y1="310" x2="850" y2="370" stroke="#3a5a7a" stroke-width="1"/>
          <line x1="870" y1="310" x2="870" y2="370" stroke="#3a5a7a" stroke-width="1"/>
          <line x1="890" y1="310" x2="890" y2="370" stroke="#3a5a7a" stroke-width="1"/>
          <line x1="910" y1="310" x2="910" y2="370" stroke="#3a5a7a" stroke-width="1"/>
        </g>
        <!-- 固定标记 -->
        <g stroke="#3a5a7a" stroke-width="1.2" opacity="0.6">
          <line x1="950" y1="440" x2="950" y2="460"/><line x1="940" y1="460" x2="960" y2="460"/>
          <line x1="970" y1="440" x2="970" y2="460"/><line x1="960" y1="460" x2="980" y2="460"/>
          <line x1="890" y1="440" x2="890" y2="460"/><line x1="880" y1="460" x2="900" y2="460"/>
          <line x1="910" y1="440" x2="910" y2="460"/><line x1="900" y1="460" x2="920" y2="460"/>
        </g>
        <text x="930" y="260" fill="#5a7a9a" font-family="IBM Plex Mono" font-size="11" text-anchor="middle">固定机座(内螺纹)</text>
      </g>

      <!-- ========== 电机 ========== -->
      <g id="motor">
        <rect x="1030" y="300" width="70" height="80" rx="6" fill="#1a2840" stroke="#2a4060" stroke-width="1.5"/>
        <circle cx="1065" cy="340" r="18" fill="none" stroke="#4a7a9a" stroke-width="2"/>
        <text x="1065" y="345" fill="#6a9aba" font-family="IBM Plex Mono" font-size="12" font-weight="700" text-anchor="middle">M</text>
        <text x="1065" y="396" fill="#5a7a9a" font-family="IBM Plex Mono" font-size="10" text-anchor="middle">单电机</text>
      </g>

      <!-- ========== 主轴组(旋转+平移)========== -->
      <g id="spindleGroup">
        <!-- 主轴体 -->
        <rect id="spindleBody" x="430" y="318" width="400" height="44" rx="4" fill="url(#spindleGrad)" stroke="#5aa0c8" stroke-width="1.2"/>
        <!-- 螺纹纹理(旋转时移动) -->
        <g id="threadPattern" clip-path="url(#spindleClip)" opacity="0.55">
        </g>
        <!-- 锥体(创新亮点) -->
        <g id="coneGroup" filter="url(#coneGlow)">
          <polygon id="coneShape" points="280,320 430,298 430,382 280,360" fill="url(#coneGrad)" stroke="#f0b848" stroke-width="1.5" stroke-linejoin="round"/>
          <!-- 锥面高光线 -->
          <line id="coneHighlight" x1="280" y1="340" x2="430" y2="305" stroke="#ffe080" stroke-width="0.8" opacity="0.6"/>
          <line id="coneHighlight2" x1="280" y1="340" x2="430" y2="375" stroke="#ffe080" stroke-width="0.8" opacity="0.6"/>
        </g>
        <!-- 伞齿轮(主轴上) -->
        <g id="spurGear" transform="translate(420,340)">
          <polygon points="0,-28 8,-24 8,-16 0,-12 -8,-16 -8,-24" fill="#5aa0c8" stroke="#80c0e0" stroke-width="0.8"/>
          <polygon points="0,12 8,16 8,24 0,28 -8,24 -8,16" fill="#5aa0c8" stroke="#80c0e0" stroke-width="0.8"/>
          <circle r="12" fill="#3a7a9a" stroke="#5aa0c8" stroke-width="1"/>
          <circle r="5" fill="#2a6080"/>
        </g>
        <!-- 旋转指示箭头 -->
        <g id="rotArrow" opacity="0.7">
          <path d="M620,290 A30,30 0 0,1 650,300" fill="none" stroke="#80c0e0" stroke-width="1.5" marker-end="url(#arrowBlue)"/>
        </g>
      </g>

      <!-- 箭头标记 -->
      <defs>
        <marker id="arrowBlue" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
          <path d="M0,0 L10,5 L0,10 z" fill="#80c0e0"/>
        </marker>
        <marker id="arrowAmber" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
          <path d="M0,0 L10,5 L0,10 z" fill="#e89830"/>
        </marker>
        <marker id="arrowGreen" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
          <path d="M0,0 L10,5 L0,10 z" fill="#3de88a"/>
        </marker>
        <marker id="arrowWhite" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
          <path d="M0,0 L10,5 L0,10 z" fill="#c0d0e0"/>
        </marker>
      </defs>

      <!-- ========== 刀轴载体组(平移+径向张合)========== -->
      <g id="carrierGroup">
        <!-- 载体环 -->
        <rect id="carrierRing" x="395" y="296" width="30" height="88" rx="4" fill="#2a3a50" stroke="#4a6a8a" stroke-width="1.2"/>

        <!-- 上刀轴 -->
        <g id="sawArmTop">
          <!-- 刀轴底座(与锥面接触) -->
          <rect id="sawBaseTop" x="398" y="280" width="24" height="16" rx="3" fill="#4a6a8a" stroke="#6a8aaa" stroke-width="1"/>
          <!-- 刀轴杆 -->
          <rect id="sawShaftTop" x="404" y="218" width="12" height="62" rx="2" fill="#3a7a9a" stroke="#5aa0c0" stroke-width="0.8"/>
          <!-- 伞齿轮(刀轴上) -->
          <g id="bevelTop" transform="translate(410,260)">
            <polygon points="-10,0 -6,-8 6,-8 10,0 6,8 -6,8" fill="#5aa0c8" stroke="#80c0e0" stroke-width="0.8"/>
          </g>
          <!-- 复位弹簧 -->
          <g id="springTop"></g>
          <!-- 锯片 -->
          <g id="sawBladeTop" transform="translate(410,200)">
            <circle r="22" fill="none" stroke="#3de88a" stroke-width="2.5" stroke-dasharray="6,4"/>
            <circle r="14" fill="none" stroke="#3de88a" stroke-width="1" opacity="0.5"/>
            <circle r="4" fill="#2a6080" stroke="#3de88a" stroke-width="1"/>
          </g>
        </g>

        <!-- 下刀轴 -->
        <g id="sawArmBottom">
          <rect id="sawBaseBottom" x="398" y="384" width="24" height="16" rx="3" fill="#4a6a8a" stroke="#6a8aaa" stroke-width="1"/>
          <rect id="sawShaftBottom" x="404" y="400" width="12" height="62" rx="2" fill="#3a7a9a" stroke="#5aa0c0" stroke-width="0.8"/>
          <g id="bevelBottom" transform="translate(410,420)">
            <polygon points="-10,0 -6,-8 6,-8 10,0 6,8 -6,8" fill="#5aa0c8" stroke="#80c0e0" stroke-width="0.8"/>
          </g>
          <g id="springBottom"></g>
          <g id="sawBladeBottom" transform="translate(410,480)">
            <circle r="22" fill="none" stroke="#3de88a" stroke-width="2.5" stroke-dasharray="6,4"/>
            <circle r="14" fill="none" stroke="#3de88a" stroke-width="1" opacity="0.5"/>
            <circle r="4" fill="#2a6080" stroke="#3de88a" stroke-width="1"/>
          </g>
        </g>
      </g>

      <!-- ========== 切削粒子层 ========== -->
      <g id="particles"></g>

      <!-- ========== 标注层 ========== -->
      <g id="annotations">
        <!-- 75mm/转 标注 -->
        <g id="dimAdvance" opacity="0">
          <line x1="430" y1="400" x2="430" y2="430" stroke="#e89830" stroke-width="1" stroke-dasharray="3,2"/>
          <line id="dimLine1" x1="430" y1="420" x2="430" y2="420" stroke="#e89830" stroke-width="1.2" marker-end="url(#arrowAmber)" marker-start="url(#arrowAmber)"/>
          <text id="dimText1" x="430" y="445" fill="#e89830" font-family="IBM Plex Mono" font-size="12" font-weight="600" text-anchor="middle">75mm/转</text>
        </g>
        <!-- 15°锥角标注 -->
        <g id="dimCone" opacity="0.9">
          <path id="coneAngleArc" d="" fill="none" stroke="#ffe080" stroke-width="1" opacity="0.7"/>
          <text id="coneAngleText" x="340" y="370" fill="#ffe080" font-family="IBM Plex Mono" font-size="11" font-weight="500" opacity="0.8">15°</text>
        </g>
        <!-- 锥面顶开力箭头 -->
        <g id="forceArrows" opacity="0">
          <line id="forceTop" x1="380" y1="290" x2="380" y2="260" stroke="#e89830" stroke-width="2" marker-end="url(#arrowAmber)"/>
          <line id="forceBottom" x1="380" y1="390" x2="380" y2="420" stroke="#e89830" stroke-width="2" marker-end="url(#arrowAmber)"/>
          <text x="348" y="254" fill="#e89830" font-family="IBM Plex Mono" font-size="10">顶开</text>
          <text x="348" y="436" fill="#e89830" font-family="IBM Plex Mono" font-size="10">顶开</text>
        </g>
        <!-- 进给方向箭头 -->
        <g id="feedArrow" opacity="0">
          <line id="feedLine" x1="680" y1="285" x2="580" y2="285" stroke="#c0d0e0" stroke-width="1.5" marker-end="url(#arrowWhite)"/>
          <text id="feedText" x="630" y="278" fill="#c0d0e0" font-family="IBM Plex Mono" font-size="10" text-anchor="middle">进给</text>
        </g>
      </g>

      <!-- ========== 前视图 ========== -->
      <g id="frontViewGroup">
        <circle cx="1060" cy="160" r="115" fill="#0a1020" stroke="#1a2a44" stroke-width="1.5"/>
        <circle cx="1060" cy="160" r="112" fill="none" stroke="#0f1a2a" stroke-width="0.5" stroke-dasharray="4,4"/>
        <!-- 标题 -->
        <text x="1060" y="38" fill="#5a7a9a" font-family="IBM Plex Mono" font-size="11" text-anchor="middle" font-weight="600">前端视图</text>
        <!-- 主轴截面 -->
        <circle cx="1060" cy="160" r="16" fill="url(#spindleGrad)" stroke="#5aa0c8" stroke-width="1.2"/>
        <!-- 锥面环 -->
        <circle id="coneRing" cx="1060" cy="160" r="30" fill="none" stroke="#e89830" stroke-width="1.5" stroke-dasharray="4,3" opacity="0.6"/>
        <!-- 4个刀轴+锯片 -->
        <g id="frontSaw0"><line x1="1060" y1="144" x2="1060" y2="90" stroke="#3a7a9a" stroke-width="3"/><circle cx="1060" cy="78" r="14" fill="none" stroke="#3de88a" stroke-width="2" stroke-dasharray="4,3"/><circle cx="1060" cy="78" r="3" fill="#2a6080"/></g>
        <g id="frontSaw90"><line x1="1076" y1="160" x2="1130" y2="160" stroke="#3a7a9a" stroke-width="3"/><circle cx="1142" cy="160" r="14" fill="none" stroke="#3de88a" stroke-width="2" stroke-dasharray="4,3"/><circle cx="1142" cy="160" r="3" fill="#2a6080"/></g>
        <g id="frontSaw180"><line x1="1060" y1="176" x2="1060" y2="230" stroke="#3a7a9a" stroke-width="3"/><circle cx="1060" cy="242" r="14" fill="none" stroke="#3de88a" stroke-width="2" stroke-dasharray="4,3"/><circle cx="1060" cy="242" r="3" fill="#2a6080"/></g>
        <g id="frontSaw270"><line x1="1044" y1="160" x2="990" y2="160" stroke="#3a7a9a" stroke-width="3"/><circle cx="978" cy="160" r="14" fill="none" stroke="#3de88a" stroke-width="2" stroke-dasharray="4,3"/><circle cx="978" cy="160" r="3" fill="#2a6080"/></g>
        <!-- 弹簧示意 -->
        <g id="frontSprings" opacity="0.5"></g>
      </g>

      <!-- ========== IFR标记 ========== -->
      <g id="ifrBadge" opacity="0">
        <rect x="24" y="590" width="200" height="70" rx="8" fill="#1a1808" stroke="#e89830" stroke-width="1.5" opacity="0.9"/>
        <text x="34" y="614" fill="#e89830" font-family="IBM Plex Mono" font-size="13" font-weight="700">IFR 最终理想解</text>
        <text x="34" y="634" fill="#b0a070" font-family="Noto Sans SC" font-size="11">1电机 → 旋转+进给+张合</text>
        <text x="34" y="650" fill="#b0a070" font-family="Noto Sans SC" font-size="11">资源自合并,复杂度极小</text>
      </g>

      <!-- 阶段文字 -->
      <text id="phaseText" x="600" y="640" fill="#5a7a9a" font-family="IBM Plex Mono" font-size="13" text-anchor="middle" font-weight="500"></text>
    </svg>
  </div>

  <!-- 控制面板 -->
  <div class="controls-bar">
    <div class="ctrl-group">
      <button class="btn" id="btnPlay" title="播放/暂停"><i class="fas fa-play"></i></button>
      <button class="btn" id="btnReset" title="重置"><i class="fas fa-undo"></i></button>
    </div>
    <div class="ctrl-group" style="flex:1;min-width:180px;">
      <label>进度</label>
      <input type="range" id="sliderProgress" min="0" max="1000" value="0" style="flex:1;"/>
    </div>
    <div class="ctrl-group">
      <label>速度</label>
      <input type="range" id="sliderSpeed" min="20" max="300" value="100" style="width:80px;"/>
      <span id="speedVal" style="font-family:IBM Plex Mono;font-size:11px;color:var(--muted);min-width:30px;">1.0x</span>
    </div>
    <div class="ctrl-group">
      <span id="phaseBadge" class="phase-badge phase-idle">待机</span>
    </div>
  </div>

  <!-- 信息卡片 -->
  <div class="info-row">
    <div class="info-card">
      <h3>螺纹导程</h3>
      <div class="val val-amber">75 mm/转</div>
      <p>主轴旋转一圈轴向推进75mm,螺旋进给无需丝杠滑台</p>
    </div>
    <div class="info-card">
      <h3>锥面斜角</h3>
      <div class="val val-amber">15°</div>
      <p>轴向力→径向张力的转换角,平衡进给阻力与张开量</p>
    </div>
    <div class="info-card">
      <h3>IFR 矛盾消除</h3>
      <div class="val val-green">1 电机</div>
      <p>删除丝杠滑台,螺纹自进给+锥面自张合,零额外执行器</p>
    </div>
    <div class="info-card">
      <h3>当前阶段</h3>
      <div class="val val-steel" id="infoPhase">待机</div>
      <p id="infoPhaseDesc">点击播放按钮启动动画</p>
    </div>
  </div>

  <!-- 图例 -->
  <div class="legend">
    <div class="legend-item"><div class="legend-dot" style="background:#4a90b8;"></div>主轴/螺纹</div>
    <div class="legend-item"><div class="legend-dot" style="background:#e89830;"></div>锥面(创新核心)</div>
    <div class="legend-item"><div class="legend-dot" style="background:#3de88a;"></div>锯片/切削</div>
    <div class="legend-item"><div class="legend-dot" style="background:#a0d830;"></div>复位弹簧</div>
    <div class="legend-item"><div class="legend-dot" style="background:#8896aa;"></div>工件</div>
    <div class="legend-item"><div class="legend-dot" style="background:#1e2e48;"></div>固定机座</div>
  </div>
</div>

<script>
// ==================== 动画引擎 ====================
const SVG_NS = 'http://www.w3.org/2000/svg';

// 机构参数
const M = {
  centerY: 340,
  spindleR: 22,
  coneTipX: 280,       // 锥体尖端初始X
  coneBaseX: 430,      // 锥体底部初始X
  coneTipR: 22,        // 锥体尖端半径
  coneBaseR: 44,       // 锥体底部半径
  totalAdvance: 180,   // 最大轴向推进量(px)
  contactAdvance: 55,  // 锯片接触工件时的推进量
  maxRadial: 40,       // 最大径向扩张量(px)
  sawBladeR: 22,       // 锯片半径
  sawRestY_top: 200,   // 上锯片初始Y
  sawRestY_bottom: 480,// 下锯片初始Y
  threadSpacing: 18,   // 螺纹间距
};

// 状态
let state = {
  progress: 0,
  playing: false,
  speed: 1,
  lastTime: 0,
  manualDrag: false,
};

// DOM引用
const svg = document.getElementById('mechSvg');
const spindleGroup = document.getElementById('spindleGroup');
const carrierGroup = document.getElementById('carrierGroup');
const threadPattern = document.getElementById('threadPattern');
const sawArmTop = document.getElementById('sawArmTop');
const sawArmBottom = document.getElementById('sawArmBottom');
const particles = document.getElementById('particles');
const btnPlay = document.getElementById('btnPlay');
const btnReset = document.getElementById('btnReset');
const sliderProgress = document.getElementById('sliderProgress');
const sliderSpeed = document.getElementById('sliderSpeed');
const speedVal = document.getElementById('speedVal');
const phaseBadge = document.getElementById('phaseBadge');
const phaseText = document.getElementById('phaseText');
const infoPhase = document.getElementById('infoPhase');
const infoPhaseDesc = document.getElementById('infoPhaseDesc');

// 初始化螺纹纹理
function initThreadPattern() {
  threadPattern.innerHTML = '';
  const clipRect = document.getElementById('spindleClipRect');
  for (let i = 0; i < 35; i++) {
    const x = 260 + i * M.threadSpacing;
    const line = document.createElementNS(SVG_NS, 'line');
    line.setAttribute('x1', x);
    line.setAttribute('y1', '305');
    line.setAttribute('x2', x + 10);
    line.setAttribute('y2', '375');
    line.setAttribute('stroke', '#80c0e0');
    line.setAttribute('stroke-width', '1.2');
    line.setAttribute('opacity', '0.4');
    threadPattern.appendChild(line);
  }
}

// 初始化弹簧路径
function initSprings() {
  const springTop = document.getElementById('springTop');
  const springBottom = document.getElementById('springBottom');
  springTop.innerHTML = createSpringPath(410, 280, 410, 200, 6, 8);
  springBottom.innerHTML = createSpringPath(410, 400, 410, 480, 6, 8);
}

function createSpringPath(x1, y1, x2, y2, coils, amp) {
  const dy = (y2 - y1) / (coils * 2 + 1);
  let d = `M${x1},${y1}`;
  for (let i = 0; i < coils * 2; i++) {
    const ny = y1 + dy * (i + 1);
    const nx = x1 + (i % 2 === 0 ? amp : -amp);
    d += ` L${nx},${ny}`;
  }
  d += ` L${x2},${y2}`;
  return `<path d="${d}" fill="none" stroke="url(#springGrad)" stroke-width="2" stroke-linecap="round"/>`;
}

// 缓动函数
function easeInOut(t) {
  return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
}
function easeOut(t) {
  return 1 - Math.pow(1 - t, 3);
}

// 计算动画位置
function getPositions(p) {
  p = Math.max(0, Math.min(1, p));
  let spindleX = 0, carrierX = 0, radial = 0, rotation = 0, phase = 'idle', phaseLabel = '', phaseDesc = '';

  if (p <= 0.01) {
    phase = 'idle'; phaseLabel = '待机'; phaseDesc = '点击播放启动机构';
  } else if (p <= 0.30) {
    // 阶段1: 空程推进
    const t = easeInOut((p - 0.01) / 0.29);
    spindleX = -t * M.contactAdvance;
    carrierX = spindleX;
    radial = 0;
    rotation = t * Math.PI * 6;
    phase = 'advance'; phaseLabel = '螺旋进给'; phaseDesc = '螺纹驱动主轴前行,锯片空转接近工件';
  } else if (p <= 0.65) {
    // 阶段2: 锥面顶开+切削
    const t = easeInOut((p - 0.30) / 0.35);
    spindleX = -(M.contactAdvance + t * (M.totalAdvance - M.contactAdvance));
    carrierX = -M.contactAdvance; // 载体停在工件处
    radial = t * M.maxRadial;
    rotation = (1 + t * 3) * Math.PI * 6;
    phase = 'chamfer'; phaseLabel = '锥面顶开·倒角'; phaseDesc = '主轴继续前行,锥面强行顶开刀轴,锯片径向切入';
  } else {
    // 阶段3: 反转退回
    const t = easeInOut((p - 0.65) / 0.35);
    spindleX = -M.totalAdvance * (1 - t);
    carrierX = -M.contactAdvance * (1 - t);
    radial = M.maxRadial * (1 - t);
    rotation = (4 - t * 4) * Math.PI * 6;
    phase = 'retract'; phaseLabel = '反转复位'; phaseDesc = '电机反转,锥面后退,弹簧拉回刀轴';
  }

  return { spindleX, carrierX, radial, rotation, phase, phaseLabel, phaseDesc };
}

// 更新可视化
function updateVisualization(p) {
  const pos = getPositions(p);

  // 1. 主轴组平移
  spindleGroup.setAttribute('transform', `translate(${pos.spindleX},0)`);

  // 2. 螺纹旋转动画(移动螺纹线位置)
  const threadOffset = (pos.rotation * M.threadSpacing / (Math.PI * 2)) % M.threadSpacing;
  const lines = threadPattern.querySelectorAll('line');
  lines.forEach((line, i) => {
    const baseX = 260 + i * M.threadSpacing;
    const newX = baseX + threadOffset;
    line.setAttribute('x1', newX);
    line.setAttribute('x2', newX + 10);
  });

  // 3. 载体组平移(独立于主轴)
  carrierGroup.setAttribute('transform', `translate(${pos.carrierX},0)`);

  // 4. 刀轴径向扩张
  const topSawY = M.sawRestY_top - pos.radial;
  const bottomSawY = M.sawRestY_bottom + pos.radial;

  sawArmTop.setAttribute('transform', `translate(0,${-pos.radial})`);
  sawArmBottom.setAttribute('transform', `translate(0,${pos.radial})`);

  // 更新弹簧
  const springTop = document.getElementById('springTop');
  const springBottom = document.getElementById('springBottom');
  const topBase = 280 - pos.radial;
  const bottomBase = 400 + pos.radial;
  springTop.innerHTML = createSpringPath(410, topBase, 410, topSawY, 6, Math.max(3, 8 - pos.radial * 0.1));
  springBottom.innerHTML = createSpringPath(410, bottomBase, 410, bottomSawY, 6, Math.max(3, 8 - pos.radial * 0.1));

  // 5. 锥体发光强度(扩张时更亮)
  const coneGlowIntensity = pos.phase === 'chamfer' ? 1 : 0.3;
  document.getElementById('coneGroup').setAttribute('filter',
    coneGlowIntensity > 0.5 ? 'url(#coneGlow)' : 'none');
  document.getElementById('coneGroup').setAttribute('opacity',
    0.6 + coneGlowIntensity * 0.4);

  // 6. 标注显示
  // 进给方向箭头
  const feedArrow = document.getElementById('feedArrow');
  feedArrow.setAttribute('transform', `translate(${pos.spindleX},0)`);
  feedArrow.setAttribute('opacity', pos.phase === 'advance' ? 0.9 : (pos.phase === 'chamfer' ? 0.6 : 0));

  // 75mm标注
  const dimAdvance = document.getElementById('dimAdvance');
  dimAdvance.setAttribute('transform', `translate(${pos.spindleX},0)`);
  dimAdvance.setAttribute('opacity', pos.phase === 'chamfer' ? 1 : (pos.phase === 'advance' ? 0.5 : 0));
  // 更新尺寸线
  const dimL1 = document.getElementById('dimLine1');
  const absAdvance = Math.abs(pos.spindleX);
  if (absAdvance > 10) {
    dimL1.setAttribute('x1', 430);
    dimL1.setAttribute('x2', 430 + absAdvance);
  }
  const dt1 = document.getElementById('dimText1');
  dt1.setAttribute('x', 430 + absAdvance / 2);

  // 锥面力箭头
  const forceArrows = document.getElementById('forceArrows');
  forceArrows.setAttribute('opacity', pos.phase === 'chamfer' ? 0.9 : 0);
  if (pos.radial > 2) {
    const fTop = document.getElementById('forceTop');
    const fBottom = document.getElementById('forceBottom');
    fTop.setAttribute('x1', 380 + pos.spindleX); fTop.setAttribute('x2', 380 + pos.spindleX);
    fTop.setAttribute('y1', M.centerY - M.coneBaseR + 2); fTop.setAttribute('y2', M.centerY - M.coneBaseR - pos.radial - 10);
    fBottom.setAttribute('x1', 380 + pos.spindleX); fBottom.setAttribute('x2', 380 + pos.spindleX);
    fBottom.setAttribute('y1', M.centerY + M.coneBaseR - 2); fBottom.setAttribute('y2', M.centerY + M.coneBaseR + pos.radial + 10);
  }

  // IFR标记
  const ifrBadge = document.getElementById('ifrBadge');
  ifrBadge.setAttribute('opacity', pos.phase === 'chamfer' ? 1 : (p > 0.02 ? 0.4 : 0));

  // 7. 前视图更新
  updateFrontView(pos.radial, pos.phase);

  // 8. 切削粒子
  updateParticles(pos, p);

  // 9. 锯片旋转
  const sawRot = pos.rotation * 180 / Math.PI;
  const sawBladeTop = document.getElementById('sawBladeTop');
  const sawBladeBottom = document.getElementById('sawBladeBottom');
  if (pos.phase !== 'idle') {
    sawBladeTop.setAttribute('transform', `translate(410,${topSawY}) rotate(${sawRot % 360})`);
    sawBladeBottom.setAttribute('transform', `translate(410,${bottomSawY}) rotate(${-sawRot % 360})`);
  }

  // 10. 旋转指示
  const rotArrow = document.getElementById('rotArrow');
  if (pos.phase !== 'idle') {
    const angle = (pos.rotation * 180 / Math.PI) % 360;
    rotArrow.setAttribute('transform', `translate(${pos.spindleX},0) rotate(${angle * 0.3},620,300)`);
    rotArrow.setAttribute('opacity', 0.7);
  } else {
    rotArrow.setAttribute('opacity', 0);
  }

  // 11. 阶段文字
  phaseText.textContent = pos.phaseLabel;

  // 12. 更新UI
  updatePhaseUI(pos);
}

// 前视图更新
function updateFrontView(radial, phase) {
  const baseLen = 54; // 刀轴基础长度
  const extLen = baseLen + radial;
  const r = extLen;

  // 更新4个锯片位置(0°, 90°, 180°, 270°)
  const angles = [0, 90, 180, 270];
  const ids = ['frontSaw0', 'frontSaw90', 'frontSaw180', 'frontSaw270'];

  ids.forEach((id, i) => {
    const g = document.getElementById(id);
    const a = angles[i] * Math.PI / 180;
    const cx = 1060 + Math.sin(a) * r;
    const cy = 160 - Math.cos(a) * r;
    const lx1 = 1060 + Math.sin(a) * 20;
    const ly1 = 160 - Math.cos(a) * 20;
    const lx2 = 1060 + Math.sin(a) * (r - 14);
    const ly2 = 160 - Math.cos(a) * (r - 14);

    const line = g.querySelector('line');
    line.setAttribute('x1', lx1); line.setAttribute('y1', ly1);
    line.setAttribute('x2', lx2); line.setAttribute('y2', ly2);

    const circle = g.querySelectorAll('circle');
    circle[0].setAttribute('cx', cx); circle[0].setAttribute('cy', cy);
    circle[1].setAttribute('cx', cx); circle[1].setAttribute('cy', cy);
  });

  // 锥面环半径
  const coneRing = document.getElementById('coneRing');
  coneRing.setAttribute('r', 20 + radial * 0.5);
  coneRing.setAttribute('opacity', phase === 'chamfer' ? 0.9 : 0.4);
}

// 切削粒子
let particlePool = [];
function updateParticles(pos, p) {
  // 只在切削阶段生成粒子
  if (pos.phase === 'chamfer' && Math.random() < 0.4) {
    for (let i = 0; i < 2; i++) {
      const isTop = Math.random() > 0.5;
      const baseY = isTop ? (M.sawRestY_top - pos.radial) : (M.sawRestY_bottom + pos.radial);
      const px = 180 + pos.carrierX + Math.random() * 20;
      const py = baseY + (Math.random() - 0.5) * 20;
      const vx = -1 - Math.random() * 3;
      const vy = (Math.random() - 0.5) * 4;
      particlePool.push({ x: px, y: py, vx, vy, life: 1, el: null });
    }
  }

  // 更新粒子
  particles.innerHTML = '';
  particlePool = particlePool.filter(pt => {
    pt.life -= 0.025;
    pt.x += pt.vx;
    pt.y += pt.vy;
    pt.vy += 0.1; // 重力
    if (pt.life <= 0) return false;

    const circle = document.createElementNS(SVG_NS, 'circle');
    circle.setAttribute('cx', pt.x);
    circle.setAttribute('cy', pt.y);
    circle.setAttribute('r', 1.5 + Math.random());
    circle.setAttribute('fill', '#3de88a');
    circle.setAttribute('opacity', pt.life * 0.8);
    particles.appendChild(circle);
    return true;
  });
}

// 阶段UI更新
function updatePhaseUI(pos) {
  // Badge
  phaseBadge.className = 'phase-badge phase-' + pos.phase;
  const labels = { idle: '待机', advance: '进给', chamfer: '倒角', retract: '复位' };
  phaseBadge.textContent = labels[pos.phase] || '待机';

  // Info cards
  infoPhase.textContent = pos.phaseLabel;
  infoPhaseDesc.textContent = pos.phaseDesc;
}

// 动画循环
function animate(timestamp) {
  if (state.playing && !state.manualDrag) {
    const delta = timestamp - state.lastTime;
    state.progress += delta * state.speed * 0.00018;
    if (state.progress > 1) state.progress = 0;
    sliderProgress.value = Math.round(state.progress * 1000);
  }
  state.lastTime = timestamp;

  updateVisualization(state.progress);
  requestAnimationFrame(animate);
}

// 事件绑定
btnPlay.addEventListener('click', () => {
  state.playing = !state.playing;
  btnPlay.innerHTML = state.playing ? '<i class="fas fa-pause"></i>' : '<i class="fas fa-play"></i>';
  btnPlay.classList.toggle('active', state.playing);
});

btnReset.addEventListener('click', () => {
  state.progress = 0;
  state.playing = false;
  sliderProgress.value = 0;
  btnPlay.innerHTML = '<i class="fas fa-play"></i>';
  btnPlay.classList.remove('active');
  particlePool = [];
});

sliderProgress.addEventListener('input', (e) => {
  state.progress = parseInt(e.target.value) / 1000;
  state.manualDrag = true;
});
sliderProgress.addEventListener('change', () => {
  state.manualDrag = false;
});

sliderSpeed.addEventListener('input', (e) => {
  state.speed = parseInt(e.target.value) / 100;
  speedVal.textContent = state.speed.toFixed(1) + 'x';
});

// 初始化
initThreadPattern();
initSprings();
updateVisualization(0);
requestAnimationFrame(animate);
</script>
</body>
</html>

这段代码实现了一个高保真的 SVG 原理动画,核心设计思路如下:

IFR 视觉叙事策略

  • 锥面高亮:整个动画中,锥体始终以琥珀色(#e89830)呈现,并在"锥面顶开"阶段自动叠加辉光滤镜(feGaussianBlur + feFlood),让用户一眼锁定创新核心
  • 力箭头动态出现:切削阶段,橙色"顶开"力箭头从锥面指向刀轴,直观展示轴向力→径向力的转换
  • IFR 徽章:倒角阶段浮现"1电机→旋转+进给+张合"标记,强调资源自合并的理想解特征
  • 75mm/转 + 15° 参数标注随动画上下文渐显,避免信息过载

三阶段时序动画

  1. 螺旋进给(0-30%):主轴旋转前行,螺纹纹理平移模拟旋转,锯片空转接近角码
  2. 锥面顶开·倒角(30-65%):载体被工件阻挡停止,主轴继续前行,锥面强行撑开4个刀轴,切削粒子飞溅
  3. 反转复位(65-100%):电机反转,锥面后退,弹簧拉回刀轴

交互控制

  • 播放/暂停、重置按钮
  • 进度滑块:可手动拖拽到任意时刻观察机构状态
  • 速度滑块:0.2x ~ 3.0x 变速
  • 阶段徽章实时变色(蓝=进给,琥珀=倒角,绿=复位)

双视图联动

  • 主视图:侧剖图展示主轴、锥面、刀轴、弹簧、工件的相对运动
  • 前端视图(右上角):4锯片呈十字分布,实时展示径向张合
积分规则:第一轮对话扣减6分,后续每轮扣4分