<!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=Syne:wght@400;600;700;800&family=IBM+Plex+Mono:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--bg:#070b12;--surface:#0d1420;--card:#111b2a;--text:#cdd8e6;
--muted:#4e6178;--accent:#00e5a0;--accent-dim:#003d2b;
--load:#ff6b35;--gravity:#ff2e50;--tension:#00ccff;
--struct:#3d5a80;--struct-active:#00e5a0;--belt-slack:#2e4050;
--belt-taut:#00ff88;--spring:#ffd556;--border:#1a2840;
}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg);color:var(--text);font-family:'IBM Plex Mono',monospace;min-height:100vh;overflow-x:hidden}
.page{max-width:1440px;margin:0 auto;padding:16px 24px 32px}
header{text-align:center;padding:22px 0 14px;border-bottom:1px solid var(--border);margin-bottom:16px}
header h1{font-family:'Syne',sans-serif;font-weight:800;font-size:1.75rem;letter-spacing:-.02em;background:linear-gradient(135deg,var(--accent),var(--tension));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
header p{color:var(--muted);font-size:.72rem;margin-top:6px;font-weight:300;letter-spacing:.04em}
.main{display:flex;gap:16px;align-items:stretch}
.viz{flex:1;background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden;position:relative;min-height:520px}
.viz::before{content:'';position:absolute;inset:0;background:radial-gradient(ellipse at 35% 55%,rgba(0,229,160,.025) 0%,transparent 60%),radial-gradient(ellipse at 65% 45%,rgba(0,204,255,.02) 0%,transparent 60%);pointer-events:none}
.viz svg{width:100%;height:100%;display:block}
aside{width:300px;display:flex;flex-direction:column;gap:12px;flex-shrink:0}
.card{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:14px 16px}
.card h3{font-family:'Syne',sans-serif;font-weight:700;font-size:.72rem;text-transform:uppercase;letter-spacing:.1em;color:var(--muted);margin-bottom:10px}
.gauge-wrap{display:flex;align-items:flex-end;gap:14px}
.gauge-bar{position:relative;width:32px;height:160px;background:var(--bg);border-radius:16px;overflow:hidden;border:1px solid var(--border)}
.gauge-fill{position:absolute;bottom:0;left:0;right:0;border-radius:16px;transition:height .4s cubic-bezier(.4,0,.2,1);background:linear-gradient(to top,var(--accent-dim),var(--accent))}
.gauge-fill::after{content:'';position:absolute;top:0;left:50%;transform:translateX(-50%);width:16px;height:4px;background:rgba(255,255,255,.5);border-radius:2px;margin-top:2px}
.gauge-info{flex:1}
.gauge-val{font-family:'Syne',sans-serif;font-size:2.2rem;font-weight:800;color:var(--accent);line-height:1}
.gauge-val small{font-size:.7rem;font-weight:400;color:var(--muted);margin-left:2px}
.gauge-label{font-size:.65rem;color:var(--muted);margin-top:4px}
.phase-row{display:flex;gap:8px;align-items:center;margin-top:12px}
.phase-dot{width:8px;height:8px;border-radius:50%;background:var(--muted);transition:all .3s}
.phase-dot.on{background:var(--accent);box-shadow:0 0 8px var(--accent)}
.phase-text{font-size:.78rem;font-weight:600;color:var(--accent);min-width:80px}
.param-row{display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--border);font-size:.7rem}
.param-row:last-child{border-bottom:none}
.param-k{color:var(--muted)}.param-v{color:var(--text);font-weight:500}
.param-v.hl{color:var(--accent)}
.ctrls{margin-top:16px;background:var(--card);border:1px solid var(--border);border-radius:12px;padding:18px 20px}
.slider-row{display:flex;align-items:center;gap:14px;margin-bottom:14px}
.slider-lbl{font-size:.75rem;color:var(--muted);white-space:nowrap;min-width:56px}
.slider-val{font-family:'Syne',sans-serif;font-weight:700;font-size:1.15rem;color:var(--load);min-width:56px;text-align:right}
input[type=range]{flex:1;-webkit-appearance:none;appearance:none;height:6px;background:var(--bg);border-radius:3px;outline:none}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:22px;height:22px;border-radius:50%;background:var(--load);cursor:pointer;box-shadow:0 0 12px rgba(255,107,53,.45)}
input[type=range]::-moz-range-thumb{width:22px;height:22px;border-radius:50%;background:var(--load);cursor:pointer;border:none}
.btn-row{display:flex;gap:10px;flex-wrap:wrap}
.btn{padding:7px 18px;border:1px solid var(--border);border-radius:8px;background:var(--surface);color:var(--text);font-family:'IBM Plex Mono',monospace;font-size:.75rem;cursor:pointer;transition:all .2s;user-select:none}
.btn:hover{border-color:var(--accent);color:var(--accent);background:rgba(0,229,160,.04)}
.btn.on{border-color:var(--accent);background:rgba(0,229,160,.1);color:var(--accent)}
.ifr-box{margin-top:14px;padding:14px 16px;background:rgba(0,229,160,.04);border:1px solid rgba(0,229,160,.12);border-radius:10px;font-size:.7rem;line-height:1.75;color:var(--muted)}
.ifr-box strong{color:var(--accent);font-weight:600}
.ifr-box em{color:var(--tension);font-style:normal;font-weight:500}
.top-view-svg{width:100%;height:auto;display:block}
.force-svg{width:100%;height:auto;display:block}
@media(max-width:960px){
.main{flex-direction:column}
aside{width:100%;flex-direction:row;flex-wrap:wrap}
.card{flex:1;min-width:180px}
.viz{min-height:400px}
}
</style>
</head>
<body>
<div class="page">
<header>
<h1>应力自适应张紧底盘</h1>
<p>TRIZ 最终理想解 (IFR) 原理演示 — 载荷自补偿刚度增强机制</p>
</header>
<div class="main">
<div class="viz">
<svg id="mech" viewBox="0 0 1000 620" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- 辉光滤镜 -->
<filter id="glow" x="-40%" y="-40%" width="180%" height="180%">
<feGaussianBlur stdDeviation="8" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="glowSm" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="4" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="18" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- 箭头标记 -->
<marker id="arrGravity" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="8" markerHeight="8" orient="auto">
<path d="M0,1 L10,5 L0,9" fill="#ff2e50"/>
</marker>
<marker id="arrTension" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto">
<path d="M0,1 L10,5 L0,9" fill="#00ccff"/>
</marker>
<marker id="arrSupport" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="7" markerHeight="7" orient="auto">
<path d="M0,1 L10,5 L0,9" fill="#00e5a0"/>
</marker>
<marker id="arrCompress" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M0,1 L10,5 L0,9" fill="#ffd556"/>
</marker>
<!-- 网格图案 -->
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M40,0 L0,0 0,40" fill="none" stroke="#0f1a28" stroke-width=".5"/>
</pattern>
<!-- 盆体填充渐变 -->
<linearGradient id="basinFill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#0f1c2e"/>
<stop offset="100%" stop-color="#0a1320"/>
</linearGradient>
</defs>
<!-- 背景网格 -->
<rect width="1000" height="620" fill="url(#grid)" opacity=".5"/>
<!-- ============ 盆体辉光层 ============ -->
<path id="basinGlow" d="" fill="none" stroke="#00e5a0" stroke-width="22" filter="url(#softGlow)" opacity="0"/>
<!-- ============ 盆体结构 ============ -->
<!-- 盆体内部填充 -->
<path id="basinFillPath" d="" fill="url(#basinFill)" stroke="none"/>
<!-- 盆体轮廓 -->
<path id="basinOutline" d="" fill="none" stroke="#3d5a80" stroke-width="5" stroke-linejoin="round" stroke-linecap="round"/>
<!-- 盆体厚度标注线 -->
<g id="thicknessCallout" opacity=".6">
<line x1="252" y1="350" x2="252" y2="370" stroke="#5a7a9a" stroke-width=".8" stroke-dasharray="2,2"/>
<line x1="248" y1="350" x2="248" y2="370" stroke="#5a7a9a" stroke-width=".8" stroke-dasharray="2,2"/>
<line x1="250" y1="352" x2="250" y2="368" stroke="#5a7a9a" stroke-width=".5"/>
<text x="232" y="364" fill="#5a7a9a" font-size="9" text-anchor="end" font-family="IBM Plex Mono">2mm</text>
</g>
<!-- ============ 导向槽 ============ -->
<g id="guideSlots" opacity=".7">
<rect x="246" y="305" width="9" height="22" rx="2" fill="#0a1320" stroke="#2a4a6a" stroke-width=".8"/>
<rect x="246" y="370" width="9" height="22" rx="2" fill="#0a1320" stroke="#2a4a6a" stroke-width=".8"/>
<rect x="745" y="305" width="9" height="22" rx="2" fill="#0a1320" stroke="#2a4a6a" stroke-width=".8"/>
<rect x="745" y="370" width="9" height="22" rx="2" fill="#0a1320" stroke="#2a4a6a" stroke-width=".8"/>
</g>
<!-- ============ 卷簧 ============ -->
<g id="springLeft"></g>
<g id="springRight"></g>
<!-- ============ 张紧带 ============ -->
<path id="beltLeft" d="" fill="none" stroke="#2e4050" stroke-width="2.5" stroke-linecap="round"/>
<path id="beltRight" d="" fill="none" stroke="#2e4050" stroke-width="2.5" stroke-linecap="round"/>
<!-- 张紧带流动效果 -->
<path id="beltFlowL" d="" fill="none" stroke="#00ff88" stroke-width="2" stroke-linecap="round" stroke-dasharray="6,10" opacity="0"/>
<path id="beltFlowR" d="" fill="none" stroke="#00ff88" stroke-width="2" stroke-linecap="round" stroke-dasharray="6,10" opacity="0"/>
<!-- 45°角标注 -->
<g id="angleLabel" opacity="0">
<path id="angleArc" d="" fill="none" stroke="#00ccff" stroke-width="1" stroke-dasharray="3,2"/>
<text id="angleText" x="0" y="0" fill="#00ccff" font-size="11" font-family="IBM Plex Mono" font-weight="500">45°</text>
</g>
<!-- ============ 浮动压盘 ============ -->
<rect id="plate" x="380" y="290" width="240" height="14" rx="3" fill="#2a4a6a" stroke="#4a7a9a" stroke-width="1.5"/>
<!-- 压盘导向柱 (在导向槽内滑动) -->
<rect id="guideL" x="248" y="310" width="5" height="16" rx="1.5" fill="#4a7a9a"/>
<rect id="guideR" x="747" y="310" width="5" height="16" rx="1.5" fill="#4a7a9a"/>
<!-- ============ 载荷 ============ -->
<g id="loadGroup" opacity="0">
<rect id="loadBlock" x="420" y="220" width="160" height="68" rx="4" fill="#3a1a0a" stroke="#ff6b35" stroke-width="2"/>
<text id="loadLabel" x="500" y="260" fill="#ff6b35" font-family="Syne,sans-serif" font-size="20" font-weight="800" text-anchor="middle">50kg</text>
</g>
<!-- ============ 力箭头 ============ -->
<g id="forceArrows" opacity="0">
<!-- 重力箭头 -->
<line id="fGravity" x1="500" y1="290" x2="500" y2="380" stroke="#ff2e50" stroke-width="3" marker-end="url(#arrGravity)"/>
<text id="fGravLabel" x="516" y="345" fill="#ff2e50" font-size="10" font-family="IBM Plex Mono" font-weight="500">F=490N</text>
<!-- 左张力箭头 -->
<line id="fTensionL" x1="0" y1="0" x2="0" y2="0" stroke="#00ccff" stroke-width="2.5" marker-end="url(#arrTension)"/>
<!-- 右张力箭头 -->
<line id="fTensionR" x1="0" y1="0" x2="0" y2="0" stroke="#00ccff" stroke-width="2.5" marker-end="url(#arrTension)"/>
<!-- 左水平分力 (向内压缩) -->
<line id="fCompL" x1="0" y1="0" x2="0" y2="0" stroke="#ffd556" stroke-width="2" marker-end="url(#arrCompress)"/>
<!-- 右水平分力 -->
<line id="fCompR" x1="0" y1="0" x2="0" y2="0" stroke="#ffd556" stroke-width="2" marker-end="url(#arrCompress)"/>
<!-- 左垂直分力 (向上支撑) -->
<line id="fSuppL" x1="0" y1="0" x2="0" y2="0" stroke="#00e5a0" stroke-width="2" marker-end="url(#arrSupport)"/>
<!-- 右垂直分力 -->
<line id="fSuppR" x1="0" y1="0" x2="0" y2="0" stroke="#00e5a0" stroke-width="2" marker-end="url(#arrSupport)"/>
<!-- 力分解虚线 -->
<line id="fDecompL1" x1="0" y1="0" x2="0" y2="0" stroke="#00ccff" stroke-width=".8" stroke-dasharray="4,3" opacity=".5"/>
<line id="fDecompL2" x1="0" y1="0" x2="0" y2="0" stroke="#00ccff" stroke-width=".8" stroke-dasharray="4,3" opacity=".5"/>
<line id="fDecompR1" x1="0" y1="0" x2="0" y2="0" stroke="#00ccff" stroke-width=".8" stroke-dasharray="4,3" opacity=".5"/>
<line id="fDecompR2" x1="0" y1="0" x2="0" y2="0" stroke="#00ccff" stroke-width=".8" stroke-dasharray="4,3" opacity=".5"/>
</g>
<!-- ============ 标注文字 ============ -->
<g id="annotations" font-family="IBM Plex Mono,monospace" font-size="10.5" fill="#5a7a9a">
<text x="500" y="510" text-anchor="middle" fill="#3d5a80" font-size="9">高强钢浅盆体 (2mm)</text>
<text id="annBelt" x="290" y="310" fill="#2e4050" font-size="9.5" transform="rotate(-42,290,310)">张紧带</text>
<text id="annSpring" x="175" y="245" fill="#8a7a30" font-size="9.5">卷簧收紧器</text>
<line id="annSpringLine" x1="220" y1="242" x2="252" y2="240" stroke="#5a5a20" stroke-width=".6" stroke-dasharray="2,2"/>
<text id="annPlate" x="640" y="295" fill="#4a6a8a" font-size="9.5">浮动压盘</text>
<line id="annPlateLine" x1="635" y1="293" x2="622" y2="293" stroke="#3a5a7a" stroke-width=".6" stroke-dasharray="2,2"/>
</g>
<!-- ============ 盆体变形指示 ============ -->
<g id="deformIndicator" opacity="0">
<text id="deformText" x="500" y="480" text-anchor="middle" fill="#ff2e50" font-family="IBM Plex Mono" font-size="10" font-weight="500">屈曲变形区</text>
<path id="deformArrow" d="" fill="none" stroke="#ff2e50" stroke-width="1.2" stroke-dasharray="3,2"/>
</g>
<!-- ============ 状态标签 ============ -->
<g id="stateLabels">
<rect id="stateBg" x="390" y="555" width="220" height="32" rx="6" fill="rgba(0,229,160,.08)" stroke="rgba(0,229,160,.2)" stroke-width="1"/>
<text id="stateText" x="500" y="576" text-anchor="middle" fill="#00e5a0" font-family="Syne,sans-serif" font-size="13" font-weight="700">空载柔性状态</text>
</g>
<!-- IFR 核心注解 -->
<g id="ifrCallout" opacity="0">
<rect x="540" y="130" width="260" height="56" rx="8" fill="rgba(0,229,160,.06)" stroke="rgba(0,229,160,.2)" stroke-width="1"/>
<text x="556" y="152" fill="#00e5a0" font-family="Syne,sans-serif" font-size="11" font-weight="700">IFR: 载荷自身补偿刚度</text>
<text x="556" y="172" fill="#5a8a7a" font-family="IBM Plex Mono" font-size="9">重力→张力→结构绷紧→刚度提升</text>
<line x1="540" y1="168" x2="510" y2="200" stroke="rgba(0,229,160,.2)" stroke-width=".8" stroke-dasharray="3,3"/>
</g>
</svg>
</div>
<aside>
<!-- 刚度仪表 -->
<div class="card">
<h3>系统刚度指数</h3>
<div class="gauge-wrap">
<div class="gauge-bar"><div class="gauge-fill" id="gaugeFill" style="height:5%"></div></div>
<div class="gauge-info">
<div class="gauge-val" id="gaugeVal">5<small>%</small></div>
<div class="gauge-label">相对刚度 (空载=5%)</div>
</div>
</div>
<div class="phase-row">
<div class="phase-dot" id="pd0"></div><div class="phase-dot" id="pd1"></div><div class="phase-dot" id="pd2"></div><div class="phase-dot" id="pd3"></div>
<span class="phase-text" id="phaseText">空载柔性</span>
</div>
</div>
<!-- 力分解图 -->
<div class="card">
<h3>单侧力分解 (45°)</h3>
<svg class="force-svg" viewBox="0 0 260 180" xmlns="http://www.w3.org/2000/svg">
<line x1="60" y1="30" x2="60" y2="150" stroke="#1a2840" stroke-width=".6"/>
<line x1="20" y1="150" x2="240" y2="150" stroke="#1a2840" stroke-width=".6"/>
<!-- 张力T -->
<line id="fdT" x1="60" y1="150" x2="180" y2="30" stroke="#00ccff" stroke-width="2.5"/>
<text x="130" y="78" fill="#00ccff" font-size="11" font-family="IBM Plex Mono" font-weight="600" transform="rotate(-45,130,78)">T=173N</text>
<!-- 重力分量F_v -->
<line id="fdFv" x1="60" y1="150" x2="60" y2="30" stroke="#ff2e50" stroke-width="2" stroke-dasharray="5,3"/>
<text x="28" y="95" fill="#ff2e50" font-size="9.5" font-family="IBM Plex Mono">Fv=122.5N</text>
<!-- 水平分量F_h -->
<line id="fdFh" x1="60" y1="150" x2="180" y2="150" stroke="#ffd556" stroke-width="2" stroke-dasharray="5,3"/>
<text x="100" y="168" fill="#ffd556" font-size="9.5" font-family="IBM Plex Mono">Fh=122.5N</text>
<!-- 45°角 -->
<path d="M80,150 A20,20 0 0,0 74.14,135.86" fill="none" stroke="#00ccff" stroke-width="1"/>
<text x="82" y="140" fill="#00ccff" font-size="9" font-family="IBM Plex Mono">45°</text>
<!-- 标注 -->
<text x="48" y="25" fill="#ff2e50" font-size="8" font-family="IBM Plex Mono" text-anchor="middle">↓重力分量</text>
<text x="180" y="165" fill="#ffd556" font-size="8" font-family="IBM Plex Mono" text-anchor="middle">→压缩分量</text>
</svg>
</div>
<!-- 俯视布局 -->
<div class="card">
<h3>俯视布局</h3>
<svg class="top-view-svg" viewBox="0 0 260 200" xmlns="http://www.w3.org/2000/svg">
<rect x="30" y="20" width="200" height="160" rx="6" fill="none" stroke="#3d5a80" stroke-width="2"/>
<text x="130" y="12" text-anchor="middle" fill="#3d5a80" font-size="8" font-family="IBM Plex Mono">浅盆体</text>
<!-- 浮动压盘 -->
<rect id="topPlate" x="85" y="65" width="90" height="70" rx="4" fill="#1a2a3a" stroke="#4a7a9a" stroke-width="1.5"/>
<text x="130" y="105" text-anchor="middle" fill="#4a7a9a" font-size="8" font-family="IBM Plex Mono">压盘</text>
<!-- 四根张紧带 -->
<line x1="85" y1="65" x2="30" y2="20" stroke="#00ff88" stroke-width="1.5" opacity=".7"/>
<line x1="175" y1="65" x2="230" y2="20" stroke="#00ff88" stroke-width="1.5" opacity=".7"/>
<line x1="85" y1="135" x2="30" y2="180" stroke="#00ff88" stroke-width="1.5" opacity=".7"/>
<line x1="175" y1="135" x2="230" y2="180" stroke="#00ff88" stroke-width="1.5" opacity=".7"/>
<!-- 卷簧标记 -->
<circle cx="30" cy="20" r="6" fill="none" stroke="#ffd556" stroke-width="1.5"/>
<circle cx="230" cy="20" r="6" fill="none" stroke="#ffd556" stroke-width="1.5"/>
<circle cx="30" cy="180" r="6" fill="none" stroke="#ffd556" stroke-width="1.5"/>
<circle cx="230" cy="180" r="6" fill="none" stroke="#ffd556" stroke-width="1.5"/>
<text x="130" y="195" text-anchor="middle" fill="#5a6a3a" font-size="7" font-family="IBM Plex Mono">● 卷簧收紧器 ○ 张紧带</text>
</svg>
</div>
<!-- 关键参数 -->
<div class="card">
<h3>关键参数</h3>
<div class="param-row"><span class="param-k">盆体厚度</span><span class="param-v">2mm</span></div>
<div class="param-row"><span class="param-k">卷簧预紧扭矩</span><span class="param-v hl">5 N·m</span></div>
<div class="param-row"><span class="param-k">安装夹角</span><span class="param-v hl">45°</span></div>
<div class="param-row"><span class="param-k">额定载荷</span><span class="param-v">50 kg</span></div>
<div class="param-row"><span class="param-k">张紧带材料</span><span class="param-v">高强涤纶</span></div>
<div class="param-row"><span class="param-k">当前载荷</span><span class="param-v hl" id="paramLoad">0 kg</span></div>
</div>
</aside>
</div>
<!-- 控制区 -->
<div class="ctrls">
<div class="slider-row">
<span class="slider-lbl">载荷控制</span>
<input type="range" id="loadSlider" min="0" max="50" value="0" step="1"/>
<span class="slider-val" id="loadDisplay">0 kg</span>
</div>
<div class="btn-row">
<button class="btn" id="btnAuto">▶ 自动演示</button>
<button class="btn" id="btnReset">↺ 重置</button>
<button class="btn" id="btnFull">满载 50kg</button>
</div>
<div class="ifr-box">
<strong>IFR 核心思想:</strong>框架自身没有重量,却完美承载50kg载荷并维持所需刚度——通过<em>载荷自身重力</em>拉动张紧带,将向下破坏力转化为对盆体四周的<em>斜向绷紧力</em>,利用货物自身重量给底盘"打了一把伞骨",以极少复杂度消除了"薄壁屈曲"与"承载刚度"之间的物理矛盾。
</div>
</div>
</div>
<script>
/* ====== 状态管理 ====== */
let load = 0; // 当前载荷 0-50 kg
let targetLoad = 0; // 目标载荷(滑块值)
let autoPlay = false;
let autoPhase = 0; // 0:空载停留 1:加载 2:满载停留 3:卸载
let autoTimer = 0;
let flowOffset = 0; // 张紧带流动偏移
/* ====== 几何常量 ====== */
const B = { lx:250, rx:750, ty:210, by:435 }; // 盆体边界
const PL = { lx:385, rx:615, yEmpty:290, yFull:375, h:14 }; // 压盘
const SP = { lx:260, rx:740, y:235 }; // 弹簧位置
const LOAD_H = 68;
/* ====== 工具函数 ====== */
function lerp(a,b,t){ return a+(b-a)*Math.max(0,Math.min(1,t)); }
function lerpColor(c1,c2,t){
const p = (c)=>{const n=parseInt(c.slice(1),16);return[(n>>16)&255,(n>>8)&255,n&255];};
const a=p(c1),b=p(c2);
const r=Math.round(lerp(a[0],b[0],t)),g=Math.round(lerp(a[1],b[1],t)),bl=Math.round(lerp(a[2],b[2],t));
return `rgb(${r},${g},${bl})`;
}
/* ====== 生成卷簧路径 ====== */
function springPath(cx, cy, h, coils, w){
let pts = [];
const step = h / (coils * 2);
for(let i = 0; i <= coils * 2; i++){
const y = cy - h + i * step;
const x = (i % 2 === 0) ? cx - w/2 : cx + w/2;
pts.push(`${x},${y}`);
}
return 'M' + pts.join(' L');
}
/* ====== 更新可视化 ====== */
function update(){
const t = load / 50; // 归一化 0-1
const plateY = lerp(PL.yEmpty, PL.yFull, t);
// --- 盆体变形参数 ---
const sag = lerp(18, 0, t); // 底部下垂量
const wallLean = lerp(0, 7, t); // 壁面内倾
// --- 盆体路径 ---
const basinD = `M${B.lx-20},${B.ty} L${B.lx},${B.ty+wallLean} L${B.lx},${B.by} Q${(B.lx+B.rx)/2},${B.by+sag} ${B.rx},${B.by} L${B.rx},${B.ty+wallLean} L${B.rx+20},${B.ty}`;
document.getElementById('basinOutline').setAttribute('d', basinD);
document.getElementById('basinGlow').setAttribute('d', basinD);
// 盆体填充路径(闭合)
const fillD = `M${B.lx-20},${B.ty} L${B.lx},${B.ty+wallLean} L${B.lx},${B.by} Q${(B.lx+B.rx)/2},${B.by+sag} ${B.rx},${B.by} L${B.rx},${B.ty+wallLean} L${B.rx+20},${B.ty} L${B.rx+20},${B.ty-8} L${B.lx-20},${B.ty-8} Z`;
document.getElementById('basinFillPath').setAttribute('d', fillD);
// --- 盆体辉光 ---
document.getElementById('basinGlow').setAttribute('opacity', t * 0.5);
// --- 盆体颜色随张力变化 ---
const structColor = lerpColor('#3d5a80', '#00e5a0', t * 0.6);
document.getElementById('basinOutline').setAttribute('stroke', structColor);
document.getElementById('basinOutline').setAttribute('stroke-width', lerp(5, 6.5, t));
// --- 浮动压盘 ---
const plate = document.getElementById('plate');
plate.setAttribute('y', plateY);
plate.setAttribute('fill', lerpColor('#2a4a6a', '#1a6a5a', t));
plate.setAttribute('stroke', lerpColor('#4a7a9a', '#00e5a0', t));
// 导向块
const guideY = plateY + 2;
document.getElementById('guideL').setAttribute('y', guideY);
document.getElementById('guideR').setAttribute('y', guideY);
// --- 载荷 ---
const loadG = document.getElementById('loadGroup');
loadG.setAttribute('opacity', Math.min(1, t * 4));
document.getElementById('loadBlock').setAttribute('y', plateY - LOAD_H - 2);
document.getElementById('loadLabel').setAttribute('y', plateY - LOAD_H/2 + 2);
const loadKg = Math.round(load);
document.getElementById('loadLabel').textContent = loadKg + 'kg';
// --- 张紧带 ---
const beltSag = lerp(20, 0, t);
// 左带: 压盘左端 → 弹簧左
const lbx1=PL.lx, lby1=plateY, lbx2=SP.lx, lby2=SP.y;
const lmx=(lbx1+lbx2)/2, lmy=(lby1+lby2)/2;
const lbD = `M${lbx1},${lby1} Q${lmx},${lmy+beltSag} ${lbx2},${lby2}`;
document.getElementById('beltLeft').setAttribute('d', lbD);
document.getElementById('beltFlowL').setAttribute('d', lbD);
// 右带: 压盘右端 → 弹簧右
const rbx1=PL.rx, rby1=plateY, rbx2=SP.rx, rby2=SP.y;
const rmx=(rbx1+rbx2)/2, rmy=(rby1+rby2)/2;
const rbD = `M${rbx1},${rby1} Q${rmx},${rmy+beltSag} ${rbx2},${rby2}`;
document.getElementById('beltRight').setAttribute('d', rbD);
document.getElementById('beltFlowR').setAttribute('d', rbD);
// 张紧带颜色和粗细
const beltColor = lerpColor('#2e4050', '#00ff88', t);
const beltW = lerp(2.5, 4.5, t);
['beltLeft','beltRight'].forEach(id => {
document.getElementById(id).setAttribute('stroke', beltColor);
document.getElementById(id).setAttribute('stroke-width', beltW);
});
// 流动效果
const flowOp = lerp(0, 0.7, t);
['beltFlowL','beltFlowR'].forEach(id => {
document.getElementById(id).setAttribute('opacity', flowOp);
document.getElementById(id).setAttribute('stroke-dashoffset', -flowOffset);
});
// 45°角标注
const angleG = document.getElementById('angleLabel');
angleG.setAttribute('opacity', t > 0.3 ? Math.min(1, (t-0.3)*3) : 0);
if(t > 0.3){
// 在左带中点处画角度弧
const arcCx = lbx2, arcCy = lby2;
const arcR = 35;
// 当前带的角度
const angle = Math.atan2(lby1-lby2, lbx1-lbx2);
const angleDeg = angle * 180 / Math.PI;
// 水平线到带的弧
const hx = arcCx + arcR, hy = arcCy;
const bx = arcCx + arcR * Math.cos(angle), by = arcCy + arcR * Math.sin(angle);
const largeArc = Math.abs(angleDeg) > 180 ? 1 : 0;
const sweep = angleDeg > 0 ? 1 : 0;
document.getElementById('angleArc').setAttribute('d',
`M${hx},${hy} A${arcR},${arcR} 0 ${largeArc},${sweep} ${bx},${by}`);
// 角度文字
const textAngle = angle / 2;
const tx = arcCx + (arcR+14) * Math.cos(textAngle);
const ty = arcCy + (arcR+14) * Math.sin(textAngle);
document.getElementById('angleText').setAttribute('x', tx);
document.getElementById('angleText').setAttribute('y', ty);
}
// --- 卷簧 ---
const springH = lerp(32, 16, t);
const springW = 14;
const springCoils = 5;
document.getElementById('springLeft').innerHTML =
`<path d="${springPath(SP.lx, SP.y, springH, springCoils, springW)}" fill="none" stroke="#ffd556" stroke-width="2" stroke-linecap="round" opacity="${lerp(0.5,1,t)}"/>` +
`<circle cx="${SP.lx}" cy="${SP.y}" r="4" fill="#ffd556" opacity="0.6"/>`;
document.getElementById('springRight').innerHTML =
`<path d="${springPath(SP.rx, SP.y, springH, springCoils, springW)}" fill="none" stroke="#ffd556" stroke-width="2" stroke-linecap="round" opacity="${lerp(0.5,1,t)}"/>` +
`<circle cx="${SP.rx}" cy="${SP.y}" r="4" fill="#ffd556" opacity="0.6"/>`;
// --- 力箭头 ---
const fG = document.getElementById('forceArrows');
fG.setAttribute('opacity', t > 0.1 ? Math.min(1, (t-0.1)*2.5) : 0);
if(t > 0.1){
const fScale = t;
// 重力箭头
const gLen = lerp(0, 80, t);
const gStartY = plateY - LOAD_H/2;
document.getElementById('fGravity').setAttribute('x1', 500);
document.getElementById('fGravity').setAttribute('y1', gStartY);
document.getElementById('fGravity').setAttribute('x2', 500);
document.getElementById('fGravity').setAttribute('y2', gStartY + gLen);
document.getElementById('fGravLabel').setAttribute('y', gStartY + gLen/2);
const forceN = Math.round(load * 9.8);
document.getElementById('fGravLabel').textContent = `F=${forceN}N`;
// 张力箭头 (沿带方向,从压盘指向弹簧)
const tLen = lerp(0, 60, t);
// 左带方向
const lAngle = Math.atan2(SP.y - plateY, SP.lx - PL.lx);
const lt1x = PL.lx + 20 * Math.cos(lAngle), lt1y = plateY + 20 * Math.sin(lAngle);
const lt2x = lt1x + tLen * Math.cos(lAngle), lt2y = lt1y + tLen * Math.sin(lAngle);
document.getElementById('fTensionL').setAttribute('x1', lt1x);
document.getElementById('fTensionL').setAttribute('y1', lt1y);
document.getElementById('fTensionL').setAttribute('x2', lt2x);
document.getElementById('fTensionL').setAttribute('y2', lt2y);
// 右带方向
const rAngle = Math.atan2(SP.y - plateY, SP.rx - PL.rx);
const rt1x = PL.rx + 20 * Math.cos(rAngle), rt1y = plateY + 20 * Math.sin(rAngle);
const rt2x = rt1x + tLen * Math.cos(rAngle), rt2y = rt1y + tLen * Math.sin(rAngle);
document.getElementById('fTensionR').setAttribute('x1', rt1x);
document.getElementById('fTensionR').setAttribute('y1', rt1y);
document.getElementById('fTensionR').setAttribute('x2', rt2x);
document.getElementById('fTensionR').setAttribute('y2', rt2y);
// 水平压缩分力 (在弹簧处向内)
const hLen = lerp(0, 50, t);
document.getElementById('fCompL').setAttribute('x1', SP.lx);
document.getElementById('fCompL').setAttribute('y1', SP.y);
document.getElementById('fCompL').setAttribute('x2', SP.lx + hLen);
document.getElementById('fCompL').setAttribute('y2', SP.y);
document.getElementById('fCompR').setAttribute('x1', SP.rx);
document.getElementById('fCompR').setAttribute('y1', SP.y);
document.getElementById('fCompR').setAttribute('x2', SP.rx - hLen);
document.getElementById('fCompR').setAttribute('y2', SP.y);
// 垂直支撑分力 (在弹簧处向上)
const vLen = lerp(0, 45, t);
document.getElementById('fSuppL').setAttribute('x1', SP.lx);
document.getElementById('fSuppL').setAttribute('y1', SP.y);
document.getElementById('fSuppL').setAttribute('x2', SP.lx);
document.getElementById('fSuppL').setAttribute('y2', SP.y - vLen);
document.getElementById('fSuppR').setAttribute('x1', SP.rx);
document.getElementById('fSuppR').setAttribute('y1', SP.y);
document.getElementById('fSuppR').setAttribute('x2', SP.rx);
document.getElementById('fSuppR').setAttribute('y2', SP.y - vLen);
// 力分解虚线
document.getElementById('fDecompL1').setAttribute('x1', SP.lx+hLen);
document.getElementById('fDecompL1').setAttribute('y1', SP.y);
document.getElementById('fDecompL1').setAttribute('x2', SP.lx);
document.getElementById('fDecompL1').setAttribute('y2', SP.y);
document.getElementById('fDecompL2').setAttribute('x1', SP.lx);
document.getElementById('fDecompL2').setAttribute('y1', SP.y-vLen);
document.getElementById('fDecompL2').setAttribute('x2', SP.lx);
document.getElementById('fDecompL2').setAttribute('y2', SP.y);
document.getElementById('fDecompR1').setAttribute('x1', SP.rx-hLen);
document.getElementById('fDecompR1').setAttribute('y1', SP.y);
document.getElementById('fDecompR1').setAttribute('x2', SP.rx);
document.getElementById('fDecompR1').setAttribute('y2', SP.y);
document.getElementById('fDecompR2').setAttribute('x1', SP.rx);
document.getElementById('fDecompR2').setAttribute('y1', SP.y-vLen);
document.getElementById('fDecompR2').setAttribute('x2', SP.rx);
document.getElementById('fDecompR2').setAttribute('y2', SP.y);
}
// --- 变形指示 ---
const defG = document.getElementById('deformIndicator');
defG.setAttribute('opacity', t < 0.3 ? lerp(0.6, 0, t/0.3) : 0);
// --- 标注颜色更新 ---
document.getElementById('annBelt').setAttribute('fill', lerpColor('#2e4050','#00ff88',t));
document.getElementById('annSpring').setAttribute('fill', lerpColor('#8a7a30','#ffd556',t));
// --- IFR 注解 ---
document.getElementById('ifrCallout').setAttribute('opacity', t > 0.4 ? Math.min(1,(t-0.4)*2.5) : 0);
// --- 状态标签 ---
const stText = document.getElementById('stateText');
const stBg = document.getElementById('stateBg');
if(t < 0.05){
stText.textContent = '空载柔性状态';
stBg.setAttribute('fill','rgba(0,229,160,.08)');
stBg.setAttribute('stroke','rgba(0,229,160,.2)');
} else if(t < 0.85){
stText.textContent = '加载张紧中...';
stBg.setAttribute('fill','rgba(0,204,255,.08)');
stBg.setAttribute('stroke','rgba(0,204,255,.2)');
stText.setAttribute('fill','#00ccff');
} else {
stText.textContent = '满载高刚度状态';
stBg.setAttribute('fill','rgba(0,229,160,.12)');
stBg.setAttribute('stroke','rgba(0,229,160,.3)');
stText.setAttribute('fill','#00e5a0');
}
// --- 侧面板更新 ---
const stiffness = lerp(5, 98, t);
document.getElementById('gaugeFill').style.height = stiffness + '%';
document.getElementById('gaugeVal').innerHTML = Math.round(stiffness) + '<small>%</small>';
document.getElementById('loadDisplay').textContent = loadKg + ' kg';
document.getElementById('paramLoad').textContent = loadKg + ' kg';
// 阶段指示灯
const phases = ['pd0','pd1','pd2','pd3'];
const phaseNames = ['空载柔性','加载张紧','满载稳定','卸载恢复'];
let currentPhase = 0;
if(t >= 0.05 && t < 0.85) currentPhase = 1;
else if(t >= 0.85) currentPhase = 2;
if(autoPlay && autoPhase === 3) currentPhase = 3;
phases.forEach((id,i) => {
document.getElementById(id).classList.toggle('on', i === currentPhase);
});
document.getElementById('phaseText').textContent = phaseNames[currentPhase];
}
/* ====== 动画循环 ====== */
let lastTime = 0;
function animate(time){
const dt = Math.min((time - lastTime) / 1000, 0.05);
lastTime = time;
// 流动偏移
if(load > 1){
flowOffset += dt * lerp(30, 120, load/50);
}
// 平滑跟随目标值
const speed = 18; // kg/s
if(Math.abs(load - targetLoad) > 0.2){
load += Math.sign(targetLoad - load) * speed * dt;
load = Math.max(0, Math.min(50, load));
} else {
load = targetLoad;
}
// 自动播放
if(autoPlay){
autoTimer += dt;
switch(autoPhase){
case 0: // 空载停留
targetLoad = 0;
if(autoTimer > 1.5){ autoPhase = 1; autoTimer = 0; }
break;
case 1: // 加载
targetLoad = 50;
if(Math.abs(load - 50) < 0.5){ autoPhase = 2; autoTimer = 0; }
break;
case 2: // 满载停留
targetLoad = 50;
if(autoTimer > 3){ autoPhase = 3; autoTimer = 0; }
break;
case 3: // 卸载
targetLoad = 0;
if(Math.abs(load) < 0.5){ autoPhase = 0; autoTimer = 0; }
break;
}
document.getElementById('loadSlider').value = Math.round(load);
}
update();
requestAnimationFrame(animate);
}
/* ====== 事件绑定 ====== */
document.getElementById('loadSlider').addEventListener('input', function(){
targetLoad = parseFloat(this.value);
if(autoPlay) stopAuto();
});
document.getElementById('btnAuto').addEventListener('click', function(){
if(autoPlay){
stopAuto();
} else {
autoPlay = true;
autoPhase = 0;
autoTimer = 0;
this.classList.add('on');
this.textContent = '⏸ 暂停';
}
});
document.getElementById('btnReset').addEventListener('click', function(){
stopAuto();
targetLoad = 0;
document.getElementById('loadSlider').value = 0;
});
document.getElementById('btnFull').addEventListener('click', function(){
stopAuto();
targetLoad = 50;
document.getElementById('loadSlider').value = 50;
});
function stopAuto(){
autoPlay = false;
const btn = document.getElementById('btnAuto');
btn.classList.remove('on');
btn.textContent = '▶ 自动演示';
}
/* ====== 启动 ====== */
update();
requestAnimationFrame(animate);
</script>
</body>
</html>
实现说明
这是一个完整的 应力自适应张紧底盘 IFR 原理动画,核心设计理念围绕 TRIZ 的最终理想解思想展开:
视觉引导策略
- 色彩渐变:盆体从暗蓝(松弛)→ 亮绿(张紧),直观传达刚度变化
- 张紧带流动效果:加载后带面上出现绿色流动虚线,可视化力传导方向
- 力箭头系统:红色重力 → 青色张力 → 黄色压缩分力 → 绿色支撑分力,完整呈现力的转化链
- 45° 角标注:加载过程中自动显示角度弧线,强调关键安装参数
- 盆底变形线:空载时底部明显下凹(屈曲暗示),加载后逐步变平
交互设计
- 载荷滑块:0-50kg 连续控制,所有视觉元素实时响应
- 自动演示:循环播放"空载→加载→满载→卸载"完整时序
- 快捷按钮:一键满载 / 一键重置
- 力分解图(侧面板):静态展示 45° 分解的数学关系
- 俯视布局图:展示 4 根张紧带的空间分布
IFR 核心表达
右上角的 IFR 注解在载荷超过 40% 时浮现,直接点明"重力→张力→结构绷紧→刚度提升"的转化逻辑——载荷自身就是解决方案的资源,无需额外配重或加强筋。
积分规则:第一轮对话扣减6分,后续每轮扣4分
等待动画代码生成...
