<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>磁力锁定折叠伞骨 · IFR原理动画</title>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=IBM+Plex+Mono:wght@300;400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<style>
:root {
--bg-deep:#060a14;--bg-surface:#0c1222;--grid:rgba(0,180,255,0.04);
--steel:#7b9cc0;--steel-hi:#a8c8e8;--cyan:#00e5ff;--cyan-g:rgba(0,229,255,0.25);
--amber:#ff9100;--amber-g:rgba(255,145,0,0.25);--red:#ff3d00;--label:#7a9abb;
--canopy:rgba(60,130,210,0.13);--canopy-s:rgba(90,160,240,0.28);
}
*{margin:0;padding:0;box-sizing:border-box}
body{background:var(--bg-deep);color:#b8cce0;font-family:'IBM Plex Mono',monospace;
min-height:100vh;display:flex;flex-direction:column;align-items:center;overflow-x:hidden}
header{text-align:center;padding:28px 20px 10px;width:100%}
header h1{font-family:'Orbitron',sans-serif;font-weight:900;font-size:clamp(18px,3vw,28px);
letter-spacing:3px;color:#d0e4f8;text-transform:uppercase}
header p{font-size:12px;color:var(--label);margin-top:6px;letter-spacing:1px;max-width:700px;margin-inline:auto}
.svg-wrap{flex:1;display:flex;justify-content:center;align-items:center;width:100%;
padding:8px 12px;min-height:420px}
svg#main{width:100%;max-width:1100px;height:auto;max-height:68vh;border-radius:12px;
background:linear-gradient(170deg,#080e1e 0%,#0a1020 50%,#060c18 100%);
box-shadow:0 0 60px rgba(0,229,255,0.04),inset 0 0 80px rgba(0,0,0,0.5)}
.controls{width:100%;max-width:1100px;padding:12px 16px 24px;display:flex;flex-wrap:wrap;
gap:10px;align-items:center;justify-content:center}
.btn{font-family:'Orbitron',sans-serif;font-size:12px;font-weight:700;letter-spacing:1px;
border:1.5px solid;border-radius:8px;padding:10px 22px;cursor:pointer;
transition:all .25s;position:relative;overflow:hidden;outline:none}
.btn::after{content:'';position:absolute;inset:0;background:currentColor;opacity:0;transition:opacity .2s}
.btn:hover::after{opacity:0.08}
.btn:active{transform:scale(0.96)}
.btn-open{color:#00e5ff;border-color:#00e5ff;background:rgba(0,229,255,0.06)}
.btn-wind{color:#ff9100;border-color:#ff9100;background:rgba(255,145,0,0.06)}
.btn-pulse{color:#e0e8f0;border-color:#5a7a9a;background:rgba(90,122,154,0.08)}
.btn-emergency{color:#ff3d00;border-color:#ff3d00;background:rgba(255,61,0,0.06);
font-size:10px;padding:8px 16px}
.btn:disabled{opacity:0.3;cursor:not-allowed;transform:none}
.slider-group{display:flex;align-items:center;gap:8px;font-size:11px;color:var(--label)}
.slider-group input[type=range]{width:120px;accent-color:var(--amber)}
.status-bar{width:100%;max-width:1100px;display:flex;flex-wrap:wrap;gap:16px;
justify-content:center;padding:0 16px 16px;font-size:11px;color:var(--label)}
.status-item{display:flex;align-items:center;gap:6px}
.dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.dot-cyan{background:var(--cyan);box-shadow:0 0 6px var(--cyan)}
.dot-amber{background:var(--amber);box-shadow:0 0 6px var(--amber)}
.dot-red{background:var(--red);box-shadow:0 0 6px var(--red)}
.dot-off{background:#2a3a4a;box-shadow:none}
.val{color:#d0e4f8;font-weight:500}
@keyframes dashFlow{to{stroke-dashoffset:-20}}
@keyframes pulseGlow{0%,100%{opacity:.5}50%{opacity:1}}
@media(max-width:640px){
.controls{gap:6px}.btn{padding:8px 14px;font-size:10px}
.slider-group input[type=range]{width:80px}
}
</style>
</head>
<body>
<header>
<h1>Magnetic-Lock Folding Rib</h1>
<p>基于 TRIZ 最终理想解:利用伞面残余张力与扭簧势能实现瞬间解锁坍缩,磁力锁定抵抗风力,脉冲消磁即刻折叠</p>
</header>
<div class="svg-wrap">
<svg id="main" viewBox="0 0 1200 800" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- 网格图案 -->
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="rgba(0,160,255,0.05)" stroke-width="0.5"/>
</pattern>
<pattern id="gridFine" width="10" height="10" patternUnits="userSpaceOnUse">
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(0,160,255,0.02)" stroke-width="0.3"/>
</pattern>
<!-- 青色辉光 -->
<filter id="glowCyan" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur in="SourceGraphic" stdDeviation="8" result="b"/>
<feFlood flood-color="#00e5ff" flood-opacity="0.7" result="c"/>
<feComposite in="c" in2="b" operator="in" result="g"/>
<feMerge><feMergeNode in="g"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- 琥珀辉光 -->
<filter id="glowAmber" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur in="SourceGraphic" stdDeviation="6" result="b"/>
<feFlood flood-color="#ff9100" flood-opacity="0.7" result="c"/>
<feComposite in="c" in2="b" operator="in" result="g"/>
<feMerge><feMergeNode in="g"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- 脉冲辉光 -->
<filter id="glowPulse" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur in="SourceGraphic" stdDeviation="14" result="b"/>
<feFlood flood-color="#ffffff" flood-opacity="0.9" result="c"/>
<feComposite in="c" in2="b" operator="in" result="g"/>
<feMerge><feMergeNode in="g"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- 轻辉光 -->
<filter id="glowSoft" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="3" result="b"/>
<feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
<!-- 轴渐变 -->
<linearGradient id="shaftGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#5a7a9a"/><stop offset="100%" stop-color="#3a5a7a"/>
</linearGradient>
<!-- 磁铁渐变 -->
<linearGradient id="magGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#e03030"/><stop offset="50%" stop-color="#c02020"/>
<stop offset="50%" stop-color="#2060e0"/><stop offset="100%" stop-color="#3070f0"/>
</linearGradient>
</defs>
<!-- 背景网格 -->
<rect width="100%" height="100%" fill="url(#gridFine)"/>
<rect width="100%" height="100%" fill="url(#grid)"/>
<!-- 所有动态元素将由JS创建 -->
</svg>
</div>
<div class="controls">
<button class="btn btn-open" id="btnOpen"><i class="fa-solid fa-lock"></i> 展开锁定</button>
<button class="btn btn-wind" id="btnWind"><i class="fa-solid fa-wind"></i> 抗风测试</button>
<div class="slider-group" id="windSlider" style="opacity:0.3;pointer-events:none">
<span>风力</span>
<input type="range" id="windRange" min="0" max="100" value="50">
<span id="windVal">50%</span>
</div>
<button class="btn btn-pulse" id="btnPulse"><i class="fa-solid fa-bolt"></i> 脉冲收伞</button>
<button class="btn btn-emergency" id="btnEmergency"><i class="fa-solid fa-triangle-exclamation"></i> 应急解锁</button>
</div>
<div class="status-bar">
<div class="status-item"><div class="dot dot-cyan" id="dotMag"></div>磁力锁定 <span class="val" id="valMag">0 N</span></div>
<div class="status-item"><div class="dot dot-amber" id="dotSpring"></div>扭簧势能 <span class="val" id="valSpring">0%</span></div>
<div class="status-item"><div class="dot dot-off" id="dotPulse"></div>脉冲电流 <span class="val" id="valPulse">OFF</span></div>
<div class="status-item"><div class="dot dot-off" id="dotWind"></div>风力 <span class="val" id="valWind">0 N</span></div>
<div class="status-item">状态: <span class="val" id="valState">待机</span></div>
</div>
<script>
/* ===================== 常量与配置 ===================== */
const NS = 'http://www.w3.org/2000/svg';
const svg = document.getElementById('main');
/* 伞骨参数 */
const HUB = { x: 520, y: 320 };
const INNER_LEN = 155, OUTER_LEN = 135;
const RIB_ANGLES = [-58, -29, 0, 29, 58]; // 度,从竖直方向偏转
const FOLD_DIR = [1, 1, -1, -1, -1]; // 左侧顺时针折,右侧逆时针折
const MAX_FOLD = 155; // 最大折叠角度
/* 临界参数 */
const MAG_FORCE_MAX = 12; // N
const PULSE_CURRENT = 3; // A
const SPRING_TORQUE = 0.2; // N·m
/* ===================== 状态变量 ===================== */
let state = 'folded';
let foldAngle = MAX_FOLD; // 0=展开, MAX_FOLD=折叠
let magStrength = 0; // 0-1
let springEnergy = 0; // 0-1
let pulseProgress = -1; // -1=无, 0~1=脉冲行进
let windIntensity = 0; // 0-1
let windOsc = 0;
let t = 0;
let windParticles = [];
let lastTime = 0;
/* ===================== 工具函数 ===================== */
function el(tag, attrs, parent) {
const e = document.createElementNS(NS, tag);
for (const [k, v] of Object.entries(attrs || {})) e.setAttribute(k, v);
if (parent) parent.appendChild(e);
return e;
}
function damp(cur, tar, spd, dt) {
return cur + (tar - cur) * (1 - Math.exp(-spd * dt));
}
function ribHinge(i) {
const r = RIB_ANGLES[i] * Math.PI / 180;
return { x: HUB.x + INNER_LEN * Math.sin(r), y: HUB.y - INNER_LEN * Math.cos(r) };
}
function ribTip(i, fa) {
const h = ribHinge(i);
const eff = (RIB_ANGLES[i] + fa * FOLD_DIR[i]) * Math.PI / 180;
return { x: h.x + OUTER_LEN * Math.sin(eff), y: h.y - OUTER_LEN * Math.cos(eff) };
}
/* ===================== 构建 SVG 元素 ===================== */
const gRoot = el('g', { id: 'mechanism' }, svg);
// --- 轴 ---
const gShaft = el('g', {}, gRoot);
el('line', { x1: HUB.x, y1: HUB.y, x2: HUB.x, y2: 660, stroke: 'url(#shaftGrad)',
'stroke-width': 6, 'stroke-linecap': 'round' }, gShaft);
// 轴内导线
el('line', { x1: HUB.x, y1: HUB.y + 10, x2: HUB.x, y2: 650, stroke: '#1a3a5a',
'stroke-width': 2, 'stroke-dasharray': '6 4', opacity: 0.5 }, gShaft);
// --- 手柄 ---
const gHandle = el('g', {}, gRoot);
el('rect', { x: HUB.x - 18, y: 648, width: 36, height: 60, rx: 10, fill: '#1a2a3a',
stroke: '#3a5a7a', 'stroke-width': 1.5 }, gHandle);
// 电路板指示
const circuitLed = el('circle', { cx: HUB.x, cy: 670, r: 3, fill: '#1a3a2a', stroke: '#2a5a3a',
'stroke-width': 0.5 }, gHandle);
el('text', { x: HUB.x, y: 690, fill: '#3a6a5a', 'font-size': 6, 'text-anchor': 'middle',
'font-family': 'IBM Plex Mono' }, gHandle).textContent = 'CTRL';
// --- 伞毂 ---
el('circle', { cx: HUB.x, cy: HUB.y, r: 10, fill: '#1a2a3a', stroke: '#5a7a9a', 'stroke-width': 2 }, gRoot);
// --- 伞骨组 ---
const ribGroups = [];
const hingeGlows = [];
const magRings = [];
const outerGroups = [];
for (let i = 0; i < RIB_ANGLES.length; i++) {
const g = el('g', {}, gRoot);
const h = ribHinge(i);
const tip0 = ribTip(i, 0);
// 内段伞骨
el('line', { x1: HUB.x, y1: HUB.y, x2: h.x, y2: h.y, stroke: '#5a7a9a',
'stroke-width': 4, 'stroke-linecap': 'round' }, g);
// 磁场环(在铰链周围)
const ring = el('ellipse', { cx: h.x, cy: h.y, rx: 18, ry: 12, fill: 'none',
stroke: '#00e5ff', 'stroke-width': 1.2, 'stroke-dasharray': '4 4', opacity: 0 }, g);
magRings.push(ring);
// 铰链辉光
const glow = el('circle', { cx: h.x, cy: h.y, r: 7, fill: '#00e5ff', opacity: 0,
filter: 'url(#glowCyan)' }, g);
hingeGlows.push(glow);
// 铰链点
el('circle', { cx: h.x, cy: h.y, r: 5, fill: '#0c1a2a', stroke: '#00e5ff',
'stroke-width': 1.5, opacity: 0.8 }, g);
// 扭簧标记(小弧线)
const springArc = el('path', { d: springPath(h.x, h.y, RIB_ANGLES[i]),
fill: 'none', stroke: '#ff9100', 'stroke-width': 1.5, opacity: 0 }, g);
// 外段伞骨(可旋转组)
const og = el('g', {}, g);
el('line', { x1: h.x, y1: h.y, x2: tip0.x, y2: tip0.y, stroke: '#5a7a9a',
'stroke-width': 3.5, 'stroke-linecap': 'round' }, og);
// 骨尖端小圆
el('circle', { cx: tip0.x, cy: tip0.y, r: 2.5, fill: '#7a9aba' }, og);
outerGroups.push(og);
ribGroups.push({ g, springArc, hinge: h });
}
function springPath(cx, cy, angle) {
// 绘制小型扭簧螺旋
const pts = [];
for (let a = 0; a < 4 * Math.PI; a += 0.3) {
const r = 8 + a * 1.2;
const px = cx + r * Math.cos(a + angle * Math.PI / 180);
const py = cy - r * Math.sin(a + angle * Math.PI / 180);
pts.push(`${a === 0 ? 'M' : 'L'}${px.toFixed(1)},${py.toFixed(1)}`);
}
return pts.join(' ');
}
// --- 伞面 ---
const canopy = el('path', { fill: 'rgba(50,120,200,0.10)', stroke: 'rgba(80,150,240,0.22)',
'stroke-width': 1.5, d: '' }, gRoot);
// 伞面放在伞骨下面 - 调整顺序
gRoot.insertBefore(canopy, ribGroups[0]?.g);
// --- 脉冲效果 ---
const pulseDot = el('circle', { cx: HUB.x, cy: 660, r: 8, fill: '#ffffff',
filter: 'url(#glowPulse)', opacity: 0 }, gRoot);
const pulseLine = el('line', { x1: HUB.x, y1: 660, x2: HUB.x, y2: 660,
stroke: '#00e5ff', 'stroke-width': 3, opacity: 0, filter: 'url(#glowCyan)' }, gRoot);
// --- 风粒子容器 ---
const gWind = el('g', {}, gRoot);
for (let i = 0; i < 40; i++) {
const p = el('circle', { cx: 0, cy: 0, r: Math.random() * 2 + 0.5,
fill: 'rgba(120,180,240,0.3)', opacity: 0 }, gWind);
windParticles.push({ el: p, x: 0, y: 0, vx: 0, vy: 0, life: 0, active: false });
}
// --- 铰链细节放大图 ---
const insetX = 830, insetY = 440, insetW = 340, insetH = 280;
const gInset = el('g', {}, gRoot);
// 背景框
el('rect', { x: insetX, y: insetY, width: insetW, height: insetH, rx: 12,
fill: 'rgba(6,10,20,0.92)', stroke: 'rgba(0,229,255,0.2)', 'stroke-width': 1 }, gInset);
el('text', { x: insetX + 14, y: insetY + 20, fill: '#5a8aaa', 'font-size': 10,
'font-family': 'Orbitron', 'font-weight': 700, 'letter-spacing': 1.5 }, gInset).textContent = 'HINGE DETAIL';
// 细节内部元素
const detailCx = insetX + insetW / 2, detailCy = insetY + 140;
// 内段端
el('rect', { x: detailCx - 100, y: detailCy - 8, width: 70, height: 16, rx: 3,
fill: '#2a3a4a', stroke: '#5a7a9a', 'stroke-width': 1 }, gInset);
el('text', { x: detailCx - 65, y: detailCy + 24, fill: '#5a7a9a', 'font-size': 8,
'text-anchor': 'middle', 'font-family': 'IBM Plex Mono' }, gInset).textContent = '内段伞骨';
// 外段端
const outerDetailRect = el('rect', { x: detailCx + 30, y: detailCy - 8, width: 70, height: 16, rx: 3,
fill: '#2a3a4a', stroke: '#5a7a9a', 'stroke-width': 1 }, gInset);
el('text', { x: detailCx + 65, y: detailCy + 24, fill: '#5a7a9a', 'font-size': 8,
'text-anchor': 'middle', 'font-family': 'IBM Plex Mono' }, gInset).textContent = '外段伞骨';
// 永磁体(红蓝)
el('rect', { x: detailCx - 28, y: detailCy - 14, width: 12, height: 28, rx: 2,
fill: 'url(#magGrad)' }, gInset);
el('text', { x: detailCx - 22, y: detailCy + 42, fill: '#8a6a6a', 'font-size': 7,
'text-anchor': 'middle', 'font-family': 'IBM Plex Mono' }, gInset).textContent = '永磁体';
// 软磁铁芯
el('rect', { x: detailCx + 16, y: detailCy - 12, width: 10, height: 24, rx: 2,
fill: '#4a5a6a', stroke: '#6a7a8a', 'stroke-width': 0.5 }, gInset);
el('text', { x: detailCx + 21, y: detailCy + 42, fill: '#6a7a8a', 'font-size': 7,
'text-anchor': 'middle', 'font-family': 'IBM Plex Mono' }, gInset).textContent = '软磁芯';
// 线圈
for (let j = 0; j < 5; j++) {
el('ellipse', { cx: detailCx + 21, cy: detailCy - 8 + j * 4, rx: 10, ry: 2.5,
fill: 'none', stroke: '#aa8844', 'stroke-width': 0.8, opacity: 0.6 }, gInset);
}
el('text', { x: detailCx + 45, y: detailCy - 6, fill: '#8a7a5a', 'font-size': 7,
'font-family': 'IBM Plex Mono' }, gInset).textContent = '线圈';
// 扭簧示意
const detailSpring = el('path', {
d: `M ${detailCx - 5} ${detailCy - 20} Q ${detailCx} ${detailCy - 35} ${detailCx + 10} ${detailCy - 25}
Q ${detailCx + 20} ${detailCy - 15} ${detailCx + 10} ${detailCy - 5}
Q ${detailCx} ${detailCy + 5} ${detailCx + 5} ${detailCy + 15}`,
fill: 'none', stroke: '#ff9100', 'stroke-width': 1.8, opacity: 0.7 }, gInset);
el('text', { x: detailCx + 35, y: detailCy - 22, fill: '#aa7a3a', 'font-size': 7,
'font-family': 'IBM Plex Mono' }, gInset).textContent = '扭簧 0.2N·m';
// 铰链轴心
el('circle', { cx: detailCx, cy: detailCy, r: 4, fill: '#0a1a2a', stroke: '#4a6a8a',
'stroke-width': 1.5 }, gInset);
// 磁力线(细节图中的动画线)
const detailMagLines = [];
for (let j = 0; j < 3; j++) {
const ml = el('path', {
d: `M ${detailCx - 26} ${detailCy - 8 + j * 8} Q ${detailCx} ${detailCy - 18 + j * 4} ${detailCx + 16} ${detailCy - 6 + j * 6}`,
fill: 'none', stroke: '#00e5ff', 'stroke-width': 1, 'stroke-dasharray': '3 3',
opacity: 0 }, gInset);
detailMagLines.push(ml);
}
// 脉冲方向箭头
const detailPulseArrow = el('path', {
d: `M ${detailCx + 21} ${detailCy + 30} L ${detailCx + 21} ${detailCy + 18}`,
fill: 'none', stroke: '#ffffff', 'stroke-width': 2, opacity: 0,
'marker-end': '' }, gInset);
const arrowHead = el('polygon', { points: `${detailCx+17},${detailCy+20} ${detailCx+21},${detailCy+12} ${detailCx+25},${detailCy+20}`,
fill: '#ffffff', opacity: 0 }, gInset);
// 细节图状态标签
const detailStateLabel = el('text', { x: detailCx, y: insetY + insetH - 18, fill: '#00e5ff',
'font-size': 10, 'text-anchor': 'middle', 'font-family': 'Orbitron', 'font-weight': 700,
opacity: 0.8 }, gInset);
detailStateLabel.textContent = 'MAGNETIC LOCK ENGAGED';
// 连接线(从主图铰链到细节图)
const connectorLine = el('line', { x1: 0, y1: 0, x2: insetX, y2: insetY + 20,
stroke: 'rgba(0,229,255,0.15)', 'stroke-width': 1, 'stroke-dasharray': '4 4' }, gRoot);
// --- 示波器面板 ---
const oscX = 30, oscY = 500, oscW = 220, oscH = 120;
const gOsc = el('g', {}, gRoot);
el('rect', { x: oscX, y: oscY, width: oscW, height: oscH, rx: 8,
fill: 'rgba(6,10,20,0.9)', stroke: 'rgba(0,229,255,0.15)', 'stroke-width': 1 }, gOsc);
el('text', { x: oscX + 10, y: oscY + 16, fill: '#4a7a9a', 'font-size': 9,
'font-family': 'Orbitron', 'font-weight': 700 }, gOsc).textContent = 'PULSE WAVEFORM';
// 中线
el('line', { x1: oscX + 10, y1: oscY + oscH / 2, x2: oscX + oscW - 10, y2: oscY + oscH / 2,
stroke: 'rgba(0,229,255,0.1)', 'stroke-width': 0.5 }, gOsc);
// 刻度
for (let j = 0; j < 5; j++) {
el('line', { x1: oscX + 10 + j * (oscW - 20) / 4, y1: oscY + oscH / 2 - 3,
x2: oscX + 10 + j * (oscW - 20) / 4, y2: oscY + oscH / 2 + 3,
stroke: 'rgba(0,229,255,0.15)', 'stroke-width': 0.5 }, gOsc);
}
const oscPath = el('polyline', { fill: 'none', stroke: '#00e5ff', 'stroke-width': 1.5,
points: '', filter: 'url(#glowSoft)' }, gOsc);
const oscLabelA = el('text', { x: oscX + oscW - 10, y: oscY + oscH - 10, fill: '#3a6a8a',
'font-size': 8, 'text-anchor': 'end', 'font-family': 'IBM Plex Mono' }, gOsc);
oscLabelA.textContent = '3A / 0.1s';
// --- 参数标签 ---
const labels = [
{ x: HUB.x - 80, y: HUB.y + 60, text: '永磁吸合力 12N', color: '#00bbcc' },
{ x: HUB.x + 20, y: HUB.y + 60, text: '扭簧扭矩 0.2N·m', color: '#cc7700' },
{ x: HUB.x - 40, y: 640, text: '瞬态脉冲 3A / 0.1s', color: '#8899aa' },
];
labels.forEach(l => {
el('text', { x: l.x, y: l.y, fill: l.color, 'font-size': 9,
'font-family': 'IBM Plex Mono', opacity: 0.6 }, gRoot).textContent = l.text;
});
/* ===================== 动画逻辑 ===================== */
function setState(s) {
state = s;
if (s === 'opening') {
springEnergy = 0.1;
} else if (s === 'wind') {
windIntensity = parseInt(document.getElementById('windRange').value) / 100;
document.getElementById('windSlider').style.opacity = '1';
document.getElementById('windSlider').style.pointerEvents = 'auto';
}
if (s !== 'wind') {
windIntensity = 0;
document.getElementById('windSlider').style.opacity = '0.3';
document.getElementById('windSlider').style.pointerEvents = 'none';
}
updateButtons();
}
function updateButtons() {
const bo = document.getElementById('btnOpen');
const bw = document.getElementById('btnWind');
const bp = document.getElementById('btnPulse');
const be = document.getElementById('btnEmergency');
bo.disabled = !['folded','open'].includes(state);
bw.disabled = !['open','wind'].includes(state);
bp.disabled = !['open','wind'].includes(state);
be.disabled = !['open','wind'].includes(state);
}
function update(dt) {
t += dt;
switch (state) {
case 'opening':
foldAngle = damp(foldAngle, 0, 5, dt);
if (foldAngle < 0.5) {
foldAngle = 0;
state = 'locking';
}
break;
case 'locking':
magStrength = damp(magStrength, 1, 6, dt);
springEnergy = damp(springEnergy, 0.85, 4, dt);
if (magStrength > 0.97) { magStrength = 1; state = 'open'; }
break;
case 'open':
magStrength = damp(magStrength, 1, 8, dt);
springEnergy = damp(springEnergy, 0.85, 6, dt);
windOsc *= 0.95;
break;
case 'wind':
windOsc = Math.sin(t * 4) * windIntensity * 12 + Math.sin(t * 7.3) * windIntensity * 5;
magStrength = damp(magStrength, 1, 8, dt);
springEnergy = damp(springEnergy, 0.85, 6, dt);
break;
case 'pulse_firing':
pulseProgress += dt * 2.8;
if (pulseProgress >= 1) { pulseProgress = -1; state = 'demagnetizing'; }
break;
case 'demagnetizing':
magStrength = damp(magStrength, 0, 12, dt);
if (magStrength < 0.03) {
magStrength = 0;
state = 'spring_release';
}
break;
case 'spring_release':
springEnergy = damp(springEnergy, 0, 8, dt);
foldAngle = damp(foldAngle, MAX_FOLD, 3, dt);
if (foldAngle > MAX_FOLD - 3) { state = 'folding'; }
break;
case 'folding':
foldAngle = damp(foldAngle, MAX_FOLD, 3, dt);
springEnergy = damp(springEnergy, 0, 6, dt);
if (foldAngle > MAX_FOLD - 0.5) {
foldAngle = MAX_FOLD;
springEnergy = 0;
state = 'folded';
}
break;
case 'emergency':
magStrength = damp(magStrength, 0, 6, dt);
if (magStrength < 0.05) {
magStrength = 0;
state = 'spring_release';
}
break;
}
}
function render() {
const fa = foldAngle + windOsc;
// 更新外段伞骨旋转
for (let i = 0; i < RIB_ANGLES.length; i++) {
const h = ribHinge(i);
const rot = fa * FOLD_DIR[i];
outerGroups[i].setAttribute('transform', `rotate(${rot.toFixed(2)}, ${h.x.toFixed(1)}, ${h.y.toFixed(1)})`);
// 更新铰链辉光
hingeGlows[i].setAttribute('opacity', (magStrength * 0.7).toFixed(3));
// 更新磁场环
const ringScale = 1 + magStrength * 0.3 * Math.sin(t * 3 + i);
magRings[i].setAttribute('opacity', (magStrength * 0.5).toFixed(3));
magRings[i].setAttribute('rx', (18 * ringScale).toFixed(1));
magRings[i].setAttribute('ry', (12 * ringScale).toFixed(1));
magRings[i].setAttribute('stroke-dashoffset', ((t * 20 + i * 10) % 20).toFixed(1));
// 扭簧可见性
ribGroups[i].springArc.setAttribute('opacity', (springEnergy * 0.8).toFixed(3));
}
// 更新伞面
const tips = RIB_ANGLES.map((_, i) => ribTip(i, fa));
let canopyD = `M ${tips[0].x.toFixed(1)} ${tips[0].y.toFixed(1)}`;
for (let i = 1; i < tips.length; i++) {
const prev = tips[i - 1], cur = tips[i];
const cpx = (prev.x + cur.x) / 2;
const cpy = Math.min(prev.y, cur.y) - 25 - (1 - fa / MAX_FOLD) * 15;
canopyD += ` Q ${cpx.toFixed(1)} ${cpy.toFixed(1)} ${cur.x.toFixed(1)} ${cur.y.toFixed(1)}`;
}
// 闭合回伞毂
canopyD += ` L ${HUB.x} ${HUB.y} Z`;
canopy.setAttribute('d', canopyD);
// 连接线
const mainHinge = ribHinge(0);
connectorLine.setAttribute('x1', mainHinge.x.toFixed(1));
connectorLine.setAttribute('y1', mainHinge.y.toFixed(1));
// 脉冲效果
if (pulseProgress >= 0 && pulseProgress <= 1) {
const py = 660 - pulseProgress * (660 - HUB.y);
pulseDot.setAttribute('cy', py.toFixed(1));
pulseDot.setAttribute('opacity', '0.9');
pulseLine.setAttribute('y1', '660');
pulseLine.setAttribute('y2', py.toFixed(1));
pulseLine.setAttribute('opacity', '0.5');
} else {
pulseDot.setAttribute('opacity', '0');
pulseLine.setAttribute('opacity', '0');
}
// 电路LED
if (pulseProgress >= 0) {
circuitLed.setAttribute('fill', '#00ff88');
} else if (magStrength > 0.5) {
circuitLed.setAttribute('fill', `rgba(0,${Math.floor(180 + magStrength * 75)},${Math.floor(100 + magStrength * 50)},0.8)`);
} else {
circuitLed.setAttribute('fill', '#1a3a2a');
}
// 风粒子
updateWindParticles();
// 细节图更新
renderDetail();
// 示波器
renderOscilloscope();
// 状态栏
renderStatusBar();
}
function updateWindParticles() {
const active = state === 'wind' && windIntensity > 0.1;
for (const p of windParticles) {
if (!p.active && active && Math.random() < 0.15) {
p.active = true;
p.x = 1050 + Math.random() * 100;
p.y = 80 + Math.random() * 300;
p.vx = -(3 + Math.random() * 4) * windIntensity;
p.vy = (Math.random() - 0.5) * 1.5;
p.life = 1;
}
if (p.active) {
p.x += p.vx;
p.y += p.vy;
p.life -= 0.015;
if (p.life <= 0 || p.x < 150) {
p.active = false;
p.el.setAttribute('opacity', '0');
} else {
p.el.setAttribute('cx', p.x.toFixed(1));
p.el.setAttribute('cy', p.y.toFixed(1));
p.el.setAttribute('opacity', (p.life * 0.4 * windIntensity).toFixed(3));
}
}
}
}
function renderDetail() {
// 磁力线
const mlOpacity = magStrength * 0.7;
detailMagLines.forEach((ml, j) => {
ml.setAttribute('opacity', mlOpacity.toFixed(3));
ml.setAttribute('stroke-dashoffset', ((t * 15 + j * 5) % 20).toFixed(1));
});
// 脉冲箭头
const showPulse = pulseProgress >= 0;
arrowHead.setAttribute('opacity', showPulse ? '0.9' : '0');
detailPulseArrow.setAttribute('opacity', showPulse ? '0.8' : '0');
// 外段端位置变化
const foldOffset = foldAngle > 10 ? 8 : 0;
outerDetailRect.setAttribute('y', detailCy - 8 + foldOffset);
outerDetailRect.setAttribute('transform', foldAngle > 10 ?
`rotate(${Math.min(foldAngle * 0.3, 35)}, ${detailCx + 30}, ${detailCy})` : '');
// 状态标签
if (magStrength > 0.8) {
detailStateLabel.textContent = 'MAGNETIC LOCK ENGAGED';
detailStateLabel.setAttribute('fill', '#00e5ff');
} else if (magStrength > 0.1) {
detailStateLabel.textContent = 'DEMAGNETIZING...';
detailStateLabel.setAttribute('fill', '#ff9100');
} else if (foldAngle > 10) {
detailStateLabel.textContent = 'SPRING RELEASE / FOLDING';
detailStateLabel.setAttribute('fill', '#ff9100');
} else {
detailStateLabel.textContent = 'STANDBY';
detailStateLabel.setAttribute('fill', '#5a7a9a');
}
}
let oscData = new Array(100).fill(0);
function renderOscilloscope() {
// 生成波形数据
let val = 0;
if (pulseProgress >= 0) {
// 尖锐脉冲:快速上升,指数衰减
const pt = pulseProgress;
val = 3 * Math.exp(-pt * 12) * Math.sin(pt * 60);
if (pt < 0.15) val = 3;
} else if (state === 'open' || state === 'wind') {
val = 0.05 * Math.sin(t * 10); // 微弱基线噪声
}
oscData.push(val);
if (oscData.length > 100) oscData.shift();
const x0 = oscX + 10, y0 = oscY + 15;
const w = oscW - 20, h = oscH - 30;
const mid = y0 + h / 2;
const pts = oscData.map((v, i) => {
const px = x0 + (i / 100) * w;
const py = mid - (v / 4) * (h / 2);
return `${px.toFixed(1)},${py.toFixed(1)}`;
}).join(' ');
oscPath.setAttribute('points', pts);
}
function renderStatusBar() {
const mf = (magStrength * MAG_FORCE_MAX).toFixed(1);
const se = (springEnergy * 100).toFixed(0);
const isPulse = pulseProgress >= 0;
const wf = (windIntensity * 30).toFixed(1);
document.getElementById('valMag').textContent = `${mf} N`;
document.getElementById('valSpring').textContent = `${se}%`;
document.getElementById('valPulse').textContent = isPulse ? `${PULSE_CURRENT}A` : 'OFF';
document.getElementById('valWind').textContent = `${wf} N`;
const dotMag = document.getElementById('dotMag');
const dotSpring = document.getElementById('dotSpring');
const dotPulse = document.getElementById('dotPulse');
const dotWind = document.getElementById('dotWind');
dotMag.className = `dot ${magStrength > 0.5 ? 'dot-cyan' : 'dot-off'}`;
dotSpring.className = `dot ${springEnergy > 0.3 ? 'dot-amber' : 'dot-off'}`;
dotPulse.className = `dot ${isPulse ? 'dot-red' : 'dot-off'}`;
dotWind.className = `dot ${windIntensity > 0.1 ? 'dot-amber' : 'dot-off'}`;
const stateNames = {
folded: '已折叠', opening: '展开中...', locking: '磁力锁定中...',
open: '展开锁定', wind: '抗风测试', pulse_firing: '脉冲发射!',
demagnetizing: '消磁中...', spring_release: '扭簧释放!',
folding: '折叠中...', emergency: '应急解锁!'
};
document.getElementById('valState').textContent = stateNames[state] || state;
}
/* ===================== 主循环 ===================== */
function loop(ts) {
const dt = Math.min((ts - lastTime) / 1000, 0.05);
lastTime = ts;
update(dt);
render();
requestAnimationFrame(loop);
}
lastTime = performance.now();
requestAnimationFrame(loop);
/* ===================== 事件处理 ===================== */
document.getElementById('btnOpen').addEventListener('click', () => {
if (state === 'folded' || state === 'open') setState('opening');
});
document.getElementById('btnWind').addEventListener('click', () => {
if (state === 'open' || state === 'wind') setState('wind');
});
document.getElementById('btnPulse').addEventListener('click', () => {
if (state === 'open' || state === 'wind') {
pulseProgress = 0;
setState('pulse_firing');
}
});
document.getElementById('btnEmergency').addEventListener('click', () => {
if (state === 'open' || state === 'wind') {
setState('emergency');
}
});
document.getElementById('windRange').addEventListener('input', (e) => {
windIntensity = parseInt(e.target.value) / 100;
document.getElementById('windVal').textContent = e.target.value + '%';
});
updateButtons();
</script>
</body>
</html>
实现说明:
这个单文件 HTML 实现了一个完整的磁力锁定折叠伞骨 IFR 原理动画,核心技术要点如下:
双段伞骨 + 铰链机制:5 根伞骨各分为内外两段,中间铰链处可视化磁力锁定(青色辉光环)与扭簧势能(琥珀色螺旋线),折叠时外段绕铰链旋转 155°,实现"极细棒状坍缩"。
动画状态机:7 个状态完整覆盖整个机理链——展开 → 磁力锁定 → 抗风(弹性振荡)→ 脉冲消磁 → 扭簧释放 → 折叠,使用指数阻尼插值(
damp)保证过渡平滑。视觉引导设计:
- 磁力锁定:青色辉光 + 脉冲虚线环 + 强度呼吸动画,直观表达"磁力有弹性边界"
- 脉冲消磁:白色光点沿伞柱上行 + 示波器面板实时显示 3A 尖峰波形
- 扭簧释放:琥珀色辉光闪现 + 外段快速翻折
- 抗风测试:风粒子流 + 伞骨微幅振荡,展示磁力锁定的弹性吸能特性
铰链细节放大图(右下角):展示永磁体(红蓝双色)、软磁铁芯、线圈绕组、扭簧的结构关系与磁力线动画,并通过连接线指向主图对应铰链。
交互控制:展开锁定 / 抗风测试(含风力滑块) / 脉冲收伞 / 应急机械解锁四个按钮,均与动画状态机联动;底部状态栏实时显示磁力、势能、电流、风力数值。
积分规则:第一轮对话扣减6分,后续每轮扣4分
等待动画代码生成...
