独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>三段式自律防夹伞骨折叠机构原理动画</title>
<style>
:root {
--bg-color: #0b0f19;
--panel-bg: rgba(15, 23, 42, 0.75);
--border-color: rgba(56, 189, 248, 0.18);
--accent-cyan: #38bdf8;
--accent-orange: #fb923c;
--accent-pink: #f43f5e;
--accent-green: #34d399;
--text-main: #f1f5f9;
--text-sub: #94a3b8;
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
}
body {
background-color: var(--bg-color);
color: var(--text-main);
font-family: var(--font-family);
width: 100vw;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
position: relative;
}
/* 背景工程网格与极光微晕 */
.bg-grid {
position: absolute;
inset: 0;
background-image:
radial-gradient(circle at 50% 20%, rgba(56, 189, 248, 0.08) 0%, transparent 60%),
linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
background-size: 100% 100%, 40px 40px, 40px 40px;
pointer-events: none;
z-index: 0;
}
/* 顶部精简状态头(极小字号,靠边缘,防遮挡) */
header {
position: absolute;
top: 14px;
left: 18px;
z-index: 10;
pointer-events: none;
display: flex;
flex-direction: column;
gap: 4px;
}
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 3px 8px;
border-radius: 4px;
background: rgba(56, 189, 248, 0.12);
border: 1px solid var(--border-color);
width: fit-content;
}
.badge-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent-cyan);
box-shadow: 0 0 8px var(--accent-cyan);
animation: pulse 2s infinite;
}
.badge-text {
font-size: 11px;
letter-spacing: 0.5px;
color: var(--accent-cyan);
font-weight: 600;
}
.main-title {
font-size: 14px;
font-weight: 600;
color: #fff;
letter-spacing: 0.3px;
}
.spec-tags {
display: flex;
gap: 8px;
margin-top: 2px;
}
.tag {
font-size: 10px;
color: var(--text-sub);
background: rgba(255, 255, 255, 0.04);
padding: 2px 6px;
border-radius: 3px;
border: 1px solid rgba(255, 255, 255, 0.06);
}
.tag b {
color: var(--text-main);
font-weight: 500;
}
/* 右上角局部微观特写 HUD 框 */
.micro-hud {
position: absolute;
top: 14px;
right: 18px;
z-index: 10;
background: var(--panel-bg);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 8px 10px;
backdrop-filter: blur(8px);
width: 190px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
pointer-events: none;
}
.hud-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
padding-bottom: 4px;
}
.hud-title {
font-size: 10px;
font-weight: 600;
color: var(--accent-orange);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.hud-status {
font-size: 10px;
font-family: monospace;
color: var(--accent-green);
}
.hud-svg-box {
width: 100%;
height: 78px;
background: rgba(0, 0, 0, 0.25);
border-radius: 4px;
}
.hud-desc {
font-size: 9.5px;
color: var(--text-sub);
margin-top: 5px;
line-height: 1.3;
}
/* 主视觉 SVG 画布容器:全屏自适应,核心位于居中区域 */
.viewport {
flex: 1;
width: 100%;
height: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
z-index: 1;
}
svg#mechanism-stage {
width: 100%;
height: 100%;
max-width: 1100px;
max-height: 820px;
overflow: visible;
}
/* 图例(右侧靠边纵向紧凑排布) */
.legend-panel {
position: absolute;
right: 18px;
bottom: 74px;
z-index: 10;
background: var(--panel-bg);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 6px;
padding: 7px 10px;
backdrop-filter: blur(6px);
display: flex;
flex-direction: column;
gap: 5px;
pointer-events: none;
}
.legend-item {
display: flex;
align-items: center;
gap: 7px;
font-size: 10px;
color: var(--text-sub);
}
.legend-bar {
width: 14px;
height: 3px;
border-radius: 2px;
}
/* 底部极简浮动控制栏 */
footer.control-dock {
position: absolute;
bottom: 14px;
left: 50%;
transform: translateX(-50%);
z-index: 20;
display: flex;
align-items: center;
gap: 12px;
background: var(--panel-bg);
border: 1px solid var(--border-color);
border-radius: 30px;
padding: 6px 16px;
backdrop-filter: blur(12px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.btn-toggle {
background: rgba(56, 189, 248, 0.15);
border: 1px solid var(--accent-cyan);
color: #fff;
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
outline: none;
transition: all 0.2s ease;
}
.btn-toggle:hover {
background: var(--accent-cyan);
color: #0b0f19;
transform: scale(1.05);
}
.slider-box {
display: flex;
align-items: center;
gap: 8px;
}
.slider-box span {
font-size: 11px;
color: var(--text-sub);
font-family: monospace;
min-width: 52px;
}
input[type="range"] {
-webkit-appearance: none;
width: 150px;
height: 4px;
background: rgba(255, 255, 255, 0.15);
border-radius: 2px;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--accent-cyan);
cursor: pointer;
box-shadow: 0 0 6px var(--accent-cyan);
}
.status-text-pill {
font-size: 11px;
color: var(--text-main);
padding: 2px 8px;
background: rgba(255, 255, 255, 0.06);
border-radius: 12px;
white-space: nowrap;
}
@keyframes pulse {
0%, 100% { opacity: 0.4; }
50% { opacity: 1; }
}
</style>
</head>
<body>
<div class="bg-grid"></div>
<!-- 顶部轻量标头 -->
<header>
<div class="badge">
<div class="badge-dot"></div>
<span class="badge-text">TRIZ IFR 最终理想解工程模型</span>
</div>
<div class="main-title">三段自律防夹骨架:外段先折 × 6mm 延时槽 × 柔性索自收拢</div>
<div class="spec-tags">
<div class="tag">主骨配比 <b>220 / 180 / 140 mm</b></div>
<div class="tag">折角限位 <b>外 165° | 中 150°</b></div>
<div class="tag">牵引预紧 <b>2.5 N</b></div>
<div class="tag">机械延时 <b>6 mm 长槽</b></div>
</div>
</header>
<!-- 右上角特写观察视窗(6mm延时机构与折角限位) -->
<div class="micro-hud">
<div class="hud-header">
<span class="hud-title">节点微动显微</span>
<span class="hud-status" id="slot-travel-text">槽位移: 0.0mm</span>
</div>
<svg class="hud-svg-box" viewBox="0 0 160 65" id="hud-svg">
<defs>
<pattern id="hud-subgrid" width="10" height="10" patternUnits="userSpaceOnUse">
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="rgba(255,255,255,0.06)" stroke-width="0.5"/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#hud-subgrid)"/>
<!-- 中段长槽导轨 -->
<rect x="25" y="24" width="100" height="12" rx="6" fill="#1e293b" stroke="#475569" stroke-width="1.5"/>
<!-- 行程刻度线 -->
<line x1="45" y1="20" x2="45" y2="40" stroke="#f43f5e" stroke-dasharray="2,2" stroke-width="1"/>
<line x1="85" y1="20" x2="85" y2="40" stroke="#34d399" stroke-dasharray="2,2" stroke-width="1"/>
<text x="35" y="16" fill="#94a3b8" font-size="7" font-family="monospace">0mm</text>
<text x="80" y="16" fill="#34d399" font-size="7" font-family="monospace">6mm限位</text>
<!-- 滑动销轴(动态) -->
<circle id="hud-pin" cx="45" cy="30" r="5" fill="#38bdf8" stroke="#fff" stroke-width="1.5"/>
<path id="hud-arrow" d="M 52 30 L 68 30 M 64 27 L 68 30 L 64 33" stroke="#38bdf8" stroke-width="1.2" fill="none"/>
</svg>
<div class="hud-desc" id="hud-note">跑套下滑初期:撑杆销轴空行程滑移,锁住中段延迟动作。</div>
</div>
<!-- 中央主机构舞台 -->
<div class="viewport">
<svg id="mechanism-stage" viewBox="0 0 960 660">
<defs>
<!-- 伞面半透明膜梯度 -->
<linearGradient id="canopyGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#38bdf8" stop-opacity="0.25"/>
<stop offset="60%" stop-color="#0284c7" stop-opacity="0.12"/>
<stop offset="100%" stop-color="#0369a1" stop-opacity="0.04"/>
</linearGradient>
<linearGradient id="shaftGrad" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#64748b"/>
<stop offset="50%" stop-color="#cbd5e1"/>
<stop offset="100%" stop-color="#475569"/>
</linearGradient>
<!-- 发光滤镜 -->
<filter id="cyanGlow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="3" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="pinkGlow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="3.5" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
</defs>
<!-- 导向中棒与刻度 -->
<g id="center-shaft-assembly">
<!-- 垂直中轴线 -->
<line x1="480" y1="60" x2="480" y2="600" stroke="rgba(255,255,255,0.08)" stroke-width="1" stroke-dasharray="4,4"/>
<!-- 中棒实体 -->
<rect x="474" y="90" width="12" height="490" rx="4" fill="url(#shaftGrad)"/>
<!-- 顶冠/上巢(固定铰接盘) -->
<path d="M 464 100 L 496 100 L 492 118 L 468 118 Z" fill="#94a3b8" stroke="#334155" stroke-width="1.5"/>
<circle cx="480" cy="106" r="3" fill="#0f172a"/>
<text x="498" y="110" fill="#64748b" font-size="9" font-family="monospace">TOP-HUB (上巢)</text>
</g>
<!-- 伞布曲面网(背景层) -->
<path id="canopy-mesh-left" fill="url(#canopyGrad)" stroke="rgba(56,189,248,0.4)" stroke-width="1.2"/>
<path id="canopy-mesh-right" fill="url(#canopyGrad)" stroke="rgba(56,189,248,0.4)" stroke-width="1.2"/>
<!-- 动态骨架系统容器 (双侧对称,重点透视右侧) -->
<!-- 柔性牵引索(粉红发光虚线:表现主动施加 2.5N 张力引导布面向轴心收束) -->
<g id="tension-cables">
<path id="cable-left" stroke="#f43f5e" stroke-width="1.5" stroke-dasharray="4,3" fill="none" opacity="0.85" filter="url(#pinkGlow)"/>
<path id="cable-right" stroke="#f43f5e" stroke-width="1.5" stroke-dasharray="4,3" fill="none" opacity="0.85" filter="url(#pinkGlow)"/>
<!-- 牵引向量箭头标识 -->
<g id="tension-vectors" opacity="0"></g>
</g>
<!-- 伞骨连杆图元 -->
<g id="ribs-group">
<!-- 左侧副连杆组 (镜像,微弱透明度以突显右侧核心分析区) -->
<g id="left-ribs" opacity="0.45"></g>
<!-- 右侧主分析连杆组 -->
<g id="right-ribs">
<!-- 内段主骨 L1 (220mm) -->
<line id="inner-rib" stroke="#94a3b8" stroke-width="5" stroke-linecap="round"/>
<line id="inner-rib-core" stroke="#e2e8f0" stroke-width="1.5" stroke-linecap="round"/>
<!-- 撑杆 Stretcher (连接跑套与中段延时槽) -->
<line id="stretcher" stroke="#38bdf8" stroke-width="3.5" stroke-linecap="round"/>
<!-- 中段主骨 L2 (180mm) -->
<line id="mid-rib" stroke="#38bdf8" stroke-width="4.5" stroke-linecap="round"/>
<!-- 中段延时滑动槽槽位标识 -->
<line id="slot-line" stroke="#10b981" stroke-width="6" stroke-linecap="round" opacity="0.75"/>
<circle id="slot-pin" r="3.2" fill="#fff" stroke="#0f172a" stroke-width="1.5"/>
<!-- 外段主骨 L3 (140mm) - 橙黄色高亮优先折转 -->
<line id="outer-rib" stroke="#fb923c" stroke-width="3.5" stroke-linecap="round" filter="url(#cyanGlow)"/>
<circle id="outer-tip" r="3" fill="#fdba74"/>
<!-- 铰链节点 -->
<circle id="hinge-0" cx="480" cy="112" r="3.5" fill="#f8fafc" stroke="#0f172a" stroke-width="1.5"/>
<circle id="hinge-1" r="4" fill="#38bdf8" stroke="#fff" stroke-width="1.5"/>
<circle id="hinge-2" r="4" fill="#fb923c" stroke="#fff" stroke-width="1.5"/>
</g>
</g>
<!-- 下滑跑套 (Runner) -->
<g id="runner-assembly">
<rect id="runner" x="468" y="240" width="24" height="28" rx="4" fill="#38bdf8" stroke="#0284c7" stroke-width="2"/>
<line x1="472" y1="254" x2="488" y2="254" stroke="#fff" stroke-width="1.5"/>
<circle id="runner-hinge" cx="480" cy="254" r="3" fill="#fff"/>
<text id="runner-label" x="424" y="258" fill="#38bdf8" font-size="9" font-family="monospace">RUNNER ▼</text>
</g>
<!-- 动态角度与运动轨迹弧线提示 -->
<g id="motion-annotations" pointer-events="none">
<!-- 外段优先折叠角指示 -->
<path id="angle-arc-outer" fill="none" stroke="#fb923c" stroke-width="1.2" stroke-dasharray="2,2"/>
<text id="angle-text-outer" fill="#fb923c" font-size="9.5" font-weight="600" font-family="monospace">165° 先折</text>
<!-- 中段后折角指示 -->
<path id="angle-arc-mid" fill="none" stroke="#38bdf8" stroke-width="1.2" stroke-dasharray="2,2"/>
<text id="angle-text-mid" fill="#38bdf8" font-size="9.5" font-weight="600" font-family="monospace">150° 延迟</text>
</g>
<!-- 收拢导引力场波纹(在近收束态时展现布面平顺内压) -->
<g id="inward-field-lines" opacity="0"></g>
</svg>
</div>
<!-- 图例面板 -->
<div class="legend-panel">
<div class="legend-item">
<div class="legend-bar" style="background:#fb923c"></div>
<span>外段骨 (140mm / 165°先折)</span>
</div>
<div class="legend-item">
<div class="legend-bar" style="background:#10b981"></div>
<span>撑杆滑移 (6mm延迟槽)</span>
</div>
<div class="legend-item">
<div class="legend-bar" style="background:#38bdf8"></div>
<span>中段骨 (180mm / 150°后折)</span>
</div>
<div class="legend-item">
<div class="legend-bar" style="background:#f43f5e"></div>
<span>柔性索 (2.5N自收拢防夹)</span>
</div>
</div>
<!-- 底部微型播放控制台 -->
<footer class="control-dock">
<button class="btn-toggle" id="btn-play-pause" title="播放 / 暂停">
<svg id="play-icon" width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>
</svg>
</button>
<div class="slider-box">
<input type="range" id="progress-slider" min="0" max="1" step="0.001" value="0"/>
<span id="cycle-percent">0%</span>
</div>
<div class="status-text-pill" id="stage-desc">开伞稳定态:膜面张紧</div>
</footer>
<script>
/**
* 三段式自律防夹伞骨收合数学运动模型
* 遵循 TRIZ IFR 哲学:利用几何行程差与构件内在时序解耦,消除随机夹布矛盾。
*/
const CONFIG = {
hub: { x: 480, y: 112 },
runnerOpenY: 235,
runnerCloseY: 485,
L1: 130, // 内段 (220mm 标度映射)
L2: 108, // 中段 (180mm 标度映射)
L3: 84, // 外段 (140mm 标度映射)
stretcherLen: 125,
slotDistOnMid: 36, // 长槽位于中段骨近端基准位置
slotStroke: 18, // 对应 6mm 机械延时槽行程
cycleDuration: 6200 // 完整单周期动画时间 ms
};
// DOM 元素引用
const stage = document.getElementById('mechanism-stage');
const runnerEl = document.getElementById('runner');
const runnerHingeEl = document.getElementById('runner-hinge');
const runnerLabel = document.getElementById('runner-label');
const innerRib = document.getElementById('inner-rib');
const innerRibCore = document.getElementById('inner-rib-core');
const midRib = document.getElementById('mid-rib');
const outerRib = document.getElementById('outer-rib');
const outerTip = document.getElementById('outer-tip');
const stretcherEl = document.getElementById('stretcher');
const slotLine = document.getElementById('slot-line');
const slotPin = document.getElementById('slot-pin');
const hinge1 = document.getElementById('hinge-1');
const hinge2 = document.getElementById('hinge-2');
const leftRibsGroup = document.getElementById('left-ribs');
const canopyLeft = document.getElementById('canopy-mesh-left');
const canopyRight = document.getElementById('canopy-mesh-right');
const cableLeft = document.getElementById('cable-left');
const cableRight = document.getElementById('cable-right');
const angleArcOuter = document.getElementById('angle-arc-outer');
const angleTextOuter = document.getElementById('angle-text-outer');
const angleArcMid = document.getElementById('angle-arc-mid');
const angleTextMid = document.getElementById('angle-text-mid');
// HUD & 控件元素
const hudPin = document.getElementById('hud-pin');
const slotTravelText = document.getElementById('slot-travel-text');
const hudNote = document.getElementById('hud-note');
const btnPlayPause = document.getElementById('btn-play-pause');
const playIcon = document.getElementById('play-icon');
const slider = document.getElementById('progress-slider');
const cyclePercent = document.getElementById('cycle-percent');
const stageDesc = document.getElementById('stage-desc');
let isPlaying = true;
let progress = 0; // 0.0 (全开) -> 1.0 (完全紧密收拢)
let lastTimestamp = null;
let animDirection = 1; // 1: 收伞折叠, -1: 复位展开
// 缓动函数
function easeInOutCubic(x) {
return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
}
/**
* 根据折叠进程 t (0 ~ 1) 计算多杆铰链精确坐标
*/
function computeKinematics(t) {
// 1. 跑套位置:沿垂直中棒线性匀速下拉
const ry = CONFIG.runnerOpenY + (CONFIG.runnerCloseY - CONFIG.runnerOpenY) * t;
// 2. 内段主骨旋转角度 theta1
// 开伞时与中轴夹角约 72°,收拢时贴近中棒约 7°
const theta1_open = (72 * Math.PI) / 180;
const theta1_closed = (7 * Math.PI) / 180;
// 引入非线性延迟响应(前段受延迟槽影响倾角变化较慢)
const t_mid = Math.max(0, (t - 0.22) / 0.78);
const theta1 = theta1_open - (theta1_open - theta1_closed) * easeInOutCubic(t_mid);
const h0 = { x: CONFIG.hub.x, y: CONFIG.hub.y };
const h1 = {
x: h0.x + CONFIG.L1 * Math.sin(theta1),
y: h0.y + CONFIG.L1 * Math.cos(theta1)
};
// 3. 6mm 延时槽内的销轴位移 (0.0 到 1.0 归一化)
// 在收伞前半段 (t: 0.05 ~ 0.38),撑杆销轴在槽内由顶端滑向底端硬限位
let slotProgress = 0;
if (t < 0.05) {
slotProgress = 0;
} else if (t <= 0.38) {
slotProgress = (t - 0.05) / 0.33;
} else {
slotProgress = 1.0;
}
const slotTravelMm = (slotProgress * 6).toFixed(1);
// 4. 中段骨主折角 theta2
// 当延时槽走完限位后,撑杆带动中段向内弯折至 150°
const midFoldAngleMax = (150 * Math.PI) / 180;
const midFold = midFoldAngleMax * easeInOutCubic(Math.max(0, (t - 0.28) / 0.72));
const theta2 = theta1 - midFold;
const h2 = {
x: h1.x + CONFIG.L2 * Math.sin(theta2),
y: h1.y + CONFIG.L2 * Math.cos(theta2)
};
// 撑杆在中段长槽上的作用点
const slotBaseDist = CONFIG.slotDistOnMid + CONFIG.slotStroke * slotProgress;
const slotPinPos = {
x: h1.x + slotBaseDist * Math.sin(theta2),
y: h1.y + slotBaseDist * Math.cos(theta2)
};
// 5. 外段骨先折角 theta3:核心创新时序(先于中段在 t: 0.10 ~ 0.52 迅速回折 165°)
const outerFoldMax = (165 * Math.PI) / 180;
let outerT = 0;
if (t > 0.06 && t < 0.58) {
outerT = (t - 0.06) / 0.52;
} else if (t >= 0.58) {
outerT = 1.0;
}
const outerFold = outerFoldMax * easeInOutCubic(outerT);
const theta3 = theta2 - outerFold;
const tip = {
x: h2.x + CONFIG.L3 * Math.sin(theta3),
y: h2.y + CONFIG.L3 * Math.cos(theta3)
};
// 6. 柔性牵引索锚点与受拉内收轨迹
// 牵引索连接内段中点、中段折痕和外伞缘,将伞布拉向中轴
const cableMidAnchor = {
x: (h1.x + h2.x) * 0.5 - 12 * (1 - t),
y: (h1.y + h2.y) * 0.5
};
return {
ry,
h0,
h1,
h2,
tip,
slotPinPos,
slotProgress,
slotTravelMm,
cableMidAnchor,
theta1,
theta2,
theta3,
outerFold
};
}
/**
* 渲染完整骨架系统
*/
function renderFrame(p) {
const state = computeKinematics(p);
const cx = CONFIG.hub.x;
// 跑套位置
runnerEl.setAttribute('y', state.ry - 14);
runnerHingeEl.setAttribute('cy', state.ry);
runnerLabel.setAttribute('y', state.ry + 4);
// 内段主骨
innerRib.setAttribute('x1', state.h0.x);
innerRib.setAttribute('y1', state.h0.y);
innerRib.setAttribute('x2', state.h1.x);
innerRib.setAttribute('y2', state.h1.y);
innerRibCore.setAttribute('x1', state.h0.x);
innerRibCore.setAttribute('y1', state.h0.y);
innerRibCore.setAttribute('x2', state.h1.x);
innerRibCore.setAttribute('y2', state.h1.y);
// 撑杆
stretcherEl.setAttribute('x1', cx);
stretcherEl.setAttribute('y1', state.ry);
stretcherEl.setAttribute('x2', state.slotPinPos.x);
stretcherEl.setAttribute('y2', state.slotPinPos.y);
// 中段主骨
midRib.setAttribute('x1', state.h1.x);
midRib.setAttribute('y1', state.h1.y);
midRib.setAttribute('x2', state.h2.x);
midRib.setAttribute('y2', state.h2.y);
// 6mm 延时槽实体与滑动销轴
const slotStart = {
x: state.h1.x + CONFIG.slotDistOnMid * Math.sin(state.theta2),
y: state.h1.y + CONFIG.slotDistOnMid * Math.cos(state.theta2)
};
const slotEnd = {
x: state.h1.x + (CONFIG.slotDistOnMid + CONFIG.slotStroke) * Math.sin(state.theta2),
y: state.h1.y + (CONFIG.slotDistOnMid + CONFIG.slotStroke) * Math.cos(state.theta2)
};
slotLine.setAttribute('x1', slotStart.x);
slotLine.setAttribute('y1', slotStart.y);
slotLine.setAttribute('x2', slotEnd.x);
slotLine.setAttribute('y2', slotEnd.y);
slotPin.setAttribute('cx', state.slotPinPos.x);
slotPin.setAttribute('cy', state.slotPinPos.y);
// 外段骨(橙色先折段)
outerRib.setAttribute('x1', state.h2.x);
outerRib.setAttribute('y1', state.h2.y);
outerRib.setAttribute('x2', state.tip.x);
outerRib.setAttribute('y2', state.tip.y);
outerTip.setAttribute('cx', state.tip.x);
outerTip.setAttribute('cy', state.tip.y);
// 铰链节点更新
hinge1.setAttribute('cx', state.h1.x);
hinge1.setAttribute('cy', state.h1.y);
hinge2.setAttribute('cx', state.h2.x);
hinge2.setAttribute('cy', state.h2.y);
// 镜像左侧骨架渲染
const leftH1 = { x: 2 * cx - state.h1.x, y: state.h1.y };
const leftH2 = { x: 2 * cx - state.h2.x, y: state.h2.y };
const leftTip = { x: 2 * cx - state.tip.x, y: state.tip.y };
const leftSlotPin = { x: 2 * cx - state.slotPinPos.x, y: state.slotPinPos.y };
leftRibsGroup.innerHTML = `
<line x1="${cx}" y1="${state.h0.y}" x2="${leftH1.x}" y2="${leftH1.y}" stroke="#94a3b8" stroke-width="4.5" stroke-linecap="round"/>
<line x1="${cx}" y1="${state.ry}" x2="${leftSlotPin.x}" y2="${leftSlotPin.y}" stroke="#38bdf8" stroke-width="3" stroke-linecap="round"/>
<line x1="${leftH1.x}" y1="${leftH1.y}" x2="${leftH2.x}" y2="${leftH2.y}" stroke="#38bdf8" stroke-width="4" stroke-linecap="round"/>
<line x1="${leftH2.x}" y1="${leftH2.y}" x2="${leftTip.x}" y2="${leftTip.y}" stroke="#fb923c" stroke-width="3" stroke-linecap="round"/>
<circle cx="${leftH1.x}" cy="${leftH1.y}" r="3.5" fill="#38bdf8"/>
<circle cx="${leftH2.x}" cy="${leftH2.y}" r="3.5" fill="#fb923c"/>
<circle cx="${leftTip.x}" cy="${leftTip.y}" r="2.5" fill="#fdba74"/>
`;
// 伞布半透明曲面:显示根据折痕紧致内收
const clothCurveRight = `M ${cx} ${state.h0.y} Q ${(cx + state.h1.x) * 0.5 + 8 * (1 - p)} ${(state.h0.y + state.h1.y) * 0.5 - 6 * (1 - p)}, ${state.h1.x} ${state.h1.y} Q ${(state.h1.x + state.h2.x) * 0.5} ${(state.h1.y + state.h2.y) * 0.5 - 4 * (1 - p)}, ${state.h2.x} ${state.h2.y} L ${state.tip.x} ${state.tip.y} L ${cx + 10 * (1 - p)} ${state.ry + 20} Z`;
canopyRight.setAttribute('d', clothCurveRight);
const clothCurveLeft = `M ${cx} ${state.h0.y} Q ${(cx + leftH1.x) * 0.5 - 8 * (1 - p)} ${(state.h0.y + leftH1.y) * 0.5 - 6 * (1 - p)}, ${leftH1.x} ${leftH1.y} Q ${(leftH1.x + leftH2.x) * 0.5} ${(leftH1.y + leftH2.y) * 0.5 - 4 * (1 - p)}, ${leftH2.x} ${leftH2.y} L ${leftTip.x} ${leftTip.y} L ${cx - 10 * (1 - p)} ${state.ry + 20} Z`;
canopyLeft.setAttribute('d', clothCurveLeft);
// 弹性收束索(内侧牵引拉力流):体现把伞布拽向中棒,彻底杜绝夹布
const cableRightPath = `M ${cx} ${state.ry - 40} Q ${state.cableMidAnchor.x} ${state.cableMidAnchor.y}, ${state.h2.x} ${state.h2.y}`;
cableRight.setAttribute('d', cableRightPath);
const cableLeftPath = `M ${cx} ${state.ry - 40} Q ${2 * cx - state.cableMidAnchor.x} ${state.cableMidAnchor.y}, ${leftH2.x} ${leftH2.y}`;
cableLeft.setAttribute('d', cableLeftPath);
// 局部标注与角度弧线
if (p > 0.08 && p < 0.88) {
// 外段先折弧线
angleArcOuter.setAttribute('opacity', '1');
angleTextOuter.setAttribute('opacity', '1');
const arcR = 24;
const arcX = state.h2.x + arcR * Math.sin(state.theta2 - 0.4);
const arcY = state.h2.y + arcR * Math.cos(state.theta2 - 0.4);
angleArcOuter.setAttribute('d', `M ${state.h2.x} ${state.h2.y - arcR} A ${arcR} ${arcR} 0 0 1 ${arcX} ${arcY}`);
angleTextOuter.setAttribute('x', state.h2.x + 22);
angleTextOuter.setAttribute('y', state.h2.y - 12);
// 中段后折指示
angleArcMid.setAttribute('opacity', '1');
angleTextMid.setAttribute('opacity', '1');
angleTextMid.setAttribute('x', state.h1.x + 18);
angleTextMid.setAttribute('y', state.h1.y + 14);
} else {
angleArcOuter.setAttribute('opacity', '0');
angleTextOuter.setAttribute('opacity', '0');
angleArcMid.setAttribute('opacity', '0');
angleTextMid.setAttribute('opacity', '0');
}
// 同步右上角 HUD 微观视窗
const hudPinX = 45 + state.slotProgress * 40;
hudPin.setAttribute('cx', hudPinX);
slotTravelText.textContent = `槽位移: ${state.slotTravelMm}mm`;
// 状态解说与文字精简更新
updateStateFeedback(p, state);
}
/**
* 阶段语义描述(精准对应 TRIZ 时序解决动作)
*/
function updateStateFeedback(p, state) {
slider.value = p;
cyclePercent.textContent = `${Math.round(p * 100)}%`;
if (p < 0.15) {
stageDesc.textContent = '【状态 1】全开受力态:双巢顶紧,伞面弧度丰盈';
hudNote.textContent = '跑套初始下滑:撑杆销轴于 6mm 长槽内自由滑移';
} else if (p < 0.42) {
stageDesc.textContent = '【状态 2】外段骨率先折入 165°:避让外缘干涉';
hudNote.textContent = `6mm 延迟槽滑移中 (${state.slotTravelMm}mm),中段保持硬限位未折`;
} else if (p < 0.72) {
stageDesc.textContent = '【状态 3】中段后折 150° + 弹性索 2.5N 张力主动内收';
hudNote.textContent = '槽行程到达 6mm 机械止挡,力矩传导带动中段折拢';
} else if (p < 0.94) {
stageDesc.textContent = '【状态 4】平顺聚拢:骨架紧贴中棒,零随机夹布';
hudNote.textContent = '伞布沿折痕规整内缩,收束环导入紧凑束身';
} else {
stageDesc.textContent = '【状态 5】极限理想解:最小收合包络完成';
hudNote.textContent = '完成一阶循环,即将进行反向开伞动作演示';
}
}
/**
* 主动画渲染循环 (基于 requestAnimationFrame)
*/
function animateLoop(timestamp) {
if (!lastTimestamp) lastTimestamp = timestamp;
const deltaTime = timestamp - lastTimestamp;
lastTimestamp = timestamp;
if (isPlaying) {
// 增量计算
const deltaProgress = (deltaTime / CONFIG.cycleDuration) * animDirection;
progress += deltaProgress;
// 到达双向极值后稍作停顿并往复
if (progress >= 1.0) {
progress = 1.0;
animDirection = -1;
} else if (progress <= 0.0) {
progress = 0.0;
animDirection = 1;
}
renderFrame(progress);
}
requestAnimationFrame(animateLoop);
}
// 交互绑定:播放/暂停
btnPlayPause.addEventListener('click', () => {
isPlaying = !isPlaying;
playIcon.innerHTML = isPlaying
? '<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>' // 暂停图标
: '<path d="M8 5v14l11-7z"/>'; // 播放图标
});
// 交互绑定:进度滑块拖拽
slider.addEventListener('input', (e) => {
isPlaying = false;
playIcon.innerHTML = '<path d="M8 5v14l11-7z"/>';
progress = parseFloat(e.target.value);
renderFrame(progress);
});
// 页面加载完成后自动启动播放
window.addEventListener('DOMContentLoaded', () => {
renderFrame(0);
requestAnimationFrame(animateLoop);
});
</script>
</body>
</html>
积分规则:每轮对话扣10分
等待动画代码生成...
