分享图
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=Rajdhani:wght@400;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<style>
:root{
  --bg:#060b14;--surface:#0d1520;--grid:#131d2e;
  --cyan:#00e5ff;--cyan-dim:#0a3d4a;--amber:#ff9100;--amber-dim:#4a2a00;
  --red:#ff3d5a;--green:#00e676;--metal:#5a6a7a;--metal-hi:#8a9aaa;
  --text:#d0dae6;--text2:#6a7a8a;--text3:#3a4a5a;
}
*{margin:0;padding:0;box-sizing:border-box}
body{
  background:var(--bg);color:var(--text);font-family:'Noto Sans SC',sans-serif;
  min-height:100vh;display:flex;flex-direction:column;align-items:center;
  overflow-x:hidden;
}
/* 背景网格 */
body::before{
  content:'';position:fixed;inset:0;z-index:0;
  background:
    linear-gradient(90deg,var(--grid) 1px,transparent 1px),
    linear-gradient(var(--grid) 1px,transparent 1px);
  background-size:60px 60px;
  opacity:.35;pointer-events:none;
}
body::after{
  content:'';position:fixed;inset:0;z-index:0;
  background:radial-gradient(ellipse at 50% 50%,transparent 40%,var(--bg) 100%);
  pointer-events:none;
}

header{
  position:relative;z-index:1;text-align:center;
  padding:28px 20px 10px;
}
header h1{
  font-family:'Rajdhani',sans-serif;font-weight:700;font-size:clamp(22px,3.2vw,38px);
  letter-spacing:.12em;color:var(--cyan);
  text-shadow:0 0 30px rgba(0,229,255,.25);
}
header p{
  font-size:clamp(12px,1.4vw,15px);color:var(--text2);
  margin-top:4px;letter-spacing:.05em;
}
.tag{
  display:inline-block;background:rgba(0,229,255,.08);
  border:1px solid rgba(0,229,255,.2);border-radius:4px;
  padding:2px 10px;font-size:11px;color:var(--cyan);
  margin-top:8px;letter-spacing:.08em;
}

.main-wrap{
  position:relative;z-index:1;width:100%;max-width:1500px;
  display:flex;gap:16px;padding:10px 20px 20px;flex:1;
  min-height:0;
}
.svg-box{
  flex:1;min-width:0;position:relative;
  background:var(--surface);border-radius:12px;
  border:1px solid rgba(0,229,255,.08);
  box-shadow:0 0 60px rgba(0,229,255,.04);
  overflow:hidden;
}
.svg-box svg{width:100%;height:100%;display:block}

/* 右侧面板 */
.panel{
  width:260px;flex-shrink:0;display:flex;flex-direction:column;gap:12px;
}
.card{
  background:var(--surface);border-radius:10px;
  border:1px solid rgba(0,229,255,.06);padding:16px;
}
.card h3{
  font-family:'Rajdhani',sans-serif;font-size:13px;font-weight:600;
  color:var(--cyan);letter-spacing:.1em;margin-bottom:10px;
  text-transform:uppercase;
  border-bottom:1px solid rgba(0,229,255,.1);padding-bottom:6px;
}
.status-row{
  display:flex;justify-content:space-between;align-items:center;
  margin-bottom:6px;font-size:12px;
}
.status-row .label{color:var(--text2)}
.status-row .val{font-family:'Rajdhani',monospace;font-weight:600;font-size:14px}
.val-cyan{color:var(--cyan)}
.val-amber{color:var(--amber)}
.val-green{color:var(--green)}
.val-red{color:var(--red)}

.tension-bar{
  height:6px;border-radius:3px;background:var(--cyan-dim);
  overflow:hidden;margin-top:3px;
}
.tension-bar .fill{
  height:100%;border-radius:3px;transition:width .1s;
}
.fill-cyan{background:linear-gradient(90deg,var(--cyan-dim),var(--cyan))}
.fill-amber{background:linear-gradient(90deg,var(--amber-dim),var(--amber))}

.phase-badge{
  display:inline-block;padding:3px 12px;border-radius:20px;
  font-size:12px;font-weight:600;letter-spacing:.06em;
}
.phase-down{background:rgba(0,229,255,.12);color:var(--cyan);border:1px solid rgba(0,229,255,.25)}
.phase-up{background:rgba(255,145,0,.12);color:var(--amber);border:1px solid rgba(255,145,0,.25)}
.phase-trans{background:rgba(0,230,118,.1);color:var(--green);border:1px solid rgba(0,230,118,.2)}

/* 控件 */
.ctrl-row{
  display:flex;align-items:center;gap:8px;margin-bottom:8px;
}
.ctrl-row label{font-size:12px;color:var(--text2);min-width:68px}
.ctrl-row input[type=range]{
  flex:1;-webkit-appearance:none;appearance:none;
  height:4px;border-radius:2px;background:var(--cyan-dim);outline:none;
}
.ctrl-row input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none;width:14px;height:14px;border-radius:50%;
  background:var(--cyan);cursor:pointer;border:2px solid var(--bg);
}
.ctrl-row .range-val{
  font-family:'Rajdhani',monospace;font-size:13px;color:var(--cyan);
  min-width:32px;text-align:right;
}

.btn{
  display:inline-flex;align-items:center;gap:6px;
  padding:6px 14px;border-radius:6px;border:1px solid rgba(0,229,255,.2);
  background:rgba(0,229,255,.06);color:var(--cyan);
  font-size:12px;cursor:pointer;transition:all .2s;
  font-family:'Noto Sans SC',sans-serif;
}
.btn:hover{background:rgba(0,229,255,.14);border-color:rgba(0,229,255,.4)}
.btn.active{background:rgba(0,229,255,.18);border-color:var(--cyan)}
.btn i{font-size:11px}

.toggle-row{
  display:flex;align-items:center;justify-content:space-between;
  margin-bottom:8px;font-size:12px;color:var(--text2);
}
.toggle{
  width:36px;height:20px;border-radius:10px;
  background:var(--cyan-dim);cursor:pointer;position:relative;
  transition:background .2s;border:1px solid rgba(0,229,255,.15);
}
.toggle.on{background:rgba(0,229,255,.3);border-color:rgba(0,229,255,.5)}
.toggle::after{
  content:'';position:absolute;top:2px;left:2px;
  width:14px;height:14px;border-radius:50%;
  background:var(--text2);transition:all .2s;
}
.toggle.on::after{left:18px;background:var(--cyan)}

/* IFR 要点 */
.ifr-point{
  font-size:11px;color:var(--text2);line-height:1.6;
  margin-bottom:6px;padding-left:12px;
  border-left:2px solid rgba(0,229,255,.2);
}
.ifr-point strong{color:var(--cyan);font-weight:500}

@keyframes pulse{0%,100%{opacity:.6}50%{opacity:1}}
.pulse{animation:pulse 2s ease-in-out infinite}

@media(max-width:900px){
  .main-wrap{flex-direction:column}
  .panel{width:100%;flex-direction:row;flex-wrap:wrap}
  .card{flex:1;min-width:200px}
}
</style>
</head>
<body>
<header>
  <h1>FLEXIBLE FLAPPING DRIVE</h1>
  <p>镍钛超弹性合金带传动 · 柔性扑翼机构原理</p>
  <span class="tag">TRIZ — 最终理想解 (IFR)</span>
</header>

<div class="main-wrap">
  <div class="svg-box">
    <svg id="mainSvg" viewBox="0 0 1400 800" preserveAspectRatio="xMidYMid meet">
      <defs>
        <!-- 辉光滤镜 -->
        <filter id="glowCyan" x="-50%" y="-50%" width="200%" height="200%">
          <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="blur"/>
          <feFlood flood-color="#00e5ff" flood-opacity=".5" result="color"/>
          <feComposite in="color" in2="blur" operator="in" result="glow"/>
          <feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
        </filter>
        <filter id="glowAmber" x="-50%" y="-50%" width="200%" height="200%">
          <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur"/>
          <feFlood flood-color="#ff9100" flood-opacity=".5" result="color"/>
          <feComposite in="color" in2="blur" operator="in" result="glow"/>
          <feMerge><feMergeNode in="glow"/><feMergeNode in="SourceGraphic"/></feMerge>
        </filter>
        <filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
          <feGaussianBlur in="SourceGraphic" stdDeviation="3"/>
        </filter>
        <!-- 渐变 -->
        <linearGradient id="stripGradCyan" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%" stop-color="#0a3d4a"/>
          <stop offset="50%" stop-color="#00e5ff"/>
          <stop offset="100%" stop-color="#0a3d4a"/>
        </linearGradient>
        <linearGradient id="stripGradAmber" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%" stop-color="#4a2a00"/>
          <stop offset="50%" stop-color="#ff9100"/>
          <stop offset="100%" stop-color="#4a2a00"/>
        </linearGradient>
        <radialGradient id="motorGrad" cx="50%" cy="40%">
          <stop offset="0%" stop-color="#4a5a6a"/>
          <stop offset="100%" stop-color="#2a3440"/>
        </radialGradient>
        <radialGradient id="camGrad" cx="45%" cy="40%">
          <stop offset="0%" stop-color="#3a4858"/>
          <stop offset="100%" stop-color="#1e2832"/>
        </radialGradient>
        <radialGradient id="pulleyGrad" cx="45%" cy="40%">
          <stop offset="0%" stop-color="#6a7a8a"/>
          <stop offset="100%" stop-color="#3a4a5a"/>
        </radialGradient>
        <!-- 箭头标记 -->
        <marker id="arrowCyan" viewBox="0 0 10 10" refX="9" refY="5"
          markerWidth="6" markerHeight="6" orient="auto-start-reverse">
          <path d="M 0 0 L 10 5 L 0 10 z" fill="#00e5ff"/>
        </marker>
        <marker id="arrowAmber" viewBox="0 0 10 10" refX="9" refY="5"
          markerWidth="6" markerHeight="6" orient="auto-start-reverse">
          <path d="M 0 0 L 10 5 L 0 10 z" fill="#ff9100"/>
        </marker>
      </defs>

      <!-- 背景装饰线 -->
      <g opacity=".12" stroke="#00e5ff" stroke-width=".5" fill="none">
        <line x1="100" y1="400" x2="1300" y2="400"/>
        <line x1="700" y1="80" x2="700" y2="720"/>
        <circle cx="700" cy="400" r="300"/>
        <circle cx="700" cy="400" r="200"/>
      </g>

      <!-- 标注群 -->
      <g id="annotations" font-family="'Rajdhani','Noto Sans SC',sans-serif" font-size="11" fill="#5a7a9a">
      </g>

      <!-- 电机 -->
      <g id="motorGroup">
        <rect x="120" y="355" width="140" height="90" rx="8"
          fill="url(#motorGrad)" stroke="#5a6a7a" stroke-width="1.5"/>
        <rect x="128" y="363" width="124" height="74" rx="4"
          fill="none" stroke="#3a4a5a" stroke-width=".5"/>
        <!-- 散热片 -->
        <g stroke="#3a4a5a" stroke-width="1">
          <line x1="130" y1="375" x2="250" y2="375"/>
          <line x1="130" y1="385" x2="250" y2="385"/>
          <line x1="130" y1="395" x2="250" y2="395"/>
          <line x1="130" y1="405" x2="250" y2="405"/>
          <line x1="130" y1="415" x2="250" y2="415"/>
          <line x1="130" y1="425" x2="250" y2="425"/>
        </g>
        <!-- 电机轴 -->
        <rect x="260" y="393" width="60" height="14" rx="3"
          fill="#4a5a6a" stroke="#6a7a8a" stroke-width="1"/>
        <text x="190" y="465" text-anchor="middle" font-size="10" fill="#5a7a9a"
          font-family="'Rajdhani',sans-serif">MOTOR</text>
      </g>

      <!-- 凸轮盘 -->
      <g id="camGroup">
        <circle cx="380" cy="400" r="58" fill="url(#camGrad)"
          stroke="#5a6a7a" stroke-width="1.5"/>
        <circle cx="380" cy="400" r="50" fill="none"
          stroke="#3a4a5a" stroke-width=".5" stroke-dasharray="4 3"/>
        <circle cx="380" cy="400" r="8" fill="#2a3440" stroke="#5a6a7a" stroke-width="1"/>
        <!-- 槽标记线 -->
        <g id="camSlots" stroke-width="2" fill="none">
          <line id="camSlotUpper" stroke="#00e5ff" opacity=".7"/>
          <line id="camSlotLower" stroke="#ff9100" opacity=".7"/>
        </g>
        <!-- 凸轮连接点 -->
        <circle id="camPointUpper" r="5" fill="#00e5ff" stroke="#0a0e17" stroke-width="1.5"/>
        <circle id="camPointLower" r="5" fill="#ff9100" stroke="#0a0e17" stroke-width="1.5"/>
        <text x="380" y="475" text-anchor="middle" font-size="10" fill="#5a7a9a"
          font-family="'Rajdhani',sans-serif">CAM DISC</text>
      </g>

      <!-- 滑轮 -->
      <g id="pulleysGroup">
        <!-- 上滑轮 -->
        <circle cx="660" cy="280" r="22" fill="url(#pulleyGrad)"
          stroke="#8a9aaa" stroke-width="1.5"/>
        <circle cx="660" cy="280" r="5" fill="#2a3440" stroke="#6a7a8a" stroke-width="1"/>
        <circle cx="660" cy="280" r="14" fill="none" stroke="#5a6a7a" stroke-width=".5"/>
        <text x="660" y="260" text-anchor="middle" font-size="9" fill="#5a7a9a"
          font-family="'Rajdhani',sans-serif">PULLEY A</text>
        <!-- 下滑轮 -->
        <circle cx="660" cy="520" r="22" fill="url(#pulleyGrad)"
          stroke="#8a9aaa" stroke-width="1.5"/>
        <circle cx="660" cy="520" r="5" fill="#2a3440" stroke="#6a7a8a" stroke-width="1"/>
        <circle cx="660" cy="520" r="14" fill="none" stroke="#5a6a7a" stroke-width=".5"/>
        <text x="660" y="555" text-anchor="middle" font-size="9" fill="#5a7a9a"
          font-family="'Rajdhani',sans-serif">PULLEY B</text>
      </g>

      <!-- 合金带(动态路径) -->
      <g id="stripsGroup">
        <!-- 上侧合金带辉光层 -->
        <path id="strip1Glow" fill="none" stroke="#00e5ff" stroke-width="8"
          opacity=".15" filter="url(#softGlow)"/>
        <!-- 下侧合金带辉光层 -->
        <path id="strip2Glow" fill="none" stroke="#ff9100" stroke-width="8"
          opacity=".15" filter="url(#softGlow)"/>
        <!-- 上侧合金带主体 -->
        <path id="strip1Main" fill="none" stroke="#00e5ff" stroke-width="2.8"
          stroke-linecap="round" stroke-linejoin="round"/>
        <!-- 下侧合金带主体 -->
        <path id="strip2Main" fill="none" stroke="#ff9100" stroke-width="2.8"
          stroke-linecap="round" stroke-linejoin="round"/>
      </g>

      <!-- 应力可视化层 -->
      <g id="stressGroup" opacity="0">
        <path id="stressStrip1" fill="none" stroke-width="5" stroke-linecap="round"/>
        <path id="stressStrip2" fill="none" stroke-width="5" stroke-linecap="round"/>
      </g>

      <!-- 能量粒子层 -->
      <g id="energyGroup" opacity="1">
      </g>

      <!-- 力箭头层 -->
      <g id="forceGroup" opacity="0">
      </g>

      <!-- 摇臂 -->
      <g id="rockerGroup">
        <circle id="rockerPivot" cx="1020" cy="400" r="8" fill="#3a4a5a"
          stroke="#8a9aaa" stroke-width="1.5"/>
        <line id="rockerArm" x1="1020" y1="400" x2="1020" y2="400"
          stroke="#7a8a9a" stroke-width="6" stroke-linecap="round"/>
        <circle id="rockerUpper" r="5" fill="#00e5ff" stroke="#0a0e17" stroke-width="1.5"/>
        <circle id="rockerLower" r="5" fill="#ff9100" stroke="#0a0e17" stroke-width="1.5"/>
        <text x="1020" y="485" text-anchor="middle" font-size="10" fill="#5a7a9a"
          font-family="'Rajdhani',sans-serif">ROCKER</text>
      </g>

      <!-- 翼面 -->
      <g id="wingGroup">
        <path id="wingShape" fill="#1a2838" stroke="#3a5a7a" stroke-width="1.5"
          stroke-linejoin="round"/>
        <path id="wingVein1" fill="none" stroke="#2a4a6a" stroke-width=".8"/>
        <path id="wingVein2" fill="none" stroke="#2a4a6a" stroke-width=".8"/>
        <path id="wingVein3" fill="none" stroke="#2a4a6a" stroke-width=".5"/>
      </g>

      <!-- 正视图小窗 -->
      <g transform="translate(1120,60)">
        <rect x="0" y="0" width="240" height="180" rx="8"
          fill="rgba(13,21,32,.9)" stroke="rgba(0,229,255,.15)" stroke-width="1"/>
        <text x="120" y="18" text-anchor="middle" font-size="9" fill="#5a7a9a"
          font-family="'Rajdhani',sans-serif" letter-spacing=".1em">FRONT VIEW</text>
        <!-- 机身 -->
        <rect x="105" y="60" width="30" height="80" rx="6"
          fill="#2a3440" stroke="#5a6a7a" stroke-width="1"/>
        <!-- 左翼 -->
        <g id="frontWingLeft">
          <path fill="#1a2838" stroke="#3a5a7a" stroke-width="1.2"/>
        </g>
        <!-- 右翼 -->
        <g id="frontWingRight">
          <path fill="#1a2838" stroke="#3a5a7a" stroke-width="1.2"/>
        </g>
        <!-- 扑动轴线 -->
        <line x1="120" y1="90" x2="120" y2="90" stroke="#5a7a9a"
          stroke-width=".5" stroke-dasharray="3 2" id="frontAxis"/>
      </g>

      <!-- 相位指示器 -->
      <g transform="translate(60,680)">
        <rect x="0" y="0" width="200" height="80" rx="8"
          fill="rgba(13,21,32,.85)" stroke="rgba(0,229,255,.1)" stroke-width="1"/>
        <text x="100" y="18" text-anchor="middle" font-size="9" fill="#5a7a9a"
          font-family="'Rajdhani',sans-serif" letter-spacing=".1em">PHASE INDICATOR</text>
        <circle cx="100" cy="52" r="22" fill="none" stroke="#2a3a4a" stroke-width="1"/>
        <line id="phaseNeedle" x1="100" y1="52" x2="100" y2="30"
          stroke="#00e5ff" stroke-width="2" stroke-linecap="round"/>
        <circle cx="100" cy="52" r="3" fill="#00e5ff"/>
        <!-- 刻度 -->
        <text x="100" y="26" text-anchor="middle" font-size="7" fill="#3a5a7a">0°</text>
        <text x="128" y="55" text-anchor="middle" font-size="7" fill="#3a5a7a">90°</text>
        <text x="100" y="80" text-anchor="middle" font-size="7" fill="#3a5a7a">180°</text>
        <text x="72" y="55" text-anchor="middle" font-size="7" fill="#3a5a7a">270°</text>
      </g>

      <!-- 角度显示 -->
      <g transform="translate(280,680)">
        <rect x="0" y="0" width="160" height="80" rx="8"
          fill="rgba(13,21,32,.85)" stroke="rgba(0,229,255,.1)" stroke-width="1"/>
        <text x="80" y="18" text-anchor="middle" font-size="9" fill="#5a7a9a"
          font-family="'Rajdhani',sans-serif" letter-spacing=".1em">FLAP ANGLE</text>
        <text id="flapAngleText" x="80" y="58" text-anchor="middle"
          font-family="'Rajdhani',sans-serif" font-size="32" font-weight="700"
          fill="#00e5ff">0.0°</text>
      </g>

      <!-- 核心创新标注 -->
      <g id="innovLabels" font-family="'Noto Sans SC',sans-serif" font-size="10">
      </g>

    </svg>
  </div>

  <!-- 右侧面板 -->
  <div class="panel">
    <!-- 运行状态 -->
    <div class="card">
      <h3><i class="fas fa-wave-square"></i> 运行状态</h3>
      <div class="status-row">
        <span class="label">当前相位</span>
        <span id="phaseLabel" class="phase-badge phase-down">下扑</span>
      </div>
      <div class="status-row">
        <span class="label">上侧带张力</span>
        <span id="tension1Val" class="val val-cyan">0%</span>
      </div>
      <div class="tension-bar"><div id="tension1Bar" class="fill fill-cyan" style="width:50%"/></div>
      <div class="status-row" style="margin-top:8px">
        <span class="label">下侧带张力</span>
        <span id="tension2Val" class="val val-amber">0%</span>
      </div>
      <div class="tension-bar"><div id="tension2Bar" class="fill fill-amber" style="width:50%"/></div>
      <div class="status-row" style="margin-top:8px">
        <span class="label">储能状态</span>
        <span id="energyVal" class="val val-green">--</span>
      </div>
      <div class="status-row">
        <span class="label">凸轮转角</span>
        <span id="camAngleVal" class="val" style="color:var(--metal-hi)">0°</span>
      </div>
    </div>

    <!-- 控制 -->
    <div class="card">
      <h3><i class="fas fa-sliders-h"></i> 控制</h3>
      <div class="ctrl-row">
        <label>转速</label>
        <input type="range" id="speedSlider" min="10" max="300" value="80"/>
        <span id="speedVal" class="range-val">1.0x</span>
      </div>
      <div style="display:flex;gap:8px;margin:10px 0">
        <button class="btn active" id="btnPlay"><i class="fas fa-pause"></i> 暂停</button>
        <button class="btn" id="btnReset"><i class="fas fa-undo"></i> 复位</button>
      </div>
    </div>

    <!-- 显示选项 -->
    <div class="card">
      <h3><i class="fas fa-eye"></i> 显示</h3>
      <div class="toggle-row">
        <span>能量流粒子</span>
        <div class="toggle on" id="togEnergy" data-key="energy"></div>
      </div>
      <div class="toggle-row">
        <span>应力分布</span>
        <div class="toggle" id="togStress" data-key="stress"></div>
      </div>
      <div class="toggle-row">
        <span>力方向箭头</span>
        <div class="toggle" id="togForce" data-key="force"></div>
      </div>
      <div class="toggle-row">
        <span>技术标注</span>
        <div class="toggle on" id="togLabels" data-key="labels"></div>
      </div>
    </div>

    <!-- IFR 解读 -->
    <div class="card">
      <h3><i class="fas fa-lightbulb"></i> IFR 要点</h3>
      <div class="ifr-point">
        <strong>消除铰链:</strong>镍钛合金带仅受拉力,以挠性传动替代全部刚性连杆与铰链副。
      </div>
      <div class="ifr-point">
        <strong>自消震:</strong>超弹性合金在拉伸时储能、回缩时释能,非线性刚度在死点自动吸收冲击。
      </div>
      <div class="ifr-point">
        <strong>资源巧用:</strong>同一合金带兼具传动、吸震、蓄能三重功能——系统未增加新部件。
      </div>
    </div>
  </div>
</div>

<script>
(function(){
  /* ============= 配置常量 ============= */
  const C = {
    cam:  { cx:380, cy:400, r:55, ecc:22 },
    pullA:{ cx:660, cy:280, r:22 },
    pullB:{ cx:660, cy:520, r:22 },
    rocker:{ px:1020, py:400, armLen:100, maxAngle: 28*Math.PI/180 },
    motor:{ x:120, y:355, w:140, h:90 },
    wingLen: 260, wingChord: 55
  };

  /* ============= 状态 ============= */
  let angle = 0;
  let speed = 1;
  let running = true;
  let showEnergy = true;
  let showStress = false;
  let showForce = false;
  let showLabels = true;
  let lastTime = 0;
  const particles = [];

  /* ============= DOM 缓存 ============= */
  const $ = id => document.getElementById(id);
  const svg = $('mainSvg');

  const camPtU = $('camPointUpper');
  const camPtL = $('camPointLower');
  const camSlotU = $('camSlotUpper');
  const camSlotL = $('camSlotLower');
  const strip1Main = $('strip1Main');
  const strip2Main = $('strip2Main');
  const strip1Glow = $('strip1Glow');
  const strip2Glow = $('strip2Glow');
  const stressS1 = $('stressStrip1');
  const stressS2 = $('stressStrip2');
  const stressGroup = $('stressGroup');
  const energyGroup = $('energyGroup');
  const forceGroup = $('forceGroup');
  const rockerArm = $('rockerArm');
  const rockerU = $('rockerUpper');
  const rockerL = $('rockerLower');
  const wingShape = $('wingShape');
  const wingV1 = $('wingVein1');
  const wingV2 = $('wingVein2');
  const wingV3 = $('wingVein3');
  const phaseNeedle = $('phaseNeedle');
  const flapAngleText = $('flapAngleText');
  const phaseLabel = $('phaseLabel');
  const tension1Val = $('tension1Val');
  const tension2Val = $('tension2Val');
  const tension1Bar = $('tension1Bar');
  const tension2Bar = $('tension2Bar');
  const energyVal = $('energyVal');
  const camAngleVal = $('camAngleVal');
  const innovLabels = $('innovLabels');

  /* ============= 几何计算 ============= */
  // 凸轮连接点位置
  function camUpper(a){
    return { x: C.cam.cx + C.cam.ecc * Math.sin(a), y: C.cam.cy - C.cam.ecc * Math.cos(a) };
  }
  function camLower(a){
    return { x: C.cam.cx - C.cam.ecc * Math.sin(a), y: C.cam.cy + C.cam.ecc * Math.cos(a) };
  }

  // 摇臂端点位置
  function rockerEndpoints(flapAngle){
    const a = flapAngle;
    const px = C.rocker.px, py = C.rocker.py, L = C.rocker.armLen;
    const ux = px + L * Math.sin(a - 0.4);
    const uy = py - L * Math.cos(a - 0.4);
    const lx = px + L * Math.sin(a + 0.4);
    const ly = py - L * Math.cos(a + 0.4);
    return { upper:{x:ux,y:uy}, lower:{x:lx,y:ly} };
  }

  // 摇臂末端(翼根连接点)
  function rockerTip(flapAngle){
    const L = C.rocker.armLen * 1.05;
    return {
      x: C.rocker.px + L * Math.sin(flapAngle),
      y: C.rocker.py - L * Math.cos(flapAngle)
    };
  }

  // 计算合金带S型路径
  // 上侧带:凸轮上连接点 → 下滑轮(下绕) → 上滑轮(上绕) → 摇臂上端
  function strip1Path(cu, ru){
    const pA = C.pullA, pB = C.pullB;
    // 下滑轮:从左侧入,底部绕过,从右侧出向上
    const bLeft  = { x: pB.cx - pB.r - 4, y: pB.cy + pB.r * 0.3 };
    const bRight = { x: pB.cx + pB.r + 4, y: pB.cy - pB.r * 0.5 };
    // 上滑轮:从左侧入(从下),顶部绕过,右侧出向右
    const aLeft  = { x: pA.cx - pA.r - 4, y: pA.cy + pA.r * 0.5 };
    const aRight = { x: pA.cx + pA.r + 4, y: pA.cy - pA.r * 0.3 };

    return `M ${cu.x} ${cu.y}
      C ${cu.x+30} ${cu.y+40}, ${bLeft.x-30} ${bLeft.y}, ${bLeft.x} ${bLeft.y}
      A ${pB.r} ${pB.r} 0 1 1 ${bRight.x} ${bRight.y}
      C ${bRight.x+20} ${bRight.y-50}, ${aLeft.x-20} ${aLeft.y+50}, ${aLeft.x} ${aLeft.y}
      A ${pA.r} ${pA.r} 0 1 1 ${aRight.x} ${aRight.y}
      C ${aRight.x+30} ${aRight.y-20}, ${ru.x-40} ${ru.y+10}, ${ru.x} ${ru.y}`;
  }

  // 下侧带:凸轮下连接点 → 上滑轮(上绕) → 下滑轮(下绕) → 摇臂下端
  function strip2Path(cl, rl){
    const pA = C.pullA, pB = C.pullB;
    const aLeft  = { x: pA.cx - pA.r - 4, y: pA.cy - pA.r * 0.3 };
    const aRight = { x: pA.cx + pA.r + 4, y: pA.cy + pA.r * 0.5 };
    const bLeft  = { x: pB.cx - pB.r - 4, y: pB.cy - pB.r * 0.5 };
    const bRight = { x: pB.cx + pB.r + 4, y: pB.cy + pB.r * 0.3 };

    return `M ${cl.x} ${cl.y}
      C ${cl.x+30} ${cl.y-40}, ${aLeft.x-30} ${aLeft.y}, ${aLeft.x} ${aLeft.y}
      A ${pA.r} ${pA.r} 0 1 0 ${aRight.x} ${aRight.y}
      C ${aRight.x+20} ${aRight.y+50}, ${bLeft.x-20} ${bLeft.y-50}, ${bLeft.x} ${bLeft.y}
      A ${pB.r} ${pB.r} 0 1 0 ${bRight.x} ${bRight.y}
      C ${bRight.x+30} ${bRight.y+20}, ${rl.x-40} ${rl.y-10}, ${rl.x} ${rl.y}`;
  }

  // 张力计算(归一化 0~1)
  function calcTension(a){
    const t1 = 0.5 + 0.5 * Math.sin(a);  // 上侧带张力
    const t2 = 0.5 - 0.5 * Math.sin(a);  // 下侧带张力
    return { t1, t2 };
  }

  // 翼面路径生成
  function wingPath(tip, flapAngle){
    const tx = tip.x, ty = tip.y;
    const L = C.wingLen, Ch = C.wingChord;
    const ca = Math.cos(flapAngle), sa = Math.sin(flapAngle);
    // 翼面沿扑动方向延伸
    const ex = tx + L * sa;
    const ey = ty - L * ca;
    // 翼弦方向
    const cx2 = -ca * Ch * 0.5;
    const cy2 = -sa * Ch * 0.5;
    // 前缘弧线
    const mid = { x: (tx+ex)/2 - ca*Ch*0.15, y: (ty+ey)/2 - sa*Ch*0.15 };
    return `M ${tx+cx2} ${ty+cy2}
      Q ${mid.x+cx2*0.8} ${mid.y+cy2*0.8} ${ex+cx2*0.3} ${ey+cy2*0.3}
      L ${ex-cx2*0.2} ${ey-cy2*0.2}
      Q ${mid.x-cx2*0.5} ${mid.y-cy2*0.5} ${tx-cx2*0.6} ${ty-cy2*0.6}
      Z`;
  }

  function wingVeinPath(tip, flapAngle, offset){
    const tx = tip.x, ty = tip.y;
    const L = C.wingLen * (0.6 + offset * 0.25);
    const sa = Math.sin(flapAngle), ca = Math.cos(flapAngle);
    const ex = tx + L * sa;
    const ey = ty - L * ca;
    return `M ${tx} ${ty} L ${ex} ${ey}`;
  }

  /* ============= 能量粒子系统 ============= */
  function initParticles(){
    energyGroup.innerHTML = '';
    for(let i = 0; i < 20; i++){
      const c1 = document.createElementNS('http://www.w3.org/2000/svg','circle');
      c1.setAttribute('r','3');
      c1.setAttribute('fill','#00e5ff');
      c1.setAttribute('opacity','0');
      c1.dataset.strip = '1';
      c1.dataset.t = String(i/20);
      energyGroup.appendChild(c1);

      const c2 = document.createElementNS('http://www.w3.org/2000/svg','circle');
      c2.setAttribute('r','3');
      c2.setAttribute('fill','#ff9100');
      c2.setAttribute('opacity','0');
      c2.dataset.strip = '2';
      c2.dataset.t = String(i/20);
      energyGroup.appendChild(c2);
    }
  }

  function updateParticles(t1, t2){
    if(!showEnergy){ 
      energyGroup.querySelectorAll('circle').forEach(c=>c.setAttribute('opacity','0'));
      return; 
    }
    const path1 = strip1Main;
    const path2 = strip2Main;
    const len1 = path1.getTotalLength();
    const len2 = path2.getTotalLength();

    energyGroup.querySelectorAll('circle').forEach(c=>{
      const strip = c.dataset.strip;
      const t = parseFloat(c.dataset.t);
      const tension = strip === '1' ? t1 : t2;
      // 只有在张力方向正确时才显示粒子(上侧带在下扑时传能,下侧带在上扑时传能)
      const active = strip === '1' ? (tension > 0.4) : (tension > 0.4);
      if(!active){
        c.setAttribute('opacity','0');
        return;
      }
      // 粒子沿路径运动
      const speed2 = tension * 0.008;
      let newT = t + speed2;
      if(newT > 1) newT -= 1;
      c.dataset.t = String(newT);

      const path = strip === '1' ? path1 : path2;
      const len = strip === '1' ? len1 : len2;
      try{
        const pt = path.getPointAtLength(newT * len);
        c.setAttribute('cx', String(pt.x));
        c.setAttribute('cy', String(pt.y));
        c.setAttribute('opacity', String(tension * 0.8));
        c.setAttribute('r', String(2 + tension * 2));
      }catch(e){}
    });
  }

  /* ============= 力箭头 ============= */
  function updateForceArrows(cu, cl, ru, rl, t1, t2){
    forceGroup.innerHTML = '';
    if(!showForce) return;

    function arrow(x1,y1,x2,y2,color){
      const ln = document.createElementNS('http://www.w3.org/2000/svg','line');
      ln.setAttribute('x1',x1); ln.setAttribute('y1',y1);
      ln.setAttribute('x2',x2); ln.setAttribute('y2',y2);
      ln.setAttribute('stroke',color);
      ln.setAttribute('stroke-width','2.5');
      ln.setAttribute('marker-end', color==='#00e5ff'?'url(#arrowCyan)':'url(#arrowAmber)');
      ln.setAttribute('opacity', String(Math.max(0.2, color==='#00e5ff'?t1:t2)*0.8));
      forceGroup.appendChild(ln);
    }
    // 上侧带拉力方向:从摇臂端指向凸轮端(带被拉伸时)
    if(t1 > 0.3){
      const dx = cu.x - ru.x, dy = cu.y - ru.y;
      const d = Math.sqrt(dx*dx+dy*dy) || 1;
      arrow(ru.x, ru.y, ru.x+dx/d*40*t1, ru.y+dy/d*40*t1, '#00e5ff');
    }
    // 下侧带拉力方向
    if(t2 > 0.3){
      const dx = cl.x - rl.x, dy = cl.y - rl.y;
      const d = Math.sqrt(dx*dx+dy*dy) || 1;
      arrow(rl.x, rl.y, rl.x+dx/d*40*t2, rl.y+dy/d*40*t2, '#ff9100');
    }
  }

  /* ============= 应力可视化 ============= */
  function updateStress(t1, t2){
    if(!showStress){
      stressGroup.setAttribute('opacity','0');
      return;
    }
    stressGroup.setAttribute('opacity','0.7');
    // 用渐变色表示应力:低应力=绿,高应力=红
    function stressColor(t){
      const r = Math.round(255 * t);
      const g = Math.round(255 * (1-t));
      return `rgb(${r},${g},60)`;
    }
    stressS1.setAttribute('d', strip1Main.getAttribute('d'));
    stressS1.setAttribute('stroke', stressColor(t1));
    stressS2.setAttribute('d', strip2Main.getAttribute('d'));
    stressS2.setAttribute('stroke', stressColor(t2));
  }

  /* ============= 技术标注 ============= */
  function updateLabels(cu, cl, t1, t2){
    innovLabels.innerHTML = '';
    if(!showLabels) return;

    function label(x, y, text, color, anchor){
      const t = document.createElementNS('http://www.w3.org/2000/svg','text');
      t.setAttribute('x',x); t.setAttribute('y',y);
      t.setAttribute('fill',color || '#5a7a9a');
      t.setAttribute('text-anchor',anchor||'start');
      t.setAttribute('font-size','10');
      t.setAttribute('font-family',"'Rajdhani','Noto Sans SC',sans-serif");
      t.textContent = text;
      innovLabels.appendChild(t);
    }
    function line(x1,y1,x2,y2,color,dash){
      const l = document.createElementNS('http://www.w3.org/2000/svg','line');
      l.setAttribute('x1',x1);l.setAttribute('y1',y1);
      l.setAttribute('x2',x2);l.setAttribute('y2',y2);
      l.setAttribute('stroke',color||'#2a4a5a');
      l.setAttribute('stroke-width','.8');
      if(dash) l.setAttribute('stroke-dasharray',dash);
      innovLabels.appendChild(l);
    }

    // 凸轮偏心标注
    label(C.cam.cx - 75, C.cam.cy - 55, '偏心槽升程 6mm', '#00e5ff');
    line(C.cam.cx - 75, C.cam.cy - 50, cu.x - 10, cu.y + 5, '#00e5ff', '3 2');

    // 滑轮中心距标注
    label(C.pullA.cx + 32, (C.pullA.cy + C.pullB.cy)/2, '中心距 30mm', '#5a7a9a', 'start');
    line(C.pullA.cx + 28, C.pullA.cy + 8, C.pullB.cx + 28, C.pullB.cy - 8, '#3a5a6a', '3 2');

    // 合金带参数标注
    const midStripX = 520;
    label(midStripX - 70, C.pullA.cy - 18, 'NiTi合金带 0.15×3mm', '#00e5ff');
    label(midStripX - 70, C.pullA.cy - 6, '工作应力 150MPa', '#3a8a9a');

    // 张力状态动态标注
    if(t1 > 0.6){
      label(500, C.pullB.cy + 10, '◀ 储能 → 释能加速', '#00e5ff');
    }
    if(t2 > 0.6){
      label(500, C.pullA.cy - 30, '◀ 储能 → 释能加速', '#ff9100');
    }

    // 超弹性标注
    label(C.rocker.px - 20, C.rocker.py + 75, '超弹性: 非线性刚度', '#00e676');
    label(C.rocker.px - 20, C.rocker.py + 87, '死点吸震 · 行程中段加力', '#00884a');
  }

  /* ============= 正视图小窗更新 ============= */
  function updateFrontView(flapAngle){
    const cx = 120, cy = 90;
    const wingL = 85;
    const sa = Math.sin(flapAngle), ca = Math.cos(flapAngle);

    // 左翼
    const lw = document.querySelector('#frontWingLeft path');
    const lx = cx - 15, ly = cy;
    const ltx = lx - wingL * sa;
    const lty = ly - wingL * ca;
    lw.setAttribute('d', `M ${lx} ${ly} Q ${(lx+ltx)/2-5} ${(ly+lty)/2-8} ${ltx} ${lty}
      L ${ltx} ${lty+6} Q ${(lx+ltx)/2+2} ${(ly+lty)/2+4} ${lx} ${ly+3} Z`);

    // 右翼
    const rw = document.querySelector('#frontWingRight path');
    const rx = cx + 15, ry = cy;
    const rtx = rx + wingL * sa;
    const rty = ry - wingL * ca;
    rw.setAttribute('d', `M ${rx} ${ry} Q ${(rx+rtx)/2+5} ${(ry+rty)/2-8} ${rtx} ${rty}
      L ${rtx} ${rty+6} Q ${(rx+rtx)/2-2} ${(ry+rty)/2+4} ${rx} ${ry+3} Z`);

    // 轴线
    const ax = document.getElementById('frontAxis');
    ax.setAttribute('x1', cx - wingL - 20);
    ax.setAttribute('x2', cx + wingL + 20);
    ax.setAttribute('y1', cy);
    ax.setAttribute('y2', cy);
  }

  /* ============= 主渲染循环 ============= */
  function render(now){
    if(!lastTime) lastTime = now;
    const dt = Math.min(now - lastTime, 50);
    lastTime = now;

    if(running){
      angle += 0.0015 * speed * dt;
      if(angle > Math.PI * 2) angle -= Math.PI * 2;
    }

    // 计算关键位置
    const cu = camUpper(angle);
    const cl = camLower(angle);
    const { t1, t2 } = calcTension(angle);
    const flapAngle = C.rocker.maxAngle * Math.sin(angle);
    const re = rockerEndpoints(flapAngle);
    const tip = rockerTip(flapAngle);

    // 更新凸轮连接点
    camPtU.setAttribute('cx', cu.x); camPtU.setAttribute('cy', cu.y);
    camPtL.setAttribute('cx', cl.x); camPtL.setAttribute('cy', cl.y);

    // 更新凸轮槽标记
    camSlotU.setAttribute('x1', C.cam.cx); camSlotU.setAttribute('y1', C.cam.cy);
    camSlotU.setAttribute('x2', cu.x); camSlotU.setAttribute('y2', cu.y);
    camSlotL.setAttribute('x1', C.cam.cx); camSlotL.setAttribute('y1', C.cam.cy);
    camSlotL.setAttribute('x2', cl.x); camSlotL.setAttribute('y2', cl.y);

    // 更新合金带路径
    const p1 = strip1Path(cu, re.upper);
    const p2 = strip2Path(cl, re.lower);
    strip1Main.setAttribute('d', p1);
    strip2Main.setAttribute('d', p2);
    strip1Glow.setAttribute('d', p1);
    strip2Glow.setAttribute('d', p2);

    // 合金带颜色与宽度随张力变化
    const alpha1 = 0.3 + t1 * 0.7;
    const alpha2 = 0.3 + t2 * 0.7;
    const w1 = 2 + t1 * 2;
    const w2 = 2 + t2 * 2;
    strip1Main.setAttribute('stroke-width', w1.toFixed(1));
    strip1Main.setAttribute('opacity', alpha1.toFixed(2));
    strip1Glow.setAttribute('opacity', (t1 * 0.3).toFixed(2));
    strip2Main.setAttribute('stroke-width', w2.toFixed(1));
    strip2Main.setAttribute('opacity', alpha2.toFixed(2));
    strip2Glow.setAttribute('opacity', (t2 * 0.3).toFixed(2));

    // 凸轮连接点辉光
    camPtU.setAttribute('r', String(3 + t1 * 4));
    camPtL.setAttribute('r', String(3 + t2 * 4));

    // 摇臂
    rockerArm.setAttribute('x2', tip.x);
    rockerArm.setAttribute('y2', tip.y);
    rockerU.setAttribute('cx', re.upper.x); rockerU.setAttribute('cy', re.upper.y);
    rockerL.setAttribute('cx', re.lower.x); rockerL.setAttribute('cy', re.lower.y);
    // 摇臂连接点大小随张力
    rockerU.setAttribute('r', String(3 + t1 * 3));
    rockerL.setAttribute('r', String(3 + t2 * 3));

    // 翼面
    const wp = wingPath(tip, flapAngle);
    wingShape.setAttribute('d', wp);
    wingV1.setAttribute('d', wingVeinPath(tip, flapAngle, 0));
    wingV2.setAttribute('d', wingVeinPath(tip, flapAngle, 1));
    wingV3.setAttribute('d', wingVeinPath(tip, flapAngle, 2));

    // 相位指针
    const needleX = 100 + 22 * Math.sin(angle);
    const needleY = 52 - 22 * Math.cos(angle);
    phaseNeedle.setAttribute('x2', needleX.toFixed(1));
    phaseNeedle.setAttribute('y2', needleY.toFixed(1));

    // 扑动角度显示
    const flapDeg = (flapAngle * 180 / Math.PI).toFixed(1);
    flapAngleText.textContent = flapDeg + '°';

    // 相位标签
    const sinA = Math.sin(angle);
    if(sinA > 0.2){
      phaseLabel.textContent = '下扑';
      phaseLabel.className = 'phase-badge phase-down';
    } else if(sinA < -0.2){
      phaseLabel.textContent = '上扑';
      phaseLabel.className = 'phase-badge phase-up';
    } else {
      phaseLabel.textContent = '换向';
      phaseLabel.className = 'phase-badge phase-trans';
    }

    // 张力面板
    tension1Val.textContent = Math.round(t1*100) + '%';
    tension2Val.textContent = Math.round(t2*100) + '%';
    tension1Bar.style.width = (t1*100)+'%';
    tension2Bar.style.width = (t2*100)+'%';

    // 储能状态
    const energyLevel = Math.abs(Math.cos(angle));
    if(energyLevel > 0.7){
      energyVal.textContent = '高储能';
      energyVal.className = 'val val-red';
    } else if(energyLevel > 0.3){
      energyVal.textContent = '释能中';
      energyVal.className = 'val val-green';
    } else {
      energyVal.textContent = '低储能';
      energyVal.className = 'val val-amber';
    }

    // 凸轮转角
    camAngleVal.textContent = ((angle * 180 / Math.PI) % 360).toFixed(0) + '°';

    // 正视图
    updateFrontView(flapAngle);

    // 子系统更新
    updateParticles(t1, t2);
    updateStress(t1, t2);
    updateForceArrows(cu, cl, re.upper, re.lower, t1, t2);
    updateLabels(cu, cl, t1, t2);

    requestAnimationFrame(render);
  }

  /* ============= 交互绑定 ============= */
  // 速度滑块
  $('speedSlider').addEventListener('input', function(){
    speed = this.value / 80;
    $('speedVal').textContent = speed.toFixed(1) + 'x';
  });

  // 播放/暂停
  $('btnPlay').addEventListener('click', function(){
    running = !running;
    this.innerHTML = running ? '<i class="fas fa-pause"></i> 暂停' : '<i class="fas fa-play"></i> 播放';
    this.classList.toggle('active', running);
  });

  // 复位
  $('btnReset').addEventListener('click', function(){
    angle = 0;
  });

  // 开关切换
  document.querySelectorAll('.toggle').forEach(tog => {
    tog.addEventListener('click', function(){
      this.classList.toggle('on');
      const key = this.dataset.key;
      const isOn = this.classList.contains('on');
      if(key === 'energy') showEnergy = isOn;
      if(key === 'stress') showStress = isOn;
      if(key === 'force') showForce = isOn;
      if(key === 'labels') showLabels = isOn;
    });
  });

  /* ============= 初始化 ============= */
  initParticles();
  requestAnimationFrame(render);

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

实现说明:

这是一个基于 TRIZ 最终理想解(IFR)思想设计的高保真 SVG 原理动画,完整展示了镍钛超弹性合金带替代刚性连杆的柔性扑翼传动机构。

核心视觉设计:

  • 深蓝工程蓝底色配青色/琥珀双色调,上侧合金带(青色)与下侧合金带(琥珀色)形成鲜明对比
  • 合金带宽度与亮度随张力实时变化——收紧时变亮变粗,放松时变暗变细,直观展示"肌肉腱索"式传力
  • S型交叉路径清晰可见:上侧带从凸轮上槽→下绕滑轮B→上绕滑轮A→摇臂上端;下侧带反向路径

动态交互特性:

  1. 能量流粒子:沿合金带路径流动的光点,流速与张力成正比,可视化力与能量的传递方向
  2. 应力分布:可切换显示渐变色应力图(绿→红),直观呈现合金带各段受力状态
  3. 力方向箭头:在摇臂端显示拉力方向与大小
  4. 技术标注:随动画动态更新,在储能/释能阶段自动切换提示文本
  5. 正视图小窗:右上角实时显示双翼对称扑动的正面视角
  6. 相位指示器:左下角圆盘指针+扑动角度数字显示

IFR 核心表达:

  • 动画直接展示理想状态运作,无传统连杆对比
  • 合金带同时承担传动、吸震、蓄能三重功能——"系统未增加新部件"的IFR精髓通过储能状态面板和动态标注突出
  • 死点位置的冲击吸收通过合金带辉光脉动和"换向"相位标签体现
积分规则:第一轮对话扣减6分,后续每轮扣4分