独立渲染引擎就绪就绪
<!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=Chakra+Petch:wght@300;400;600;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<style>
:root {
--bg: #080c18;
--bg2: #0d1225;
--fg: #d8dfe8;
--muted: #5a6a82;
--accent: #00e5ff;
--accent2: #00b8d4;
--brass: #c9a54e;
--brass-light: #e0c97f;
--steel: #7b8fa6;
--steel-dark: #4a5d73;
--danger: #ff5252;
--safe: #00e676;
--card: #111827;
--border: #1e2a40;
--ground: #1a2236;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--bg);
color: var(--fg);
font-family: 'Chakra Petch', sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
overflow-x: hidden;
}
/* 标题栏 */
.top-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 32px;
background: linear-gradient(180deg, rgba(13,18,37,0.95), rgba(8,12,24,0.8));
border-bottom: 1px solid var(--border);
position: relative;
z-index: 10;
}
.top-bar h1 {
font-size: 20px;
font-weight: 600;
letter-spacing: 2px;
color: var(--accent);
text-transform: uppercase;
}
.top-bar .subtitle {
font-family: 'Share Tech Mono', monospace;
font-size: 12px;
color: var(--muted);
letter-spacing: 1px;
}
.ifr-badge {
background: linear-gradient(135deg, var(--accent), #0091ea);
color: #000;
font-size: 11px;
font-weight: 700;
padding: 4px 12px;
border-radius: 3px;
letter-spacing: 2px;
}
/* 主容器 */
.main-wrap {
flex: 1;
display: flex;
position: relative;
overflow: hidden;
}
/* SVG 场景容器 */
.scene-container {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
position: relative;
min-height: 500px;
}
.scene-container svg {
width: 100%;
max-width: 1200px;
height: auto;
max-height: 75vh;
}
/* 右侧数据面板 */
.data-panel {
width: 260px;
background: var(--card);
border-left: 1px solid var(--border);
padding: 20px 16px;
display: flex;
flex-direction: column;
gap: 12px;
overflow-y: auto;
}
.data-panel h2 {
font-size: 13px;
font-weight: 600;
color: var(--accent);
letter-spacing: 2px;
text-transform: uppercase;
margin-bottom: 4px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border);
}
.data-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.data-item .label {
font-family: 'Share Tech Mono', monospace;
font-size: 10px;
color: var(--muted);
letter-spacing: 1px;
text-transform: uppercase;
}
.data-item .value {
font-family: 'Share Tech Mono', monospace;
font-size: 22px;
font-weight: 700;
color: var(--fg);
line-height: 1;
}
.data-item .value.accent { color: var(--accent); }
.data-item .value.danger { color: var(--danger); }
.data-item .value.safe { color: var(--safe); }
.data-item .unit {
font-size: 12px;
color: var(--muted);
margin-left: 4px;
}
.force-bar-wrap {
width: 100%;
height: 8px;
background: var(--border);
border-radius: 4px;
overflow: hidden;
margin-top: 4px;
}
.force-bar {
height: 100%;
border-radius: 4px;
transition: width 0.6s ease, background 0.6s ease;
}
.phase-indicator {
background: rgba(0,229,255,0.06);
border: 1px solid rgba(0,229,255,0.15);
border-radius: 6px;
padding: 10px 12px;
margin-top: 4px;
}
.phase-indicator .phase-name {
font-size: 14px;
font-weight: 600;
color: var(--accent);
margin-bottom: 4px;
}
.phase-indicator .phase-desc {
font-size: 11px;
color: var(--muted);
line-height: 1.5;
}
.lock-badge {
display: inline-block;
font-family: 'Share Tech Mono', monospace;
font-size: 11px;
font-weight: 700;
padding: 3px 10px;
border-radius: 3px;
letter-spacing: 1px;
margin-top: 4px;
}
.lock-badge.active {
background: rgba(0,230,118,0.15);
color: var(--safe);
border: 1px solid rgba(0,230,118,0.3);
}
.lock-badge.inactive {
background: rgba(90,106,130,0.15);
color: var(--muted);
border: 1px solid rgba(90,106,130,0.2);
}
/* 底部控制栏 */
.control-bar {
display: flex;
align-items: center;
gap: 16px;
padding: 14px 32px;
background: var(--card);
border-top: 1px solid var(--border);
flex-wrap: wrap;
}
.ctrl-btn {
font-family: 'Chakra Petch', sans-serif;
font-size: 13px;
font-weight: 600;
padding: 8px 20px;
border: 1px solid var(--border);
border-radius: 4px;
background: var(--bg2);
color: var(--fg);
cursor: pointer;
transition: all 0.2s;
letter-spacing: 1px;
user-select: none;
}
.ctrl-btn:hover { border-color: var(--accent); color: var(--accent); }
.ctrl-btn:active { transform: scale(0.96); }
.ctrl-btn.primary {
background: linear-gradient(135deg, var(--accent2), var(--accent));
color: #000;
border: none;
}
.ctrl-btn.primary:hover { opacity: 0.85; }
.ctrl-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.slider-group {
display: flex;
align-items: center;
gap: 8px;
}
.slider-group label {
font-family: 'Share Tech Mono', monospace;
font-size: 11px;
color: var(--muted);
white-space: nowrap;
}
.slider-group input[type=range] {
width: 120px;
accent-color: var(--accent);
cursor: pointer;
}
.slider-group .slider-val {
font-family: 'Share Tech Mono', monospace;
font-size: 12px;
color: var(--accent);
min-width: 48px;
}
/* 阶段时间线 */
.timeline {
display: flex;
align-items: center;
gap: 0;
margin-left: auto;
}
.timeline .step {
display: flex;
align-items: center;
gap: 0;
}
.timeline .dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--border);
border: 2px solid var(--steel-dark);
transition: all 0.3s;
cursor: pointer;
}
.timeline .dot.active {
background: var(--accent);
border-color: var(--accent);
box-shadow: 0 0 8px var(--accent);
}
.timeline .dot.done {
background: var(--accent2);
border-color: var(--accent2);
}
.timeline .line {
width: 20px;
height: 2px;
background: var(--border);
transition: background 0.3s;
}
.timeline .line.done { background: var(--accent2); }
/* 提示文本 */
.hint-text {
font-family: 'Share Tech Mono', monospace;
font-size: 11px;
color: var(--muted);
font-style: italic;
}
@media (max-width: 900px) {
.data-panel { display: none; }
.control-bar { padding: 10px 16px; gap: 8px; }
}
</style>
</head>
<body>
<div class="top-bar">
<div>
<h1>气浮滑块 + 差动螺旋千斤顶</h1>
<div class="subtitle">Air-Floating Slide & Differential Screw Jack — Principle Animation</div>
</div>
<div class="ifr-badge">IFR 最终理想解</div>
</div>
<div class="main-wrap">
<div class="scene-container">
<svg id="scene" viewBox="0 0 1100 680" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- 背景网格 -->
<pattern id="grid" width="30" height="30" patternUnits="userSpaceOnUse">
<path d="M 30 0 L 0 0 0 30" fill="none" stroke="rgba(30,42,64,0.4)" stroke-width="0.5"/>
</pattern>
<!-- 气膜辉光 -->
<filter id="airGlow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="6" result="blur"/>
<feComposite in="SourceGraphic" in2="blur" operator="over"/>
</filter>
<filter id="airGlowStrong" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="12" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<!-- 金属渐变 -->
<linearGradient id="brassGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#e0c97f"/>
<stop offset="50%" stop-color="#c9a54e"/>
<stop offset="100%" stop-color="#a07e30"/>
</linearGradient>
<linearGradient id="steelGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#8fa4b8"/>
<stop offset="100%" stop-color="#5a7088"/>
</linearGradient>
<linearGradient id="fixtureGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6b7d92"/>
<stop offset="100%" stop-color="#4a5d73"/>
</linearGradient>
<linearGradient id="groundGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#1e2a40"/>
<stop offset="100%" stop-color="#141c2e"/>
</linearGradient>
<!-- 气膜粒子渐变 -->
<radialGradient id="particleGrad">
<stop offset="0%" stop-color="#00e5ff" stop-opacity="0.8"/>
<stop offset="100%" stop-color="#00e5ff" stop-opacity="0"/>
</radialGradient>
<!-- 螺纹图案 -->
<pattern id="threadPattern" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(60)">
<line x1="0" y1="0" x2="0" y2="8" stroke="#a07e30" stroke-width="1.5" opacity="0.6"/>
</pattern>
<!-- 风机叶片 -->
<clipPath id="fanClip">
<circle cx="0" cy="0" r="16"/>
</clipPath>
</defs>
<!-- 背景网格 -->
<rect width="1100" height="680" fill="var(--bg)"/>
<rect width="1100" height="680" fill="url(#grid)"/>
<!-- 地面 -->
<rect x="0" y="530" width="1100" height="150" fill="url(#groundGrad)"/>
<line x1="0" y1="530" x2="1100" y2="530" stroke="#2a3a54" stroke-width="2"/>
<!-- 地面纹理线 -->
<g opacity="0.15">
<line x1="0" y1="560" x2="1100" y2="560" stroke="#3a4a64" stroke-width="0.5" stroke-dasharray="4,8"/>
<line x1="0" y1="590" x2="1100" y2="590" stroke="#3a4a64" stroke-width="0.5" stroke-dasharray="4,8"/>
<line x1="0" y1="620" x2="1100" y2="620" stroke="#3a4a64" stroke-width="0.5" stroke-dasharray="4,8"/>
</g>
<text x="550" y="655" text-anchor="middle" fill="#2a3a54" font-family="'Share Tech Mono', monospace" font-size="11" letter-spacing="3">HIGH-PRECISION FLAT FLOOR · SEAMLESS SURFACE</text>
<!-- 气膜效果组 -->
<g id="airFilmGroup" opacity="0">
<!-- 气膜辉光条 -->
<rect id="airGlow1" x="195" y="524" width="90" height="8" rx="4" fill="#00e5ff" opacity="0.5" filter="url(#airGlowStrong)"/>
<rect id="airGlow2" x="345" y="524" width="90" height="8" rx="4" fill="#00e5ff" opacity="0.5" filter="url(#airGlowStrong)"/>
<rect id="airGlow3" x="495" y="524" width="90" height="8" rx="4" fill="#00e5ff" opacity="0.5" filter="url(#airGlowStrong)"/>
<rect id="airGlow4" x="645" y="524" width="90" height="8" rx="4" fill="#00e5ff" opacity="0.5" filter="url(#airGlowStrong)"/>
<!-- 气膜间隙亮线 -->
<line x1="195" y1="528" x2="285" y2="528" stroke="#00e5ff" stroke-width="1.5" opacity="0.9"/>
<line x1="345" y1="528" x2="435" y2="528" stroke="#00e5ff" stroke-width="1.5" opacity="0.9"/>
<line x1="495" y1="528" x2="585" y2="528" stroke="#00e5ff" stroke-width="1.5" opacity="0.9"/>
<line x1="645" y1="528" x2="735" y2="528" stroke="#00e5ff" stroke-width="1.5" opacity="0.9"/>
<!-- 气膜厚度标注 -->
<g id="airGapAnnotation" opacity="0">
<line x1="780" y1="524" x2="780" y2="532" stroke="#00e5ff" stroke-width="0.8" stroke-dasharray="2,2"/>
<line x1="775" y1="524" x2="785" y2="524" stroke="#00e5ff" stroke-width="0.8"/>
<line x1="775" y1="532" x2="785" y2="532" stroke="#00e5ff" stroke-width="0.8"/>
<text x="795" y="530" fill="#00e5ff" font-family="'Share Tech Mono', monospace" font-size="10" id="airGapLabel">0.03mm</text>
</g>
</g>
<!-- 气膜粒子容器 -->
<g id="particleContainer"></g>
<!-- ========== 车体主组 ========== -->
<g id="cartGroup">
<!-- 底盘基板 -->
<rect x="180" y="470" width="560" height="40" rx="3" fill="url(#brassGrad)" stroke="#a07e30" stroke-width="1"/>
<!-- 底盘加强筋 -->
<rect x="200" y="478" width="520" height="4" rx="1" fill="rgba(160,126,48,0.4)"/>
<rect x="200" y="496" width="520" height="4" rx="1" fill="rgba(160,126,48,0.3)"/>
<!-- 气浮垫 ×4 -->
<g>
<rect x="200" y="510" width="80" height="18" rx="3" fill="#3a5068" stroke="#4a6078" stroke-width="1"/>
<rect x="205" y="512" width="70" height="5" rx="2" fill="#2a3e54" opacity="0.6"/>
<text x="240" y="528" text-anchor="middle" fill="#5a7a98" font-family="'Share Tech Mono', monospace" font-size="7">PAD</text>
</g>
<g>
<rect x="350" y="510" width="80" height="18" rx="3" fill="#3a5068" stroke="#4a6078" stroke-width="1"/>
<rect x="355" y="512" width="70" height="5" rx="2" fill="#2a3e54" opacity="0.6"/>
<text x="390" y="528" text-anchor="middle" fill="#5a7a98" font-family="'Share Tech Mono', monospace" font-size="7">PAD</text>
</g>
<g>
<rect x="500" y="510" width="80" height="18" rx="3" fill="#3a5068" stroke="#4a6078" stroke-width="1"/>
<rect x="505" y="512" width="70" height="5" rx="2" fill="#2a3e54" opacity="0.6"/>
<text x="540" y="528" text-anchor="middle" fill="#5a7a98" font-family="'Share Tech Mono', monospace" font-size="7">PAD</text>
</g>
<g>
<rect x="650" y="510" width="80" height="18" rx="3" fill="#3a5068" stroke="#4a6078" stroke-width="1"/>
<rect x="655" y="512" width="70" height="5" rx="2" fill="#2a3e54" opacity="0.6"/>
<text x="690" y="528" text-anchor="middle" fill="#5a7a98" font-family="'Share Tech Mono', monospace" font-size="7">PAD</text>
</g>
<!-- 风机 ×3 -->
<g id="fan1" transform="translate(260,455)">
<circle cx="0" cy="0" r="17" fill="#1a2a3e" stroke="#3a5068" stroke-width="1"/>
<g id="fanBlade1" clip-path="url(#fanClip)">
<rect x="-2" y="-16" width="4" height="32" rx="1" fill="#5a7a98"/>
<rect x="-16" y="-2" width="32" height="4" rx="1" fill="#5a7a98"/>
</g>
<circle cx="0" cy="0" r="4" fill="#2a3e54" stroke="#4a6078" stroke-width="0.5"/>
</g>
<g id="fan2" transform="translate(460,455)">
<circle cx="0" cy="0" r="17" fill="#1a2a3e" stroke="#3a5068" stroke-width="1"/>
<g id="fanBlade2" clip-path="url(#fanClip)">
<rect x="-2" y="-16" width="4" height="32" rx="1" fill="#5a7a98"/>
<rect x="-16" y="-2" width="32" height="4" rx="1" fill="#5a7a98"/>
</g>
<circle cx="0" cy="0" r="4" fill="#2a3e54" stroke="#4a6078" stroke-width="0.5"/>
</g>
<g id="fan3" transform="translate(660,455)">
<circle cx="0" cy="0" r="17" fill="#1a2a3e" stroke="#3a5068" stroke-width="1"/>
<g id="fanBlade3" clip-path="url(#fanClip)">
<rect x="-2" y="-16" width="4" height="32" rx="1" fill="#5a7a98"/>
<rect x="-16" y="-2" width="32" height="4" rx="1" fill="#5a7a98"/>
</g>
<circle cx="0" cy="0" r="4" fill="#2a3e54" stroke="#4a6078" stroke-width="0.5"/>
</g>
<!-- 风机气流指示箭头 -->
<g id="fanArrows" opacity="0">
<path d="M260,438 L260,418 M254,424 L260,414 L266,424" stroke="#00e5ff" stroke-width="1.5" fill="none" opacity="0.6"/>
<path d="M460,438 L460,418 M454,424 L460,414 L466,424" stroke="#00e5ff" stroke-width="1.5" fill="none" opacity="0.6"/>
<path d="M660,438 L660,418 M654,424 L660,414 L666,424" stroke="#00e5ff" stroke-width="1.5" fill="none" opacity="0.6"/>
</g>
<!-- 差动螺旋千斤顶外壳 -->
<g id="jackHousing">
<rect x="430" y="260" width="60" height="210" rx="3" fill="url(#steelGrad)" stroke="#5a7088" stroke-width="1"/>
<!-- 外壳细节线 -->
<line x1="435" y1="270" x2="485" y2="270" stroke="#8fa4b8" stroke-width="0.5" opacity="0.3"/>
<line x1="435" y1="460" x2="485" y2="460" stroke="#8fa4b8" stroke-width="0.5" opacity="0.3"/>
<!-- 螺纹可见区域 -->
<rect id="threadArea" x="440" y="265" width="40" height="195" fill="url(#threadPattern)" opacity="0.4" clip-path="url(#jackClip)"/>
<!-- 差动螺旋标注 -->
<text x="460" y="375" text-anchor="middle" fill="#8fa4b8" font-family="'Share Tech Mono', monospace" font-size="8" opacity="0.6" transform="rotate(-90,460,375)">DIFFERENTIAL SCREW</text>
</g>
<!-- 螺旋顶杆 -->
<g id="screwRod">
<rect x="448" y="240" width="24" height="30" rx="2" fill="#8fa4b8" stroke="#6b8aa0" stroke-width="0.8"/>
<!-- 顶杆螺纹 -->
<line x1="450" y1="245" x2="470" y2="245" stroke="#a0b8cc" stroke-width="0.5" opacity="0.5"/>
<line x1="450" y1="250" x2="470" y2="250" stroke="#a0b8cc" stroke-width="0.5" opacity="0.5"/>
<line x1="450" y1="255" x2="470" y2="255" stroke="#a0b8cc" stroke-width="0.5" opacity="0.5"/>
<line x1="450" y1="260" x2="470" y2="260" stroke="#a0b8cc" stroke-width="0.5" opacity="0.5"/>
<line x1="450" y1="265" x2="470" y2="265" stroke="#a0b8cc" stroke-width="0.5" opacity="0.5"/>
</g>
<!-- 手电钻 -->
<g id="handDrill" transform="translate(490, 340)">
<rect x="0" y="-12" width="50" height="24" rx="4" fill="#e05050" stroke="#c03030" stroke-width="1"/>
<rect x="48" y="-6" width="12" height="12" rx="2" fill="#c03030"/>
<circle cx="14" cy="0" r="8" fill="#b02828" stroke="#901818" stroke-width="0.8"/>
<circle cx="14" cy="0" r="3" fill="#801818"/>
<!-- 钻头夹持器 -->
<rect x="-8" y="-4" width="8" height="8" rx="1" fill="#888" stroke="#666" stroke-width="0.5"/>
<text x="25" y="3" text-anchor="middle" fill="#ffaaaa" font-family="'Share Tech Mono', monospace" font-size="7">DRILL</text>
</g>
<!-- 升降平台 -->
<g id="liftPlatform">
<rect x="260" y="228" width="400" height="16" rx="2" fill="url(#brassGrad)" stroke="#a07e30" stroke-width="1"/>
<!-- 平台加强筋 -->
<line x1="280" y1="236" x2="640" y2="236" stroke="rgba(160,126,48,0.3)" stroke-width="1"/>
</g>
<!-- 夹具/模具 -->
<g id="fixtureGroup">
<rect x="290" y="120" width="340" height="108" rx="4" fill="url(#fixtureGrad)" stroke="#5a7088" stroke-width="1.5"/>
<!-- 夹具细节 -->
<rect x="300" y="130" width="320" height="88" rx="2" fill="none" stroke="#6b7d92" stroke-width="0.5" stroke-dasharray="4,4" opacity="0.4"/>
<!-- T型槽 -->
<rect x="340" y="140" width="60" height="8" rx="1" fill="#3a5068"/>
<rect x="420" y="140" width="60" height="8" rx="1" fill="#3a5068"/>
<rect x="500" y="140" width="60" height="8" rx="1" fill="#3a5068"/>
<!-- 重量标注 -->
<text x="460" y="190" text-anchor="middle" fill="#8fa4b8" font-family="'Chakra Petch', sans-serif" font-size="28" font-weight="700">500</text>
<text x="460" y="210" text-anchor="middle" fill="#5a7a98" font-family="'Share Tech Mono', monospace" font-size="12">KG</text>
<!-- 吊装孔 -->
<circle cx="320" cy="155" r="6" fill="#2a3e54" stroke="#4a6078" stroke-width="0.8"/>
<circle cx="600" cy="155" r="6" fill="#2a3e54" stroke="#4a6078" stroke-width="0.8"/>
</g>
</g>
<!-- ========== 手指推力组 ========== -->
<g id="fingerGroup" opacity="0">
<!-- 手指形状 -->
<g transform="translate(148, 430)">
<!-- 手掌 -->
<rect x="-40" y="10" width="48" height="50" rx="10" fill="#e8b89a" stroke="#c4937a" stroke-width="1"/>
<!-- 食指 -->
<rect x="-8" y="-50" width="16" height="65" rx="8" fill="#e8b89a" stroke="#c4937a" stroke-width="1"/>
<!-- 指甲 -->
<ellipse cx="0" cy="-46" rx="6" ry="5" fill="#f5d0c0" stroke="#c4937a" stroke-width="0.5"/>
<!-- 推力箭头 -->
<g id="pushArrow">
<line x1="10" y1="-30" x2="35" y2="-30" stroke="#ff5252" stroke-width="2.5" stroke-linecap="round"/>
<polygon points="35,-36 47,-30 35,-24" fill="#ff5252"/>
</g>
<!-- 力值标注 -->
<text id="pushForceLabel" x="30" y="-42" fill="#ff5252" font-family="'Share Tech Mono', monospace" font-size="11" font-weight="700">~0.5N</text>
</g>
</g>
<!-- ========== 标注与说明 ========== -->
<g id="annotations">
<!-- 自锁状态标注 -->
<g id="lockAnnotation" opacity="0">
<rect x="545" y="285" width="120" height="36" rx="4" fill="rgba(0,230,118,0.1)" stroke="rgba(0,230,118,0.3)" stroke-width="1"/>
<text x="605" y="300" text-anchor="middle" fill="#00e676" font-family="'Chakra Petch', sans-serif" font-size="11" font-weight="700">SELF-LOCKED</text>
<text x="605" y="314" text-anchor="middle" fill="rgba(0,230,118,0.6)" font-family="'Share Tech Mono', monospace" font-size="8">MECHANICAL SAFETY</text>
<!-- 连线 -->
<line x1="545" y1="303" x2="492" y2="303" stroke="rgba(0,230,118,0.3)" stroke-width="0.8" stroke-dasharray="3,3"/>
</g>
<!-- 气浮原理标注 -->
<g id="airFloatAnnotation" opacity="0">
<rect x="20" y="395" width="140" height="50" rx="4" fill="rgba(0,229,255,0.08)" stroke="rgba(0,229,255,0.2)" stroke-width="1"/>
<text x="90" y="414" text-anchor="middle" fill="#00e5ff" font-family="'Chakra Petch', sans-serif" font-size="11" font-weight="600">AIR-FILM ACTIVE</text>
<text x="90" y="430" text-anchor="middle" fill="rgba(0,229,255,0.5)" font-family="'Share Tech Mono', monospace" font-size="9">μ < 0.001</text>
<!-- 连线 -->
<line x1="160" y1="420" x2="195" y2="524" stroke="rgba(0,229,255,0.2)" stroke-width="0.8" stroke-dasharray="3,3"/>
</g>
<!-- 风机功率标注 -->
<g id="fanPowerAnnotation" opacity="0">
<text x="460" y="405" text-anchor="middle" fill="rgba(0,229,255,0.5)" font-family="'Share Tech Mono', monospace" font-size="9">500W BLOWER</text>
</g>
</g>
<!-- 目标位置标记 -->
<g id="targetMarker" opacity="0">
<line x1="830" y1="480" x2="830" y2="530" stroke="#00e676" stroke-width="1.5" stroke-dasharray="4,3"/>
<polygon points="824,480 836,480 830,470" fill="#00e676" opacity="0.7"/>
<text x="830" y="555" text-anchor="middle" fill="#00e676" font-family="'Share Tech Mono', monospace" font-size="10" opacity="0.7">TARGET</text>
</g>
<!-- 阶段大标题 -->
<g id="phaseTitle" opacity="0">
<text id="phaseTitleText" x="550" y="60" text-anchor="middle" fill="rgba(0,229,255,0.15)" font-family="'Chakra Petch', sans-serif" font-size="42" font-weight="700" letter-spacing="6"></text>
</g>
<!-- 交互提示 -->
<g id="dragHint" opacity="0">
<text x="460" y="580" text-anchor="middle" fill="rgba(0,229,255,0.4)" font-family="'Share Tech Mono', monospace" font-size="10">← DRAG CART TO PUSH →</text>
</g>
</svg>
</div>
<!-- 右侧数据面板 -->
<div class="data-panel">
<h2>参数监控</h2>
<div class="data-item">
<span class="label">总载荷</span>
<span class="value">500<span class="unit">kg</span></span>
</div>
<div class="data-item">
<span class="label">摩擦系数 μ</span>
<span class="value" id="muValue">0.150</span>
<div class="force-bar-wrap">
<div class="force-bar" id="muBar" style="width:100%;background:var(--danger);"></div>
</div>
</div>
<div class="data-item">
<span class="label">所需推力</span>
<span class="value" id="forceValue">735<span class="unit">N</span></span>
<div class="force-bar-wrap">
<div class="force-bar" id="forceBar" style="width:100%;background:var(--danger);"></div>
</div>
</div>
<div class="data-item">
<span class="label">气膜厚度</span>
<span class="value accent" id="gapValue">0<span class="unit">mm</span></span>
</div>
<div class="data-item">
<span class="label">风机功率</span>
<span class="value" id="fanPowerValue">0<span class="unit">W</span></span>
</div>
<div class="data-item">
<span class="label">螺旋自锁</span>
<div>
<span class="lock-badge inactive" id="lockBadge">INACTIVE</span>
</div>
</div>
<div class="phase-indicator">
<div class="phase-name" id="phaseNameDisplay">待命</div>
<div class="phase-desc" id="phaseDescDisplay">点击播放按钮启动动画序列</div>
</div>
</div>
</div>
<!-- 底部控制栏 -->
<div class="control-bar">
<button class="ctrl-btn primary" id="btnPlay">播放序列</button>
<button class="ctrl-btn" id="btnPause" disabled>暂停</button>
<button class="ctrl-btn" id="btnReset">重置</button>
<div style="width:1px;height:28px;background:var(--border);margin:0 4px;"></div>
<button class="ctrl-btn" id="btnPrev" disabled>上一步</button>
<button class="ctrl-btn" id="btnNext">下一步</button>
<div class="slider-group">
<label>气膜厚度</label>
<input type="range" id="gapSlider" min="10" max="50" value="30" step="1"/>
<span class="slider-val" id="gapSliderVal">0.030mm</span>
</div>
<div class="timeline" id="timeline">
<!-- 由 JS 动态生成 -->
</div>
<span class="hint-text" id="hintText"></span>
</div>
<script>
(function() {
'use strict';
// ========== 阶段定义 ==========
const PHASES = [
{ id: 'idle', name: '待命', desc: '夹具放置于换模车上,等待操作', duration: 1200 },
{ id: 'lift', name: '升起夹具', desc: '手电钻驱动差动螺旋千斤顶,一人轻松摇起500kg夹具,螺纹自锁确保安全', duration: 3500 },
{ id: 'approach', name: '推至目标', desc: '将换模车推行至目标位置附近', duration: 2500 },
{ id: 'airfilm', name: '气膜启动', desc: '底盘风机启动,高压气膜形成,摩擦系数骤降至0.001以下', duration: 3000 },
{ id: 'push', name: '一指推送', desc: '单根手指即可推动500kg换模车精确对位——IFR最终理想解', duration: 3500 },
{ id: 'land', name: '刚性着地', desc: '风机关闭,气膜消散,底盘刚性接触地面,稳固定位', duration: 2500 },
{ id: 'lower', name: '降下夹具', desc: '反向驱动差动螺旋,夹具缓慢降落至安装位,完成换模', duration: 3000 },
{ id: 'complete', name: '换模完成', desc: '全部操作完成,安全可靠', duration: 2000 }
];
// ========== 状态变量 ==========
let currentPhase = 0;
let phaseProgress = 0;
let isPlaying = false;
let isPaused = false;
let lastTimestamp = 0;
let animFrameId = null;
// 动画插值状态
let fixtureLift = 0; // 0~1 夹具升起程度
let cartOffsetX = 0; // 车体水平偏移
let airFilmOpacity = 0; // 气膜可见度 0~1
let fanSpeed = 0; // 风机转速 0~1
let fingerOpacity = 0; // 手指可见度 0~1
let lockActive = false; // 自锁状态
let mu = 0.15; // 摩擦系数
let pushForce = 735; // 推力 N
let fanPower = 0; // 风机功率
let airGapMM = 0; // 气膜厚度 mm
let targetOpacity = 0; // 目标标记可见度
// 风机旋转角度
let fanAngle = 0;
// 粒子系统
let particles = [];
const MAX_PARTICLES = 60;
// 拖拽交互
let isDragging = false;
let dragStartX = 0;
let dragCartStartX = 0;
// DOM 引用
const svg = document.getElementById('scene');
const cartGroup = document.getElementById('cartGroup');
const fixtureGroup = document.getElementById('fixtureGroup');
const liftPlatform = document.getElementById('liftPlatform');
const screwRod = document.getElementById('screwRod');
const airFilmGroup = document.getElementById('airFilmGroup');
const fingerGroup = document.getElementById('fingerGroup');
const lockAnnotation = document.getElementById('lockAnnotation');
const airFloatAnnotation = document.getElementById('airFloatAnnotation');
const fanPowerAnnotation = document.getElementById('fanPowerAnnotation');
const targetMarker = document.getElementById('targetMarker');
const phaseTitle = document.getElementById('phaseTitle');
const phaseTitleText = document.getElementById('phaseTitleText');
const dragHint = document.getElementById('dragHint');
const fanArrows = document.getElementById('fanArrows');
const airGapAnnotation = document.getElementById('airGapAnnotation');
const particleContainer = document.getElementById('particleContainer');
const fanBlade1 = document.getElementById('fanBlade1');
const fanBlade2 = document.getElementById('fanBlade2');
const fanBlade3 = document.getElementById('fanBlade3');
// 数据面板
const muValueEl = document.getElementById('muValue');
const muBarEl = document.getElementById('muBar');
const forceValueEl = document.getElementById('forceValue');
const forceBarEl = document.getElementById('forceBar');
const gapValueEl = document.getElementById('gapValue');
const fanPowerValueEl = document.getElementById('fanPowerValue');
const lockBadgeEl = document.getElementById('lockBadge');
const phaseNameDisplay = document.getElementById('phaseNameDisplay');
const phaseDescDisplay = document.getElementById('phaseDescDisplay');
// 控件
const btnPlay = document.getElementById('btnPlay');
const btnPause = document.getElementById('btnPause');
const btnReset = document.getElementById('btnReset');
const btnPrev = document.getElementById('btnPrev');
const btnNext = document.getElementById('btnNext');
const gapSlider = document.getElementById('gapSlider');
const gapSliderVal = document.getElementById('gapSliderVal');
const hintText = document.getElementById('hintText');
// ========== 初始化时间线 ==========
function initTimeline() {
const tl = document.getElementById('timeline');
tl.innerHTML = '';
PHASES.forEach((p, i) => {
if (i > 0) {
const line = document.createElement('div');
line.className = 'line';
line.id = 'tl-line-' + i;
tl.appendChild(line);
}
const dot = document.createElement('div');
dot.className = 'dot';
dot.id = 'tl-dot-' + i;
dot.title = p.name;
dot.addEventListener('click', () => jumpToPhase(i));
tl.appendChild(dot);
});
updateTimeline();
}
function updateTimeline() {
PHASES.forEach((_, i) => {
const dot = document.getElementById('tl-dot-' + i);
const line = document.getElementById('tl-line-' + i);
dot.className = 'dot' + (i === currentPhase ? ' active' : (i < currentPhase ? ' done' : ''));
if (line) line.className = 'line' + (i <= currentPhase ? ' done' : '');
});
}
// ========== 缓动函数 ==========
function easeInOut(t) {
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
}
function easeOut(t) {
return 1 - Math.pow(1 - t, 3);
}
function easeIn(t) {
return t * t * t;
}
// ========== 插值 ==========
function lerp(a, b, t) {
return a + (b - a) * t;
}
// ========== 阶段动画逻辑 ==========
// 每个阶段定义起始和目标状态,根据 progress 插值
function getPhaseTargets(phaseId) {
switch(phaseId) {
case 'idle':
return { fixtureLift: 0, cartOffsetX: 0, airFilmOpacity: 0, fanSpeed: 0, fingerOpacity: 0, lockActive: false, mu: 0.15, pushForce: 735, fanPower: 0, airGapMM: 0, targetOpacity: 0 };
case 'lift':
return { fixtureLift: 1, cartOffsetX: 0, airFilmOpacity: 0, fanSpeed: 0, fingerOpacity: 0, lockActive: true, mu: 0.15, pushForce: 735, fanPower: 0, airGapMM: 0, targetOpacity: 0 };
case 'approach':
return { fixtureLift: 1, cartOffsetX: 120, airFilmOpacity: 0, fanSpeed: 0, fingerOpacity: 0, lockActive: true, mu: 0.15, pushForce: 735, fanPower: 0, airGapMM: 0, targetOpacity: 1 };
case 'airfilm':
return { fixtureLift: 1, cartOffsetX: 120, airFilmOpacity: 1, fanSpeed: 1, fingerOpacity: 0, lockActive: true, mu: 0.001, pushForce: 0.5, fanPower: 500, airGapMM: parseFloat(gapSlider.value) / 1000, targetOpacity: 1 };
case 'push':
return { fixtureLift: 1, cartOffsetX: 250, airFilmOpacity: 1, fanSpeed: 1, fingerOpacity: 1, lockActive: true, mu: 0.001, pushForce: 0.5, fanPower: 500, airGapMM: parseFloat(gapSlider.value) / 1000, targetOpacity: 1 };
case 'land':
return { fixtureLift: 1, cartOffsetX: 250, airFilmOpacity: 0, fanSpeed: 0, fingerOpacity: 0, lockActive: true, mu: 0.15, pushForce: 735, fanPower: 0, airGapMM: 0, targetOpacity: 0 };
case 'lower':
return { fixtureLift: 0, cartOffsetX: 250, airFilmOpacity: 0, fanSpeed: 0, fingerOpacity: 0, lockActive: false, mu: 0.15, pushForce: 735, fanPower: 0, airGapMM: 0, targetOpacity: 0 };
case 'complete':
return { fixtureLift: 0, cartOffsetX: 250, airFilmOpacity: 0, fanSpeed: 0, fingerOpacity: 0, lockActive: false, mu: 0.15, pushForce: 735, fanPower: 0, airGapMM: 0, targetOpacity: 0 };
default:
return getPhaseTargets('idle');
}
}
// 获取上一阶段的终态
function getPrevTargets() {
if (currentPhase === 0) return getPhaseTargets('idle');
return getPhaseTargets(PHASES[currentPhase - 1].id);
}
function getCurrentTargets() {
return getPhaseTargets(PHASES[currentPhase].id);
}
// ========== 渲染 ==========
function render() {
const t = easeInOut(phaseProgress);
const prev = getPrevTargets();
const curr = getCurrentTargets();
// 插值当前状态
fixtureLift = lerp(prev.fixtureLift, curr.fixtureLift, t);
cartOffsetX = lerp(prev.cartOffsetX, curr.cartOffsetX, t);
airFilmOpacity = lerp(prev.airFilmOpacity, curr.airFilmOpacity, t);
fanSpeed = lerp(prev.fanSpeed, curr.fanSpeed, t);
fingerOpacity = lerp(prev.fingerOpacity, curr.fingerOpacity, t);
mu = lerp(prev.mu, curr.mu, t);
pushForce = lerp(prev.pushForce, curr.pushForce, t);
fanPower = lerp(prev.fanPower, curr.fanPower, t);
airGapMM = lerp(prev.airGapMM, curr.airGapMM, t);
targetOpacity = lerp(prev.targetOpacity, curr.targetOpacity, t);
// 自锁状态(立即切换)
lockActive = curr.lockActive;
// 车体水平移动
cartGroup.setAttribute('transform', `translate(${cartOffsetX}, 0)`);
// 夹具升起(向上移动)
const liftAmount = fixtureLift * 80;
fixtureGroup.setAttribute('transform', `translate(${cartOffsetX}, ${-liftAmount})`);
liftPlatform.setAttribute('transform', `translate(${cartOffsetX}, ${-liftAmount})`);
screwRod.setAttribute('transform', `translate(${cartOffsetX}, ${-liftAmount * 0.5})`);
// 气膜效果
airFilmGroup.setAttribute('opacity', airFilmOpacity);
// 气膜辉光脉冲
if (airFilmOpacity > 0.1) {
const pulse = 0.5 + 0.3 * Math.sin(Date.now() * 0.005);
const glowEls = airFilmGroup.querySelectorAll('rect[id^="airGlow"]');
glowEls.forEach(el => el.setAttribute('opacity', pulse * airFilmOpacity));
}
// 气膜厚度标注
airGapAnnotation.setAttribute('opacity', airFilmOpacity > 0.5 ? (airFilmOpacity - 0.5) * 2 : 0);
// 风机旋转
if (fanSpeed > 0.01) {
fanAngle += fanSpeed * 15;
}
fanBlade1.setAttribute('transform', `rotate(${fanAngle})`);
fanBlade2.setAttribute('transform', `rotate(${fanAngle * 1.1})`);
fanBlade3.setAttribute('transform', `rotate(${fanAngle * 0.95})`);
fanArrows.setAttribute('opacity', fanSpeed * 0.8);
// 手指
fingerGroup.setAttribute('opacity', fingerOpacity);
// 手指位置跟随车体
const fingerX = 148 + cartOffsetX;
fingerGroup.querySelector('g').setAttribute('transform', `translate(${fingerX}, 430)`);
// 标注
lockAnnotation.setAttribute('opacity', lockActive ? 1 : 0);
lockAnnotation.querySelector('g, rect')?.setAttribute('transform', `translate(${cartOffsetX}, 0)`);
// 重新定位锁定标注
const lockRect = lockAnnotation.querySelector('rect');
const lockText1 = lockAnnotation.querySelectorAll('text')[0];
const lockText2 = lockAnnotation.querySelectorAll('text')[1];
const lockLine = lockAnnotation.querySelector('line');
if (lockRect) {
lockRect.setAttribute('x', 545 + cartOffsetX);
lockRect.setAttribute('y', 285 - liftAmount * 0.3);
}
if (lockText1) { lockText1.setAttribute('x', 605 + cartOffsetX); lockText1.setAttribute('y', 300 - liftAmount * 0.3); }
if (lockText2) { lockText2.setAttribute('x', 605 + cartOffsetX); lockText2.setAttribute('y', 314 - liftAmount * 0.3); }
if (lockLine) { lockLine.setAttribute('x1', 545 + cartOffsetX); lockLine.setAttribute('y1', 303 - liftAmount * 0.3); lockLine.setAttribute('x2', 492 + cartOffsetX); lockLine.setAttribute('y2', 303 - liftAmount * 0.3); }
airFloatAnnotation.setAttribute('opacity', airFilmOpacity > 0.3 ? Math.min(1, (airFilmOpacity - 0.3) * 2) : 0);
fanPowerAnnotation.setAttribute('opacity', fanSpeed > 0.3 ? Math.min(1, (fanSpeed - 0.3) * 2) : 0);
if (fanPowerAnnotation.querySelector('text')) {
fanPowerAnnotation.querySelector('text').setAttribute('x', 460 + cartOffsetX);
}
// 目标标记
targetMarker.setAttribute('opacity', targetOpacity);
// 阶段标题
const phaseObj = PHASES[currentPhase];
if (phaseProgress < 0.3 && isPlaying) {
phaseTitle.setAttribute('opacity', 1 - phaseProgress / 0.3);
phaseTitleText.textContent = phaseObj.name;
} else {
phaseTitle.setAttribute('opacity', 0);
}
// 拖拽提示
const showDrag = (PHASES[currentPhase].id === 'push' && isPlaying);
dragHint.setAttribute('opacity', showDrag ? 0.6 : 0);
if (showDrag) {
dragHint.querySelector('text').setAttribute('x', 460 + cartOffsetX);
}
// 粒子系统
updateParticles();
// 数据面板更新
updateDataPanel();
}
// ========== 粒子系统 ==========
function updateParticles() {
if (airFilmOpacity > 0.2 && fanSpeed > 0.3) {
// 生成新粒子
const spawnRate = airFilmOpacity * fanSpeed * 3;
if (Math.random() < spawnRate && particles.length < MAX_PARTICLES) {
const padIndex = Math.floor(Math.random() * 4);
const padCenters = [240, 390, 540, 690];
const px = padCenters[padIndex] + cartOffsetX + (Math.random() - 0.5) * 40;
const py = 528;
const dir = Math.random() > 0.5 ? 1 : -1;
particles.push({
x: px,
y: py,
vx: dir * (1 + Math.random() * 2) * fanSpeed,
vy: (Math.random() - 0.6) * 0.5,
life: 1,
decay: 0.01 + Math.random() * 0.02,
size: 2 + Math.random() * 3
});
}
}
// 更新粒子
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.life -= p.decay;
if (p.life <= 0) {
particles.splice(i, 1);
}
}
// 渲染粒子
while (particleContainer.children.length > particles.length) {
particleContainer.removeChild(particleContainer.lastChild);
}
while (particleContainer.children.length < particles.length) {
const c = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
c.setAttribute('fill', '#00e5ff');
particleContainer.appendChild(c);
}
particles.forEach((p, i) => {
const el = particleContainer.children[i];
el.setAttribute('cx', p.x);
el.setAttribute('cy', p.y);
el.setAttribute('r', p.size * p.life * airFilmOpacity);
el.setAttribute('opacity', p.life * 0.6 * airFilmOpacity);
});
}
// ========== 数据面板 ==========
function updateDataPanel() {
muValueEl.textContent = mu < 0.01 ? mu.toFixed(3) : mu.toFixed(3);
muValueEl.className = 'value' + (mu < 0.01 ? ' accent' : ' danger');
const muPct = Math.min(100, (mu / 0.15) * 100);
muBarEl.style.width = muPct + '%';
muBarEl.style.background = mu < 0.01 ? 'var(--accent)' : 'var(--danger)';
forceValueEl.innerHTML = pushForce < 1 ? pushForce.toFixed(1) + '<span class="unit">N</span>' : Math.round(pushForce) + '<span class="unit">N</span>';
forceValueEl.className = 'value' + (pushForce < 1 ? ' accent' : ' danger');
const forcePct = Math.min(100, (pushForce / 735) * 100);
forceBarEl.style.width = Math.max(forcePct, pushForce < 1 ? 1 : 0) + '%';
forceBarEl.style.background = pushForce < 1 ? 'var(--accent)' : 'var(--danger)';
gapValueEl.innerHTML = airGapMM > 0 ? airGapMM.toFixed(3) + '<span class="unit">mm</span>' : '0<span class="unit">mm</span>';
fanPowerValueEl.innerHTML = Math.round(fanPower) + '<span class="unit">W</span>';
lockBadgeEl.textContent = lockActive ? 'ACTIVE' : 'INACTIVE';
lockBadgeEl.className = 'lock-badge ' + (lockActive ? 'active' : 'inactive');
// 气膜厚度标注文字更新
const gapLabel = document.getElementById('airGapLabel');
if (gapLabel) gapLabel.textContent = airGapMM > 0 ? airGapMM.toFixed(3) + 'mm' : '';
}
// ========== 动画主循环 ==========
function animate(timestamp) {
if (!isPlaying || isPaused) {
animFrameId = requestAnimationFrame(animate);
render();
return;
}
if (lastTimestamp === 0) lastTimestamp = timestamp;
const dt = timestamp - lastTimestamp;
lastTimestamp = timestamp;
const phase = PHASES[currentPhase];
phaseProgress += dt / phase.duration;
if (phaseProgress >= 1) {
phaseProgress = 1;
render();
// 进入下一阶段
if (currentPhase < PHASES.length - 1) {
currentPhase++;
phaseProgress = 0;
updateTimeline();
updatePhaseDisplay();
} else {
// 动画完成
isPlaying = false;
isPaused = false;
btnPlay.textContent = '重新播放';
btnPlay.disabled = false;
btnPause.disabled = true;
hintText.textContent = '动画序列已完成';
}
}
render();
animFrameId = requestAnimationFrame(animate);
}
// ========== 控制函数 ==========
function play() {
if (currentPhase >= PHASES.length - 1 && phaseProgress >= 1) {
reset();
}
isPlaying = true;
isPaused = false;
lastTimestamp = 0;
btnPlay.disabled = true;
btnPause.disabled = false;
hintText.textContent = '正在播放动画序列...';
updatePhaseDisplay();
}
function pause() {
isPaused = !isPaused;
btnPause.textContent = isPaused ? '继续' : '暂停';
if (!isPaused) lastTimestamp = 0;
hintText.textContent = isPaused ? '已暂停' : '继续播放...';
}
function reset() {
currentPhase = 0;
phaseProgress = 0;
isPlaying = false;
isPaused = false;
lastTimestamp = 0;
fixtureLift = 0;
cartOffsetX = 0;
airFilmOpacity = 0;
fanSpeed = 0;
fingerOpacity = 0;
lockActive = false;
mu = 0.15;
pushForce = 735;
fanPower = 0;
airGapMM = 0;
targetOpacity = 0;
fanAngle = 0;
particles = [];
btnPlay.textContent = '播放序列';
btnPlay.disabled = false;
btnPause.textContent = '暂停';
btnPause.disabled = true;
hintText.textContent = '';
updateTimeline();
updatePhaseDisplay();
render();
}
function nextPhase() {
if (currentPhase < PHASES.length - 1) {
// 完成当前阶段
phaseProgress = 1;
render();
// 进入下一阶段
currentPhase++;
phaseProgress = 0;
lastTimestamp = 0;
updateTimeline();
updatePhaseDisplay();
if (isPlaying) {
// 继续播放
} else {
// 单步模式:直接展示该阶段起始状态
render();
}
}
}
function prevPhase() {
if (currentPhase > 0) {
currentPhase--;
phaseProgress = 1;
lastTimestamp = 0;
updateTimeline();
updatePhaseDisplay();
render();
}
}
function jumpToPhase(index) {
if (index >= 0 && index < PHASES.length) {
// 先完成之前的所有阶段
currentPhase = index;
phaseProgress = 0;
lastTimestamp = 0;
if (!isPlaying) {
// 在单步模式下,直接显示该阶段完成状态
phaseProgress = 1;
}
updateTimeline();
updatePhaseDisplay();
render();
}
}
function updatePhaseDisplay() {
const phase = PHASES[currentPhase];
phaseNameDisplay.textContent = phase.name;
phaseDescDisplay.textContent = phase.desc;
}
// ========== 拖拽交互 ==========
function getSVGPoint(evt) {
const pt = svg.createSVGPoint();
const clientX = evt.touches ? evt.touches[0].clientX : evt.clientX;
const clientY = evt.touches ? evt.touches[0].clientY : evt.clientY;
pt.x = clientX;
pt.y = clientY;
const svgP = pt.matrixTransform(svg.getScreenCTM().inverse());
return svgP;
}
svg.addEventListener('mousedown', startDrag);
svg.addEventListener('touchstart', startDrag, { passive: false });
function startDrag(evt) {
// 仅在 push 阶段允许拖拽
if (PHASES[currentPhase].id !== 'push' || !isPlaying) return;
const pt = getSVGPoint(evt);
// 检查是否点击在车体区域
if (pt.y > 100 && pt.y < 540 && pt.x > 150 + cartOffsetX && pt.x < 800 + cartOffsetX) {
isDragging = true;
dragStartX = pt.x;
dragCartStartX = cartOffsetX;
evt.preventDefault();
}
}
window.addEventListener('mousemove', onDrag);
window.addEventListener('touchmove', onDrag, { passive: false });
function onDrag(evt) {
if (!isDragging) return;
evt.preventDefault();
const pt = getSVGPoint(evt);
const dx = pt.x - dragStartX;
// 限制拖拽范围
const newOffset = Math.max(120, Math.min(350, dragCartStartX + dx));
cartOffsetX = newOffset;
// 覆盖插值,直接设置
const curr = getCurrentTargets();
curr.cartOffsetX = newOffset;
}
window.addEventListener('mouseup', endDrag);
window.addEventListener('touchend', endDrag);
function endDrag() {
isDragging = false;
}
// ========== 气膜厚度滑块 ==========
gapSlider.addEventListener('input', function() {
const val = parseFloat(this.value) / 1000;
gapSliderVal.textContent = val.toFixed(3) + 'mm';
// 如果当前在气膜阶段,实时更新
if (['airfilm', 'push'].includes(PHASES[currentPhase].id)) {
const curr = getCurrentTargets();
curr.airGapMM = val;
}
});
// ========== 绑定控件 ==========
btnPlay.addEventListener('click', play);
btnPause.addEventListener('click', pause);
btnReset.addEventListener('click', reset);
btnNext.addEventListener('click', nextPhase);
btnPrev.addEventListener('click', prevPhase);
// ========== 键盘控制 ==========
document.addEventListener('keydown', (e) => {
if (e.key === ' ') { e.preventDefault(); isPlaying ? pause() : play(); }
if (e.key === 'ArrowRight') nextPhase();
if (e.key === 'ArrowLeft') prevPhase();
if (e.key === 'r' || e.key === 'R') reset();
});
// ========== 启动 ==========
initTimeline();
updatePhaseDisplay();
render();
animFrameId = requestAnimationFrame(animate);
})();
</script>
</body>
</html>
这是一段完整的高保真 SVG 原理动画代码,实现细节如下:
设计理念与视觉方向
采用"工业蓝图"风格——深色背景搭配精密网格,黄铜色结构件、钢灰色夹具、青色气膜辉光形成三重色调层次。核心创新点(气膜悬浮与差动自锁)通过高对比度的色彩与辉光脉冲强制吸引视线。
IFR 理想解视觉表达
- 消除矛盾:摩擦系数从 0.15 骤降至 0.001 以下,右侧面板实时显示力值从 735N 崩塌至 0.5N,配合红→青的颜色跳变,直观呈现"重物变轻"的理想态
- 资源利用:气膜标注「AIR-FILM ACTIVE · μ < 0.001」与自锁标注「SELF-LOCKED · MECHANICAL SAFETY」分别突出空气(免费资源)和纯机械几何(零能耗)的巧妙利用
- 一指推场景:真实手指图形 + 红色力箭头 + "~0.5N" 标注,将"500kg 一指推"的 IFR 终态视觉化
8 阶段动画时序
待命 → 升起夹具(差动螺旋+自锁亮起)→ 推至目标 → 气膜启动(风机旋转+粒子喷射+辉光扩散)→ 一指推送 → 刚性着地 → 降下夹具 → 完成
交互设计
- 播放/暂停/重置/步进控制 + 底部时间线可点击跳转
- 气膜厚度滑块(0.010–0.050mm)实时调节悬浮间隙
- 在"一指推送"阶段,可直接拖拽车体体验近零摩擦滑移
- 键盘快捷键:空格播放/暂停,←→步进,R 重置
积分规则:第一轮对话扣减6分,后续每轮扣4分
等待动画代码生成...
