独立渲染引擎就绪引擎就绪
这是为您生成的一个展示“行星翻转轮组”越障原理的高保真 SVG 动画网页源码,您可以直接在浏览器中运行和观看。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>行星翻转轮组 - 越障原理动画 | TRIZ 最终理想解</title>
<style>
:root {
--bg: #1a1b20;
--surface: #23242a;
--text: #d4d5db;
--text-secondary: #9597a2;
--accent: #f0a54a;
--accent-glow: #f0a54a88;
--highlight: #ffcc66;
--step: #4e5060;
--step-edge: #7a7d90;
--ground: #3a3b44;
--metal: #8892a0;
--metal-dark: #5a5f6b;
--rubber: #2c2d35;
--cargo: #c9a96e;
--cargo-dark: #8b6f3f;
--danger-zone: #e0554a33;
--success-zone: #4ecf8f22;
--trajectory: #f0a54a66;
--pivot-glow: #ffcc66;
--font-display: 'Georgia', 'Noto Serif SC', 'STSong', 'SimSun', 'Songti SC', serif;
--font-body: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', sans-serif;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: var(--bg);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
font-family: var(--font-body);
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
}
.animation-wrapper {
position: relative;
width: 95vw;
max-width: 1300px;
aspect-ratio: 16/7.5;
min-height: 520px;
background: radial-gradient(ellipse at 50% 40%, #24252d 0%, #18191e 70%);
border-radius: 18px;
overflow: hidden;
box-shadow:
0 2px 30px rgba(0, 0, 0, 0.5),
0 1px 0 rgba(255, 255, 255, 0.03) inset,
0 0 0 1px rgba(255, 255, 255, 0.05);
cursor: default;
}
.animation-wrapper svg {
width: 100%;
height: 100%;
display: block;
}
.overlay-labels {
position: absolute;
pointer-events: none;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 10;
}
.label-tag {
position: absolute;
font-family: var(--font-body);
font-size: 11px;
font-weight: 500;
letter-spacing: 0.03em;
color: var(--text-secondary);
background: rgba(26, 27, 32, 0.75);
padding: 4px 10px;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.08);
white-space: nowrap;
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
transition: color 0.6s, border-color 0.6s, background 0.6s;
}
.label-tag.highlight-active {
color: #f0cc7a;
border-color: rgba(240, 165, 74, 0.5);
background: rgba(30, 28, 22, 0.8);
}
.label-top-left {
top: 14px;
left: 16px;
}
.label-top-right {
top: 14px;
right: 16px;
}
.label-bottom-left {
bottom: 14px;
left: 16px;
}
.label-bottom-right {
bottom: 14px;
right: 16px;
}
.label-center-bottom {
bottom: 10px;
left: 50%;
transform: translateX(-50%);
font-size: 10px;
letter-spacing: 0.05em;
}
.phase-indicator {
position: absolute;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 6px;
z-index: 11;
pointer-events: none;
}
.phase-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.phase-dot.active {
background: var(--accent);
box-shadow: 0 0 10px var(--accent-glow);
width: 9px;
height: 9px;
margin-top: -1px;
}
.phase-dot.done {
background: rgba(240, 165, 74, 0.5);
}
.speed-control {
position: absolute;
bottom: 16px;
right: 80px;
z-index: 20;
display: flex;
align-items: center;
gap: 8px;
pointer-events: all;
opacity: 0.7;
transition: opacity 0.3s;
}
.speed-control:hover {
opacity: 1;
}
.speed-control label {
font-size: 10px;
color: var(--text-secondary);
font-family: var(--font-body);
letter-spacing: 0.04em;
}
.speed-control input[type="range"] {
-webkit-appearance: none;
width: 90px;
height: 4px;
border-radius: 2px;
background: rgba(255, 255, 255, 0.2);
outline: none;
cursor: pointer;
}
.speed-control input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--accent);
cursor: pointer;
border: 2px solid var(--bg);
box-shadow: 0 0 8px rgba(240, 165, 74, 0.4);
}
@keyframes subtlePulse {
0%,
100% {
opacity: 0.6;
}
50% {
opacity: 1;
}
}
.pulse-active {
animation: subtlePulse 1.6s ease-in-out infinite;
}
</style>
</head>
<body>
<div class="animation-wrapper" id="animContainer">
<svg id="mainSvg" viewBox="0 0 1400 560" 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(255,255,255,0.03)" stroke-width="0.5"/>
</pattern>
<!-- 发光滤镜 -->
<filter id="glowPivot" x="-80%" y="-80%" width="260%" height="260%">
<feGaussianBlur stdDeviation="5" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowSoft" x="-40%" y="-40%" width="180%" height="180%">
<feGaussianBlur stdDeviation="3" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<!-- 渐变 -->
<linearGradient id="gradGround" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#4a4b58"/>
<stop offset="100%" stop-color="#2e2f38"/>
</linearGradient>
<linearGradient id="gradStepFace" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#5c5e6e"/>
<stop offset="100%" stop-color="#3e404d"/>
</linearGradient>
<linearGradient id="gradStepTop" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6a6d7c"/>
<stop offset="100%" stop-color="#4e505e"/>
</linearGradient>
<linearGradient id="gradMetal" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#9aa3b0"/>
<stop offset="50%" stop-color="#6e7684"/>
<stop offset="100%" stop-color="#8890a0"/>
</linearGradient>
<linearGradient id="gradCargo" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#dbb878"/>
<stop offset="100%" stop-color="#9e7d44"/>
</linearGradient>
<radialGradient id="gradPivotGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ffd166" stop-opacity="0.9"/>
<stop offset="40%" stop-color="#f0a54a" stop-opacity="0.4"/>
<stop offset="100%" stop-color="#f0a54a" stop-opacity="0"/>
</radialGradient>
<radialGradient id="gradContactFlash" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.8"/>
<stop offset="30%" stop-color="#ffcc66" stop-opacity="0.5"/>
<stop offset="100%" stop-color="#ff8800" stop-opacity="0"/>
</radialGradient>
</defs>
<!-- 背景层 -->
<rect x="0" y="0" width="1400" height="560" fill="url(#grid)" opacity="0.6"/>
<!-- 低处地面 -->
<rect id="groundLow" x="0" y="445" width="740" height="115" fill="url(#gradGround)"/>
<line x1="0" y1="445" x2="740" y2="445" stroke="#7a7d90" stroke-width="2"/>
<!-- 台阶立面 -->
<rect id="stepFace" x="735" y="340" width="20" height="105" fill="url(#gradStepFace)" rx="2"/>
<!-- 台阶顶部边缘高亮 -->
<line x1="733" y1="342" x2="757" y2="342" stroke="#b0b4c0" stroke-width="2.5" stroke-linecap="round"/>
<!-- 台阶顶部地面 -->
<rect id="groundHigh" x="750" y="340" width="650" height="220" fill="url(#gradStepTop)"/>
<line x1="750" y1="340" x2="1400" y2="340" stroke="#8a8d9a" stroke-width="2"/>
<!-- 台阶高度标注虚线 -->
<line x1="770" y1="340" x2="770" y2="445" stroke="rgba(255,255,255,0.18)" stroke-width="1" stroke-dasharray="5,5"/>
<line x1="765" y1="340" x2="775" y2="340" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
<line x1="765" y1="445" x2="775" y2="445" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
<!-- 台阶高度标签 -->
<text x="778" y="396" fill="rgba(255,255,255,0.35)" font-size="11" font-family="var(--font-body)">台阶</text>
<text x="778" y="410" fill="rgba(255,255,255,0.35)" font-size="11" font-family="var(--font-body)">~100mm</text>
<!-- 支点发光指示器(在翻转阶段显示) -->
<circle id="pivotGlow" cx="0" cy="0" r="22" fill="url(#gradPivotGlow)" filter="url(#glowPivot)" opacity="0" pointer-events="none"/>
<circle id="pivotGlowInner" cx="0" cy="0" r="8" fill="#ffd166" opacity="0" pointer-events="none" filter="url(#glowSoft)"/>
<!-- 翻转轨迹弧线(虚线) -->
<path id="flipTrajectory" d="" fill="none" stroke="#f0a54a" stroke-width="2" stroke-dasharray="8,4" opacity="0" pointer-events="none" filter="url(#glowSoft)"/>
<!-- 接触闪光 -->
<circle id="contactFlash" cx="0" cy="0" r="14" fill="url(#gradContactFlash)" opacity="0" pointer-events="none"/>
<!-- === 动态元素组 === -->
<!-- 后轮组 -->
<g id="rearHubGroup">
<!-- 旋转臂 -->
<g id="rearArms">
<line x1="0" y1="0" x2="0" y2="-115" stroke="url(#gradMetal)" stroke-width="6" stroke-linecap="round"/>
<line x1="0" y1="0" x2="99.6" y2="57.5" stroke="url(#gradMetal)" stroke-width="6" stroke-linecap="round"/>
<line x1="0" y1="0" x2="-99.6" y2="57.5" stroke="url(#gradMetal)" stroke-width="6" stroke-linecap="round"/>
</g>
<!-- 中心轴 -->
<circle cx="0" cy="0" r="9" fill="#6e7684" stroke="#9aa3b0" stroke-width="2"/>
<circle cx="0" cy="0" r="4" fill="#3a3d45"/>
<!-- 三个小轮 -->
<circle cx="0" cy="-115" r="40" fill="var(--rubber)" stroke="#444" stroke-width="2.5" class="wheel-rear"/>
<circle cx="99.6" cy="57.5" r="40" fill="var(--rubber)" stroke="#444" stroke-width="2.5" class="wheel-rear"/>
<circle cx="-99.6" cy="57.5" r="40" fill="var(--rubber)" stroke="#444" stroke-width="2.5" class="wheel-rear"/>
<!-- 小轮轮毂标记 -->
<circle cx="0" cy="-115" r="7" fill="#555" stroke="#777" stroke-width="1.5"/>
<circle cx="99.6" cy="57.5" r="7" fill="#555" stroke="#777" stroke-width="1.5"/>
<circle cx="-99.6" cy="57.5" r="7" fill="#555" stroke="#777" stroke-width="1.5"/>
</g>
<!-- 前轮组 -->
<g id="frontHubGroup">
<g id="frontArms">
<line x1="0" y1="0" x2="0" y2="-115" stroke="url(#gradMetal)" stroke-width="6" stroke-linecap="round"/>
<line x1="0" y1="0" x2="99.6" y2="57.5" stroke="url(#gradMetal)" stroke-width="6" stroke-linecap="round"/>
<line x1="0" y1="0" x2="-99.6" y2="57.5" stroke="url(#gradMetal)" stroke-width="6" stroke-linecap="round"/>
</g>
<circle cx="0" cy="0" r="9" fill="#6e7684" stroke="#9aa3b0" stroke-width="2"/>
<circle cx="0" cy="0" r="4" fill="#3a3d45"/>
<circle cx="0" cy="-115" r="40" fill="var(--rubber)" stroke="#444" stroke-width="2.5" class="wheel-front"/>
<circle cx="99.6" cy="57.5" r="40" fill="var(--rubber)" stroke="#444" stroke-width="2.5" class="wheel-front"/>
<circle cx="-99.6" cy="57.5" r="40" fill="var(--rubber)" stroke="#444" stroke-width="2.5" class="wheel-front"/>
<circle cx="0" cy="-115" r="7" fill="#555" stroke="#777" stroke-width="1.5"/>
<circle cx="99.6" cy="57.5" r="7" fill="#555" stroke="#777" stroke-width="1.5"/>
<circle cx="-99.6" cy="57.5" r="7" fill="#555" stroke="#777" stroke-width="1.5"/>
</g>
<!-- 底盘主梁 -->
<g id="chassisGroup">
<rect x="-175" y="-14" width="350" height="10" rx="5" fill="#5a5f6b" stroke="#8892a0" stroke-width="1.5"/>
<!-- 阻尼铰链标记 -->
<circle cx="0" cy="-9" r="6" fill="#3a3d45" stroke="#8892a0" stroke-width="1.5"/>
<line x1="-3" y1="-9" x2="3" y2="-9" stroke="#f0a54a" stroke-width="2" stroke-linecap="round"/>
</g>
<!-- 货箱 -->
<g id="cargoGroup">
<rect x="-80" y="-75" width="160" height="58" rx="6" fill="url(#gradCargo)" stroke="#7a6238" stroke-width="2"/>
<!-- 货箱内部纹理 -->
<line x1="-55" y1="-55" x2="55" y2="-55" stroke="rgba(255,255,255,0.08)" stroke-width="1"/>
<line x1="-55" y1="-38" x2="55" y2="-38" stroke="rgba(255,255,255,0.08)" stroke-width="1"/>
<!-- 货箱标签 -->
<text x="0" y="-20" text-anchor="middle" fill="rgba(255,255,255,0.5)" font-size="10" font-family="var(--font-body)" letter-spacing="0.06em">货箱</text>
<!-- 阻尼铰链连接线 -->
<line x1="0" y1="-17" x2="0" y2="0" stroke="#c9a96e" stroke-width="2" stroke-dasharray="3,3" opacity="0.6"/>
</g>
<!-- 翻转阶段高亮遮罩 -->
<rect id="highlightOverlay" x="0" y="0" width="1400" height="560" fill="none" pointer-events="none" stroke="none"/>
<!-- 地面接触指示点 -->
<circle id="rearContactDot" cx="0" cy="0" r="5" fill="#4ecf8f" opacity="0.6" pointer-events="none" filter="url(#glowSoft)"/>
<circle id="frontContactDot" cx="0" cy="0" r="5" fill="#4ecf8f" opacity="0.6" pointer-events="none" filter="url(#glowSoft)"/>
</svg>
<!-- 覆盖层标签 -->
<div class="overlay-labels">
<div class="label-tag label-top-left" id="tagTitle">🌟 TRIZ 最终理想解 · 行星翻转轮组</div>
<div class="label-tag label-top-right" id="tagPhase">阶段:平路滚动</div>
<div class="label-tag label-bottom-left" id="tagInnovation">💡 核心创新:台阶从"障碍"变为"支点"</div>
<div class="label-tag label-bottom-right" id="tagDetail">回转半径 115mm · 小轮 Φ80mm</div>
</div>
<!-- 阶段指示点 -->
<div class="phase-indicator" id="phaseDots">
<div class="phase-dot active" title="平路滚动"></div>
<div class="phase-dot" title="遇障"></div>
<div class="phase-dot" title="翻转"></div>
<div class="phase-dot" title="登台"></div>
<div class="phase-dot" title="完成"></div>
</div>
<!-- 速度控制 -->
<div class="speed-control">
<label for="speedSlider">⏱ 速度</label>
<input type="range" id="speedSlider" min="0.3" max="2.5" step="0.1" value="1.0" title="调节动画播放速度">
<label id="speedLabel" style="min-width:28px;text-align:center;">1×</label>
</div>
</div>
<script>
(function() {
// ============ DOM 引用 ============
const svg = document.getElementById('mainSvg');
const rearHubGroup = document.getElementById('rearHubGroup');
const frontHubGroup = document.getElementById('frontHubGroup');
const chassisGroup = document.getElementById('chassisGroup');
const cargoGroup = document.getElementById('cargoGroup');
const pivotGlow = document.getElementById('pivotGlow');
const pivotGlowInner = document.getElementById('pivotGlowInner');
const flipTrajectory = document.getElementById('flipTrajectory');
const contactFlash = document.getElementById('contactFlash');
const rearContactDot = document.getElementById('rearContactDot');
const frontContactDot = document.getElementById('frontContactDot');
const tagPhase = document.getElementById('tagPhase');
const tagInnovation = document.getElementById('tagInnovation');
const phaseDots = document.querySelectorAll('.phase-dot');
const speedSlider = document.getElementById('speedSlider');
const speedLabel = document.getElementById('speedLabel');
// ============ 常量与参数 ============
const GROUND_LOW_Y = 445; // 低地面Y坐标
const GROUND_HIGH_Y = 340; // 台阶顶部Y坐标
const STEP_X = 745; // 台阶立面X坐标
const STEP_HEIGHT = GROUND_LOW_Y - GROUND_HIGH_Y; // 105
const WHEEL_RADIUS = 40; // 小轮半径
const ARM_RADIUS = 115; // 回转半径
const HUB_GROUND_OFFSET = ARM_RADIUS - WHEEL_RADIUS + 5; // 轮组中心距地面高度(平路) ≈80
const CHASSIS_LENGTH = 350; // 前后轮组间距
const TOTAL_DURATION = 11.0; // 动画总时长(秒)
// 小轮在轮组中的本地坐标(120°分布,0°在正上方)
const wheelLocalAngles = [-90, 30, 150]; // 角度(度),-90=正上方
const wheelLocalPositions = wheelLocalAngles.map(a => ({
x: ARM_RADIUS * Math.cos(a * Math.PI / 180),
y: ARM_RADIUS * Math.sin(a * Math.PI / 180)
}));
// ============ 动画状态 ============
let animTime = 0;
let speedMultiplier = 1.0;
let lastFrameTime = null;
let animationId = null;
let isRunning = true;
let currentPhaseIndex = 0;
// ============ 阶段定义 ============
const phaseNames = ['平路滚动', '前轮遇障', '翻转越障', '前轮登台', '后轮跟进', '整车登台', '平稳驶离'];
const phaseTimeRanges = [
{ start: 0, end: 2.2, name: '平路滚动', dotIndex: 0 },
{ start: 2.2, end: 3.2, name: '前轮遇障', dotIndex: 1 },
{ start: 3.2, end: 5.2, name: '翻转越障', dotIndex: 2 },
{ start: 5.2, end: 6.8, name: '前轮登台', dotIndex: 3 },
{ start: 6.8, end: 8.2, name: '后轮翻转', dotIndex: 3 },
{ start: 8.2, end: 9.6, name: '整车登台', dotIndex: 4 },
{ start: 9.6, end: TOTAL_DURATION, name: '平稳驶离', dotIndex: 4 },
];
function getPhaseInfo(t) {
for (const p of phaseTimeRanges) {
if (t >= p.start && t < p.end) return p;
}
return phaseTimeRanges[0];
}
// ============ 缓动函数 ============
function easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; }
function easeOutBack(t) { const c1 = 1.70158; const c3 = c1 + 1; return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math
.pow(t - 1, 2); }
function easeOutElastic(t) { if (t === 0 || t === 1) return t; return Math.pow(2, -10 * t) * Math.sin((t - 1) *
3.5 * Math.PI) + 1; }
function easeInOutQuad(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; }
function lerp(a, b, t) { return a + (b - a) * Math.max(0, Math.min(1, t)); }
function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); }
// ============ 动画计算核心 ============
function computeAnimationState(t) {
// t: 0 到 TOTAL_DURATION
const tNorm = t / TOTAL_DURATION;
// 全局X位移:小车整体从左向右移动
// 阶段0-1:平路接近 (0 → 2.2s),移动约280px
// 阶段2:遇障减速 (2.2 → 3.2s),移动约30px
// 阶段3:翻转 (3.2 → 5.2s),前轮组翻转,底盘前进约60px
// 阶段4:前轮登台过渡 (5.2 → 6.8s),前进约80px
// 阶段5:后轮翻转 (6.8 → 8.2s),前进约50px
// 阶段6:整车登台 (8.2 → 9.6s),前进约100px
// 阶段7:驶离 (9.6 → 11s),前进约120px
let globalX = 160; // 初始X位置(底盘参考点)
let chassisTilt = 0; // 底盘倾斜角度(度)
let cargoTilt = 0; // 货箱倾斜角度(度,相对于水平面)
let frontHubRotation = 0; // 前轮组自转角度(度)
let rearHubRotation = 0; // 后轮组自转角度(度)
let frontHubLocalX = 0; // 前轮组相对于底盘中心的X偏移
let frontHubLocalY = 0; // 前轮组相对于底盘中心的Y偏移
let rearHubLocalX = 0;
let rearHubLocalY = 0;
let pivotX = 0; // 翻转支点X(全局坐标)
let pivotY = 0; // 翻转支点Y
let showPivotGlow = false;
let pivotGlowAlpha = 0;
let showTrajectory = false;
let trajectoryAlpha = 0;
let contactFlashAlpha = 0;
let contactFlashX = 0;
let contactFlashY = 0;
const halfChassis = CHASSIS_LENGTH / 2;
if (t < 2.2) {
// === 阶段0:平路滚动 ===
const phaseT = t / 2.2;
const easedT = easeInOutQuad(phaseT);
globalX = 160 + easedT * 300;
chassisTilt = 0;
cargoTilt = 0;
frontHubRotation = easedT * 360 * 1.3;
rearHubRotation = easedT * 360 * 1.3;
frontHubLocalX = halfChassis;
frontHubLocalY = HUB_GROUND_OFFSET;
rearHubLocalX = -halfChassis;
rearHubLocalY = HUB_GROUND_OFFSET;
} else if (t < 3.2) {
// === 阶段1:前轮遇障(减速,前轮接触台阶立面) ===
const phaseT = (t - 2.2) / 1.0;
const easedT = easeInOutCubic(phaseT);
globalX = 460 + easedT * 35;
chassisTilt = easedT * 3;
cargoTilt = -chassisTilt * 0.3;
frontHubRotation += (t - 2.2) / (3.2 - 2.2) * 80;
rearHubRotation += (t - 2.2) / (3.2 - 2.2) * 100;
frontHubLocalX = halfChassis;
frontHubLocalY = HUB_GROUND_OFFSET - easedT * 15;
rearHubLocalX = -halfChassis;
rearHubLocalY = HUB_GROUND_OFFSET;
// 接触点
contactFlashAlpha = easedT > 0.6 ? (easedT - 0.6) / 0.4 : 0;
contactFlashX = globalX + halfChassis;
contactFlashY = GROUND_LOW_Y - WHEEL_RADIUS;
showPivotGlow = easedT > 0.7;
pivotGlowAlpha = easedT > 0.7 ? (easedT - 0.7) / 0.3 * 0.6 : 0;
pivotX = contactFlashX;
pivotY = contactFlashY;
} else if (t < 5.2) {
// === 阶段2:翻转越障(核心动作) ===
const phaseT = (t - 3.2) / 2.0;
const easedT = easeOutBack(clamp(phaseT, 0, 1));
globalX = 495 + easedT * 70;
chassisTilt = 3 + easedT * 14;
cargoTilt = -chassisTilt * 0.25;
const flipAngle = easedT * 120;
frontHubRotation += (t - 3.2) / (5.2 - 3.2) * 200;
rearHubRotation += (t - 3.2) / (5.2 - 3.2) * 130;
frontHubLocalX = halfChassis - easedT * 25;
frontHubLocalY = HUB_GROUND_OFFSET - 15 - easedT * (STEP_HEIGHT + 20);
rearHubLocalX = -halfChassis + easedT * 10;
rearHubLocalY = HUB_GROUND_OFFSET + easedT * 8;
pivotX = 495 + halfChassis;
pivotY = GROUND_LOW_Y - WHEEL_RADIUS;
showPivotGlow = true;
pivotGlowAlpha = 0.5 + easedT * 0.4;
showTrajectory = true;
trajectoryAlpha = 0.35 + easedT * 0.45;
contactFlashAlpha = 0.7 - easedT * 0.5;
contactFlashX = pivotX;
contactFlashY = pivotY;
} else if (t < 6.8) {
// === 阶段3:前轮登台,过渡 ===
const phaseT = (t - 5.2) / 1.6;
const easedT = easeInOutCubic(phaseT);
globalX = 565 + easedT * 90;
chassisTilt = 17 - easedT * 8;
cargoTilt = -chassisTilt * 0.25;
frontHubRotation += (t - 5.2) / (6.8 - 5.2) * 150;
rearHubRotation += (t - 5.2) / (6.8 - 5.2) * 160;
frontHubLocalX = halfChassis - 25 + easedT * 25;
frontHubLocalY = HUB_GROUND_OFFSET - 15 - STEP_HEIGHT - 20 + easedT * (STEP_HEIGHT + 15);
rearHubLocalX = -halfChassis + 10 + easedT * 20;
rearHubLocalY = HUB_GROUND_OFFSET + 8 - easedT * 2;
pivotGlowAlpha = 0.9 - easedT * 0.6;
showPivotGlow = pivotGlowAlpha > 0.05;
trajectoryAlpha = 0.8 - easedT * 0.6;
showTrajectory = trajectoryAlpha > 0.05;
contactFlashAlpha = 0.2 - easedT * 0.2;
} else if (t < 8.2) {
// === 阶段4:后轮翻转 ===
const phaseT = (t - 6.8) / 1.4;
const easedT = easeOutBack(clamp(phaseT, 0, 1));
globalX = 655 + easedT * 55;
chassisTilt = 9 - easedT * 7;
cargoTilt = -chassisTilt * 0.22;
frontHubRotation += (t - 6.8) / (8.2 - 6.8) * 100;
rearHubRotation += (t - 6.8) / (8.2 - 6.8) * 210;
frontHubLocalX = halfChassis;
frontHubLocalY = HUB_GROUND_OFFSET - STEP_HEIGHT + 5;
rearHubLocalX = -halfChassis + 30 - easedT * 30;
rearHubLocalY = HUB_GROUND_OFFSET + 6 - easedT * (STEP_HEIGHT + 25);
pivotX = globalX - halfChassis + 30;
pivotY = GROUND_LOW_Y - WHEEL_RADIUS;
showPivotGlow = easedT > 0.25 && easedT < 0.9;
pivotGlowAlpha = (easedT > 0.25 && easedT < 0.9) ? 0.55 : 0;
showTrajectory = easedT > 0.2 && easedT < 0.85;
trajectoryAlpha = showTrajectory ? 0.4 : 0;
contactFlashAlpha = (easedT > 0.3 && easedT < 0.7) ? 0.5 : 0;
contactFlashX = pivotX;
contactFlashY = pivotY;
} else if (t < 9.6) {
// === 阶段5:整车登台 ===
const phaseT = (t - 8.2) / 1.4;
const easedT = easeInOutCubic(phaseT);
globalX = 710 + easedT * 110;
chassisTilt = 2 - easedT * 1.5;
cargoTilt = -chassisTilt * 0.2;
frontHubRotation += (t - 8.2) / (9.6 - 8.2) * 80;
rearHubRotation += (t - 8.2) / (9.6 - 8.2) * 80;
frontHubLocalX = halfChassis;
frontHubLocalY = HUB_GROUND_OFFSET - STEP_HEIGHT + 5;
rearHubLocalX = -halfChassis;
rearHubLocalY = HUB_GROUND_OFFSET - STEP_HEIGHT + 3;
pivotGlowAlpha = Math.max(0, pivotGlowAlpha - 0.03);
showPivotGlow = pivotGlowAlpha > 0.02;
trajectoryAlpha = Math.max(0, trajectoryAlpha - 0.03);
showTrajectory = trajectoryAlpha > 0.02;
contactFlashAlpha = Math.max(0, contactFlashAlpha - 0.04);
} else {
// === 阶段6:平稳驶离 ===
const phaseT = (t - 9.6) / (TOTAL_DURATION - 9.6);
const easedT = easeInOutQuad(phaseT);
globalX = 820 + easedT * 160;
chassisTilt = 0.5 - easedT * 0.5;
cargoTilt = 0;
frontHubRotation += (t - 9.6) / (TOTAL_DURATION - 9.6) * 60;
rearHubRotation += (t - 9.6) / (TOTAL_DURATION - 9.6) * 60;
frontHubLocalX = halfChassis;
frontHubLocalY = HUB_GROUND_OFFSET - STEP_HEIGHT;
rearHubLocalX = -halfChassis;
rearHubLocalY = HUB_GROUND_OFFSET - STEP_HEIGHT;
pivotGlowAlpha = 0;
showPivotGlow = false;
trajectoryAlpha = 0;
showTrajectory = false;
contactFlashAlpha = 0;
}
return {
globalX,
chassisTilt,
cargoTilt,
frontHubRotation,
rearHubRotation,
frontHubLocalX,
frontHubLocalY,
rearHubLocalX,
rearHubLocalY,
pivotX,
pivotY,
showPivotGlow,
pivotGlowAlpha,
showTrajectory,
trajectoryAlpha,
contactFlashAlpha,
contactFlashX,
contactFlashY,
};
}
// ============ 更新SVG元素 ============
function updateSVG(state) {
const cx = state.globalX;
const cy = GROUND_LOW_Y - HUB_GROUND_OFFSET;
// 底盘组:位于前后轮组中间
const chassisCX = cx;
const chassisCY = cy - (state.frontHubLocalY + state.rearHubLocalY) / 2 + HUB_GROUND_OFFSET;
chassisGroup.setAttribute('transform',
`translate(${chassisCX}, ${chassisCY}) rotate(${state.chassisTilt})`);
// 前轮组
const fhx = cx + state.frontHubLocalX;
const fhy = cy - HUB_GROUND_OFFSET + state.frontHubLocalY;
frontHubGroup.setAttribute('transform',
`translate(${fhx}, ${fhy}) rotate(${state.frontHubRotation})`);
// 后轮组
const rhx = cx + state.rearHubLocalX;
const rhy = cy - HUB_GROUND_OFFSET + state.rearHubLocalY;
rearHubGroup.setAttribute('transform',
`translate(${rhx}, ${rhy}) rotate(${state.rearHubRotation})`);
// 货箱(相对于底盘,绕阻尼铰链旋转)
cargoGroup.setAttribute('transform', `rotate(${state.cargoTilt})`);
// 支点发光
if (state.showPivotGlow && state.pivotGlowAlpha > 0.01) {
pivotGlow.setAttribute('cx', state.pivotX);
pivotGlow.setAttribute('cy', state.pivotY);
pivotGlow.setAttribute('opacity', state.pivotGlowAlpha);
pivotGlowInner.setAttribute('cx', state.pivotX);
pivotGlowInner.setAttribute('cy', state.pivotY);
pivotGlowInner.setAttribute('opacity', state.pivotGlowAlpha * 0.8);
} else {
pivotGlow.setAttribute('opacity', '0');
pivotGlowInner.setAttribute('opacity', '0');
}
// 翻转轨迹
if (state.showTrajectory && state.trajectoryAlpha > 0.01) {
flipTrajectory.setAttribute('opacity', state.trajectoryAlpha);
// 更新轨迹路径(从前轮组翻转支点画弧线到台阶顶部)
const arcStartX = state.pivotX;
const arcStartY = state.pivotY;
const arcEndX = state.pivotX + 30;
const arcEndY = GROUND_HIGH_Y - WHEEL_RADIUS;
const arcMidX = (arcStartX + arcEndX) / 2 + 40;
const arcMidY = Math.min(arcStartY, arcEndY) - 55;
flipTrajectory.setAttribute('d',
`M ${arcStartX} ${arcStartY} Q ${arcMidX} ${arcMidY} ${arcEndX} ${arcEndY}`);
} else {
flipTrajectory.setAttribute('opacity', '0');
}
// 接触闪光
if (state.contactFlashAlpha > 0.01) {
contactFlash.setAttribute('cx', state.contactFlashX);
contactFlash.setAttribute('cy', state.contactFlashY);
contactFlash.setAttribute('opacity', state.contactFlashAlpha);
contactFlash.setAttribute('r', 10 + state.contactFlashAlpha * 8);
} else {
contactFlash.setAttribute('opacity', '0');
}
// 地面接触点
const fContactX = fhx;
const fContactY = GROUND_LOW_Y;
const rContactX = rhx;
const rContactY = GROUND_LOW_Y;
const onHighGround = state.globalX > STEP_X + 80;
if (onHighGround && state.chassisTilt < 2) {
frontContactDot.setAttribute('cx', fContactX);
frontContactDot.setAttribute('cy', GROUND_HIGH_Y);
rearContactDot.setAttribute('cx', rContactX);
rearContactDot.setAttribute('cy', GROUND_HIGH_Y);
} else {
frontContactDot.setAttribute('cx', fContactX);
frontContactDot.setAttribute('cy', Math.min(fContactY, GROUND_HIGH_Y + 50));
rearContactDot.setAttribute('cx', rContactX);
rearContactDot.setAttribute('cy', Math.min(rContactY, GROUND_HIGH_Y + 50));
}
}
// ============ 更新UI覆盖层 ============
function updateUI(t) {
const phaseInfo = getPhaseInfo(t);
if (phaseInfo && phaseInfo.dotIndex !== currentPhaseIndex) {
currentPhaseIndex = phaseInfo.dotIndex;
tagPhase.textContent = '阶段:' + phaseInfo.name;
// 更新阶段指示点
phaseDots.forEach((dot, i) => {
dot.classList.remove('active', 'done');
if (i < currentPhaseIndex) dot.classList.add('done');
if (i === currentPhaseIndex) dot.classList.add('active');
});
}
// 动态更新创新标签
if (t > 2.8 && t < 5.5) {
tagInnovation.textContent = '💡 台阶成为翻转支点 · 资源被巧妙利用';
tagInnovation.classList.add('highlight-active');
} else if (t > 6.5 && t < 8.5) {
tagInnovation.textContent = '💡 后轮组同样利用台阶完成翻转';
tagInnovation.classList.add('highlight-active');
} else {
tagInnovation.textContent = '💡 核心创新:台阶从"障碍"变为"支点"';
tagInnovation.classList.remove('highlight-active');
}
}
// ============ 动画循环 ============
function animate(timestamp) {
if (!isRunning) return;
if (lastFrameTime === null) {
lastFrameTime = timestamp;
}
let delta = (timestamp - lastFrameTime) / 1000; // 秒
if (delta > 0.2) delta = 0.2; // 防止大帧跳跃
if (delta <= 0) delta = 0.016;
lastFrameTime = timestamp;
animTime += delta * speedMultiplier;
if (animTime >= TOTAL_DURATION) {
animTime -= TOTAL_DURATION;
currentPhaseIndex = -1; // 强制刷新阶段
}
if (animTime < 0) animTime = 0;
const state = computeAnimationState(animTime);
updateSVG(state);
updateUI(animTime);
animationId = requestAnimationFrame(animate);
}
// ============ 启动与重置 ============
function startAnimation() {
if (animationId) {
cancelAnimationFrame(animationId);
}
animTime = 0;
lastFrameTime = null;
currentPhaseIndex = -1;
isRunning = true;
// 重置阶段指示点
phaseDots.forEach((dot, i) => {
dot.classList.remove('active', 'done');
if (i === 0) dot.classList.add('active');
});
currentPhaseIndex = 0;
tagPhase.textContent = '阶段:平路滚动';
tagInnovation.textContent = '💡 核心创新:台阶从"障碍"变为"支点"';
tagInnovation.classList.remove('highlight-active');
pivotGlow.setAttribute('opacity', '0');
pivotGlowInner.setAttribute('opacity', '0');
flipTrajectory.setAttribute('opacity', '0');
contactFlash.setAttribute('opacity', '0');
animationId = requestAnimationFrame(animate);
}
function stopAnimation() {
isRunning = false;
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
}
// ============ 事件监听 ============
window.addEventListener('DOMContentLoaded', () => {
startAnimation();
});
// 如果DOM已加载则立即启动
if (document.readyState === 'interactive' || document.readyState === 'complete') {
startAnimation();
}
// 页面可见性变化时处理
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
stopAnimation();
} else {
// 页面重新可见时重新启动
lastFrameTime = null;
isRunning = true;
if (!animationId) {
animationId = requestAnimationFrame(animate);
}
}
});
// 速度滑块
speedSlider.addEventListener('input', () => {
speedMultiplier = parseFloat(speedSlider.value);
speedLabel.textContent = speedMultiplier.toFixed(1) + '×';
});
// 窗口大小变化时不需要特殊处理,SVG viewBox自动适配
console.log('🚀 行星翻转轮组动画已就绪 | TRIZ 最终理想解 (IFR) 可视化');
console.log(' - 自动循环播放中');
console.log(' - 使用速度滑块可调节播放速率');
console.log(' - 观察金色发光标记:台阶从障碍变为支点');
})();
</script>
</body>
</html>
动画展示解析
这个动画结合清晰机械动作、阶段说明和轮组翻转轨迹,直观展示了“台阶障碍→阶越支点”这一创新点和基于转换原理的越障过程。
清晰的动画叙事:页面将复杂的越障过程拆解为6个连续阶段,每个阶段都有对应的文字说明和进度指示点。动画默认自动循环播放,无需手动触发,让您能完整观察从平路滚动、前轮遇障、翻转登台到整车驶离的全过程。
直观的交互控制:页面右下角提供了动画速度调节滑块。您可以放慢动画,仔细观察轮组翻转时各部件(小轮、旋转臂、货箱)的相对运动;或加快动画,快速浏览整个越障循环。
聚焦核心的视觉设计:采用了深色工业风格背景,将视觉焦点集中在金色主色调的行星轮组机构上。动画中会动态高亮台阶立面的接触点,并绘制出轮组翻转的轨迹弧线,直接展示了“利用台阶作为支点完成翻转”这一核心物理原理。
优化建议: 您可以在代码开头的<style>标签中修改CSS变量(如--accent)来调整主题色;台阶高度和轮组尺寸等关键参数位于<script>开头的常量定义区域,您可按需微调。
这是为您重新生成的行星翻转轮组越障动画,重点优化了支架与轮组的整体联动表现。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>行星翻转轮组 · 越障原理动画 | TRIZ 最终理想解</title>
<style>
:root {
--bg: #14151a;
--surface: #1e1f26;
--text: #c8cad4;
--text-secondary: #8a8d9a;
--accent: #f0a54a;
--accent-bright: #ffd166;
--accent-glow: rgba(240, 165, 74, 0.5);
--step: #4a4c5a;
--step-edge: #7a7d90;
--ground-low: #2e303b;
--ground-high: #353744;
--metal: #8892a0;
--metal-light: #a8b2c0;
--rubber: #262830;
--rubber-stroke: #3e404a;
--cargo: #c9a96e;
--cargo-dark: #7a5f36;
--hub-glow: #ffcc66;
--font-display: 'Georgia', 'Noto Serif SC', 'STSong', 'SimSun', 'Songti SC', serif;
--font-body: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', 'Hiragino Sans GB', sans-serif;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: var(--bg);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
font-family: var(--font-body);
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
user-select: none;
-webkit-user-select: none;
}
.animation-wrapper {
position: relative;
width: 96vw;
max-width: 1350px;
aspect-ratio: 16 / 7;
min-height: 500px;
background: radial-gradient(ellipse at 50% 45%, #1e2030 0%, #111218 75%);
border-radius: 20px;
overflow: hidden;
box-shadow:
0 4px 40px rgba(0, 0, 0, 0.55),
0 1px 0 rgba(255, 255, 255, 0.025) inset,
0 0 0 1px rgba(255, 255, 255, 0.06);
}
.animation-wrapper svg {
width: 100%;
height: 100%;
display: block;
}
/* 覆盖层标签 */
.overlay-labels {
position: absolute;
pointer-events: none;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 10;
}
.label-tag {
position: absolute;
font-family: var(--font-body);
font-size: 10.5px;
font-weight: 500;
letter-spacing: 0.03em;
color: #9597a2;
background: rgba(20, 21, 26, 0.78);
padding: 4px 11px;
border-radius: 13px;
border: 1px solid rgba(255, 255, 255, 0.07);
white-space: nowrap;
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
transition: color 0.5s, border-color 0.5s, background 0.5s;
pointer-events: none;
}
.label-tag.highlight-active {
color: #f0cc7a;
border-color: rgba(240, 165, 74, 0.45);
background: rgba(28, 25, 18, 0.82);
}
.label-tl {
top: 12px;
left: 14px;
}
.label-tr {
top: 12px;
right: 14px;
}
.label-bl {
bottom: 12px;
left: 14px;
}
.label-br {
bottom: 12px;
right: 14px;
}
.label-bc {
bottom: 6px;
left: 50%;
transform: translateX(-50%);
font-size: 9.5px;
letter-spacing: 0.04em;
}
/* 阶段指示点 */
.phase-indicator {
position: absolute;
bottom: 38px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 5px;
z-index: 11;
pointer-events: none;
}
.phase-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.18);
transition: all 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}
.phase-dot.active {
background: var(--accent);
box-shadow: 0 0 9px var(--accent-glow);
width: 8px;
height: 8px;
margin-top: -1px;
}
.phase-dot.done {
background: rgba(240, 165, 74, 0.45);
}
/* 速度控制 */
.speed-control {
position: absolute;
bottom: 14px;
right: 70px;
z-index: 20;
display: flex;
align-items: center;
gap: 7px;
pointer-events: all;
opacity: 0.65;
transition: opacity 0.3s;
}
.speed-control:hover {
opacity: 1;
}
.speed-control label {
font-size: 10px;
color: #9597a2;
font-family: var(--font-body);
letter-spacing: 0.04em;
pointer-events: none;
}
.speed-control input[type="range"] {
-webkit-appearance: none;
width: 85px;
height: 4px;
border-radius: 2px;
background: rgba(255, 255, 255, 0.18);
outline: none;
cursor: pointer;
}
.speed-control input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 15px;
height: 15px;
border-radius: 50%;
background: var(--accent);
cursor: pointer;
border: 2px solid #14151a;
box-shadow: 0 0 8px rgba(240, 165, 74, 0.35);
}
#speedLabel {
min-width: 26px;
text-align: center;
color: #c8cad4;
}
@keyframes subtlePulse {
0%,
100% {
opacity: 0.55;
}
50% {
opacity: 1;
}
}
.pulse-active {
animation: subtlePulse 1.5s ease-in-out infinite;
}
</style>
</head>
<body>
<div class="animation-wrapper" id="animContainer">
<svg id="mainSvg" viewBox="0 0 1450 560" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- 背景网格 -->
<pattern id="grid" width="50" height="50" patternUnits="userSpaceOnUse">
<path d="M 50 0 L 0 0 0 50" fill="none" stroke="rgba(255,255,255,0.025)" stroke-width="0.5"/>
</pattern>
<!-- 发光滤镜 -->
<filter id="glowStrong" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="6" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowSoft" x="-60%" y="-60%" width="220%" height="220%">
<feGaussianBlur stdDeviation="3.5" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowTiny" x="-40%" y="-40%" width="180%" height="180%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<!-- 渐变 -->
<linearGradient id="gradGroundLow" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#424454"/>
<stop offset="100%" stop-color="#272934"/>
</linearGradient>
<linearGradient id="gradStepFace" x1="0" y1="0" x2="1" y2="0">
<stop offset="0%" stop-color="#555868"/>
<stop offset="100%" stop-color="#383a47"/>
</linearGradient>
<linearGradient id="gradGroundHigh" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#5e6072"/>
<stop offset="100%" stop-color="#434554"/>
</linearGradient>
<linearGradient id="gradMetalArm" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#a8b2c0"/>
<stop offset="45%" stop-color="#788290"/>
<stop offset="100%" stop-color="#8a94a4"/>
</linearGradient>
<linearGradient id="gradCargo" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#ddbb78"/>
<stop offset="100%" stop-color="#9a7340"/>
</linearGradient>
<radialGradient id="gradPivotGlow" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ffe08a" stop-opacity="0.95"/>
<stop offset="35%" stop-color="#f0a54a" stop-opacity="0.5"/>
<stop offset="100%" stop-color="#f0a54a" stop-opacity="0"/>
</radialGradient>
<radialGradient id="gradContactFlash" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.85"/>
<stop offset="25%" stop-color="#ffd166" stop-opacity="0.55"/>
<stop offset="100%" stop-color="#ff8800" stop-opacity="0"/>
</radialGradient>
<radialGradient id="gradHubHighlight" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ffe8b0" stop-opacity="0.9"/>
<stop offset="50%" stop-color="#f0a54a" stop-opacity="0.35"/>
<stop offset="100%" stop-color="#f0a54a" stop-opacity="0"/>
</radialGradient>
</defs>
<!-- 背景层 -->
<rect x="0" y="0" width="1450" height="560" fill="url(#grid)" opacity="0.5"/>
<!-- 低处地面 -->
<rect id="groundLow" x="0" y="445" width="755" height="115" fill="url(#gradGroundLow)"/>
<line x1="0" y1="445" x2="755" y2="445" stroke="#6e707e" stroke-width="2.5"/>
<!-- 台阶立面 -->
<rect id="stepFace" x="750" y="340" width="22" height="105" fill="url(#gradStepFace)" rx="3"/>
<line x1="748" y1="342" x2="774" y2="342" stroke="#a0a3b2" stroke-width="2.5" stroke-linecap="round"/>
<!-- 台阶边缘微小高光 -->
<line x1="750" y1="343" x2="770" y2="343" stroke="rgba(255,255,255,0.25)" stroke-width="1"/>
<!-- 台阶顶部地面 -->
<rect id="groundHigh" x="770" y="340" width="680" height="220" fill="url(#gradGroundHigh)"/>
<line x1="770" y1="340" x2="1450" y2="340" stroke="#7e8090" stroke-width="2.5"/>
<!-- 台阶高度标注 -->
<line x1="790" y1="340" x2="790" y2="445" stroke="rgba(255,255,255,0.15)" stroke-width="1" stroke-dasharray="5,5"/>
<line x1="785" y1="340" x2="795" y2="340" stroke="rgba(255,255,255,0.2)" stroke-width="1"/>
<line x1="785" y1="445" x2="795" y2="445" stroke="rgba(255,255,255,0.2)" stroke-width="1"/>
<text x="798" y="396" fill="rgba(255,255,255,0.3)" font-size="10" font-family="var(--font-body)">台阶</text>
<text x="798" y="409" fill="rgba(255,255,255,0.3)" font-size="10" font-family="var(--font-body)">~100mm</text>
<!-- 支点发光(翻转阶段显示) -->
<circle id="pivotGlow" cx="0" cy="0" r="24" fill="url(#gradPivotGlow)" filter="url(#glowStrong)" opacity="0" pointer-events="none"/>
<circle id="pivotGlowInner" cx="0" cy="0" r="7" fill="#ffe8b0" opacity="0" pointer-events="none" filter="url(#glowSoft)"/>
<!-- 翻转轨迹弧线 -->
<path id="flipTrajectory" d="" fill="none" stroke="#f0a54a" stroke-width="2.2" stroke-dasharray="7,5" opacity="0" pointer-events="none" filter="url(#glowTiny)"/>
<!-- 接触闪光 -->
<circle id="contactFlash" cx="0" cy="0" r="16" fill="url(#gradContactFlash)" opacity="0" pointer-events="none"/>
<!-- 轮组中心高亮(翻转时标记支架位置) -->
<circle id="frontHubHighlight" cx="0" cy="0" r="15" fill="url(#gradHubHighlight)" filter="url(#glowSoft)" opacity="0" pointer-events="none"/>
<circle id="rearHubHighlight" cx="0" cy="0" r="15" fill="url(#gradHubHighlight)" filter="url(#glowSoft)" opacity="0" pointer-events="none"/>
<!-- === 动态元素组 === -->
<!-- 后轮组 -->
<g id="rearHubGroup">
<!-- 旋转臂(三条,120°分布) -->
<g id="rearArms">
<line x1="0" y1="0" x2="0" y2="-115" stroke="url(#gradMetalArm)" stroke-width="6.5" stroke-linecap="round"/>
<line x1="0" y1="0" x2="99.59" y2="57.5" stroke="url(#gradMetalArm)" stroke-width="6.5" stroke-linecap="round"/>
<line x1="0" y1="0" x2="-99.59" y2="57.5" stroke="url(#gradMetalArm)" stroke-width="6.5" stroke-linecap="round"/>
</g>
<!-- 中心支架 / 轮毂 -->
<circle cx="0" cy="0" r="10" fill="#5a5f6b" stroke="#8892a0" stroke-width="2.5"/>
<circle cx="0" cy="0" r="4.5" fill="#2e3038"/>
<circle cx="0" cy="0" r="2.2" fill="#8892a0"/>
<!-- 三个小轮 -->
<circle cx="0" cy="-115" r="40" fill="var(--rubber)" stroke="var(--rubber-stroke)" stroke-width="2.8"/>
<circle cx="99.59" cy="57.5" r="40" fill="var(--rubber)" stroke="var(--rubber-stroke)" stroke-width="2.8"/>
<circle cx="-99.59" cy="57.5" r="40" fill="var(--rubber)" stroke="var(--rubber-stroke)" stroke-width="2.8"/>
<!-- 小轮内圈标记 -->
<circle cx="0" cy="-115" r="6.5" fill="#3a3c44" stroke="#555" stroke-width="1.2"/>
<circle cx="99.59" cy="57.5" r="6.5" fill="#3a3c44" stroke="#555" stroke-width="1.2"/>
<circle cx="-99.59" cy="57.5" r="6.5" fill="#3a3c44" stroke="#555" stroke-width="1.2"/>
<!-- 小轮花纹暗示(细微横线) -->
<line x1="-10" y1="-115" x2="10" y2="-115" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>
<line x1="89.59" y1="57.5" x2="109.59" y2="57.5" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>
<line x1="-109.59" y1="57.5" x2="-89.59" y2="57.5" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>
</g>
<!-- 前轮组(结构与后轮组相同) -->
<g id="frontHubGroup">
<g id="frontArms">
<line x1="0" y1="0" x2="0" y2="-115" stroke="url(#gradMetalArm)" stroke-width="6.5" stroke-linecap="round"/>
<line x1="0" y1="0" x2="99.59" y2="57.5" stroke="url(#gradMetalArm)" stroke-width="6.5" stroke-linecap="round"/>
<line x1="0" y1="0" x2="-99.59" y2="57.5" stroke="url(#gradMetalArm)" stroke-width="6.5" stroke-linecap="round"/>
</g>
<circle cx="0" cy="0" r="10" fill="#5a5f6b" stroke="#8892a0" stroke-width="2.5"/>
<circle cx="0" cy="0" r="4.5" fill="#2e3038"/>
<circle cx="0" cy="0" r="2.2" fill="#8892a0"/>
<circle cx="0" cy="-115" r="40" fill="var(--rubber)" stroke="var(--rubber-stroke)" stroke-width="2.8"/>
<circle cx="99.59" cy="57.5" r="40" fill="var(--rubber)" stroke="var(--rubber-stroke)" stroke-width="2.8"/>
<circle cx="-99.59" cy="57.5" r="40" fill="var(--rubber)" stroke="var(--rubber-stroke)" stroke-width="2.8"/>
<circle cx="0" cy="-115" r="6.5" fill="#3a3c44" stroke="#555" stroke-width="1.2"/>
<circle cx="99.59" cy="57.5" r="6.5" fill="#3a3c44" stroke="#555" stroke-width="1.2"/>
<circle cx="-99.59" cy="57.5" r="6.5" fill="#3a3c44" stroke="#555" stroke-width="1.2"/>
<line x1="-10" y1="-115" x2="10" y2="-115" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>
<line x1="89.59" y1="57.5" x2="109.59" y2="57.5" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>
<line x1="-109.59" y1="57.5" x2="-89.59" y2="57.5" stroke="rgba(255,255,255,0.06)" stroke-width="1"/>
</g>
<!-- 底盘主梁(动态线段,连接前后轮组中心) -->
<line id="chassisBeam" x1="0" y1="0" x2="0" y2="0" stroke="#6e7684" stroke-width="11" stroke-linecap="round" opacity="0.9"/>
<line id="chassisBeamHighlight" x1="0" y1="0" x2="0" y2="0" stroke="rgba(180,190,200,0.5)" stroke-width="2" stroke-linecap="round"/>
<!-- 货箱(挂在底盘中点) -->
<g id="cargoGroup">
<rect x="-78" y="-72" width="156" height="56" rx="7" fill="url(#gradCargo)" stroke="#7a5f36" stroke-width="2.2"/>
<line x1="-52" y1="-52" x2="52" y2="-52" stroke="rgba(255,255,255,0.07)" stroke-width="1"/>
<line x1="-52" y1="-35" x2="52" y2="-35" stroke="rgba(255,255,255,0.07)" stroke-width="1"/>
<text x="0" y="-18" text-anchor="middle" fill="rgba(255,255,255,0.45)" font-size="10" font-family="var(--font-body)" letter-spacing="0.05em">货箱</text>
<!-- 阻尼铰链示意 -->
<circle cx="0" cy="-16" r="5" fill="#3a3d45" stroke="#8892a0" stroke-width="1.5"/>
<line x1="-3" y1="-16" x2="3" y2="-16" stroke="#f0a54a" stroke-width="2" stroke-linecap="round"/>
</g>
<!-- 地面接触指示点 -->
<circle id="rearContactDot" cx="0" cy="0" r="5" fill="#4ecf8f" opacity="0.5" pointer-events="none" filter="url(#glowTiny)"/>
<circle id="frontContactDot" cx="0" cy="0" r="5" fill="#4ecf8f" opacity="0.5" pointer-events="none" filter="url(#glowTiny)"/>
</svg>
<!-- 覆盖层标签 -->
<div class="overlay-labels">
<div class="label-tag label-tl" id="tagTitle">🌟 TRIZ 最终理想解 · 行星翻转轮组</div>
<div class="label-tag label-tr" id="tagPhase">阶段:平路滚动</div>
<div class="label-tag label-bl" id="tagInnovation">💡 台阶从"障碍"变为翻转"支点"</div>
<div class="label-tag label-br" id="tagDetail">回转半径 115mm · 小轮 Φ80mm · 阻尼铰链 ±20°</div>
</div>
<!-- 阶段指示点 -->
<div class="phase-indicator" id="phaseDots">
<div class="phase-dot active" title="平路滚动"></div>
<div class="phase-dot" title="遇障"></div>
<div class="phase-dot" title="翻转"></div>
<div class="phase-dot" title="登台"></div>
<div class="phase-dot" title="完成"></div>
</div>
<!-- 速度控制 -->
<div class="speed-control">
<label for="speedSlider">⏱ 速度</label>
<input type="range" id="speedSlider" min="0.25" max="2.5" step="0.05" value="1.0" title="调节动画播放速度">
<label id="speedLabel">1×</label>
</div>
</div>
<script>
(function() {
// ========== DOM 引用 ==========
const rearHubGroup = document.getElementById('rearHubGroup');
const frontHubGroup = document.getElementById('frontHubGroup');
const chassisBeam = document.getElementById('chassisBeam');
const chassisBeamHighlight = document.getElementById('chassisBeamHighlight');
const cargoGroup = document.getElementById('cargoGroup');
const pivotGlow = document.getElementById('pivotGlow');
const pivotGlowInner = document.getElementById('pivotGlowInner');
const flipTrajectory = document.getElementById('flipTrajectory');
const contactFlash = document.getElementById('contactFlash');
const frontHubHighlight = document.getElementById('frontHubHighlight');
const rearHubHighlight = document.getElementById('rearHubHighlight');
const rearContactDot = document.getElementById('rearContactDot');
const frontContactDot = document.getElementById('frontContactDot');
const tagPhase = document.getElementById('tagPhase');
const tagInnovation = document.getElementById('tagInnovation');
const phaseDots = document.querySelectorAll('.phase-dot');
const speedSlider = document.getElementById('speedSlider');
const speedLabel = document.getElementById('speedLabel');
// ========== 常量 ==========
const GROUND_LOW_Y = 445;
const GROUND_HIGH_Y = 340;
const STEP_X = 750; // 台阶立面X
const STEP_HEIGHT = GROUND_LOW_Y - GROUND_HIGH_Y; // 105
const WHEEL_RADIUS = 40;
const ARM_RADIUS = 115;
const HUB_RIDE_HEIGHT = ARM_RADIUS - WHEEL_RADIUS + 6; // 轮组中心距地面高度 ≈81
const CHASSIS_HALF = 175; // 前后轮组间距的一半
const TOTAL_DURATION = 11.5; // 总时长(秒)
// ========== 状态变量 ==========
let animTime = 0;
let speedMultiplier = 1.0;
let lastFrameTime = null;
let animationId = null;
let isRunning = true;
let currentPhaseIndex = -1;
// ========== 阶段定义 ==========
const phaseTimeRanges = [
{ start: 0, end: 2.3, name: '平路滚动', dotIndex: 0 },
{ start: 2.3, end: 3.3, name: '前轮遇障', dotIndex: 1 },
{ start: 3.3, end: 5.5, name: '翻转越障', dotIndex: 2 },
{ start: 5.5, end: 7.0, name: '前轮登台', dotIndex: 3 },
{ start: 7.0, end: 8.6, name: '后轮翻转', dotIndex: 3 },
{ start: 8.6, end: 10.0, name: '整车登台', dotIndex: 4 },
{ start: 10.0, end: TOTAL_DURATION, name: '平稳驶离', dotIndex: 4 },
];
function getPhaseInfo(t) {
for (const p of phaseTimeRanges) {
if (t >= p.start && t < p.end) return p;
}
return phaseTimeRanges[0];
}
// ========== 缓动函数 ==========
function easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; }
function easeOutBack(t) { const c1 = 1.70158; const c3 = c1 + 1; return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math
.pow(t - 1, 2); }
function easeInOutQuad(t) { return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2; }
function easeOutQuad(t) { return 1 - (1 - t) * (1 - t); }
function easeInQuad(t) { return t * t; }
function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); }
function lerp(a, b, t) { return a + (b - a) * clamp(t, 0, 1); }
// ========== 核心动画计算 ==========
function computeAnimationState(t) {
// 全局X位移(底盘参考点初始位置)
let globalBaseX = 155;
// 前后轮组中心全局坐标
let frontHubGX, frontHubGY;
let rearHubGX, rearHubGY;
// 轮组自转角度
let frontRotation = 0;
let rearRotation = 0;
// 支点相关
let pivotX = 0,
pivotY = 0;
let pivotGlowAlpha = 0;
let showPivotGlow = false;
let trajectoryAlpha = 0;
let showTrajectory = false;
let trajectoryD = '';
let contactFlashAlpha = 0;
let contactFlashX = 0,
contactFlashY = 0;
let frontHubHighlightAlpha = 0;
let rearHubHighlightAlpha = 0;
// 平路时轮组中心Y坐标
const hubYLow = GROUND_LOW_Y - HUB_RIDE_HEIGHT; // 445-81=364
const hubYHigh = GROUND_HIGH_Y - HUB_RIDE_HEIGHT; // 340-81=259
if (t < 2.3) {
// === 阶段0:平路滚动 ===
const phaseT = t / 2.3;
const easedT = easeInOutQuad(phaseT);
globalBaseX = 155 + easedT * 310;
frontHubGX = globalBaseX + CHASSIS_HALF;
frontHubGY = hubYLow;
rearHubGX = globalBaseX - CHASSIS_HALF;
rearHubGY = hubYLow;
frontRotation = easedT * 360 * 1.3;
rearRotation = easedT * 360 * 1.3;
} else if (t < 3.3) {
// === 阶段1:前轮遇障 ===
const phaseT = (t - 2.3) / 1.0;
const easedT = easeInOutCubic(phaseT);
globalBaseX = 465 + easedT * 35;
frontHubGX = globalBaseX + CHASSIS_HALF;
frontHubGY = hubYLow - easedT * 10;
rearHubGX = globalBaseX - CHASSIS_HALF;
rearHubGY = hubYLow;
frontRotation = 468 + easedT * 85;
rearRotation = 468 + easedT * 105;
// 接触闪光
if (easedT > 0.5) {
contactFlashAlpha = (easedT - 0.5) / 0.5 * 0.7;
contactFlashX = frontHubGX;
contactFlashY = GROUND_LOW_Y - WHEEL_RADIUS;
}
if (easedT > 0.65) {
pivotGlowAlpha = (easedT - 0.65) / 0.35 * 0.5;
showPivotGlow = true;
pivotX = contactFlashX;
pivotY = contactFlashY;
}
} else if (t < 5.5) {
// === 阶段2:前轮组翻转越障(核心) ===
const phaseT = (t - 3.3) / 2.2;
const easedT = easeOutBack(clamp(phaseT, 0, 1));
// 支点固定:前轮组最前方小轮接触台阶立面底部
pivotX = 465 + CHASSIS_HALF; // 约640(实际会随globalBaseX变化,这里取遇障结束时的位置)
// 重新精确计算:globalBaseX在阶段1结束时约500,前轮组中心约675
// 但实际上轮组绕支点翻转,支点大约在(STEP_X + WHEEL_RADIUS, GROUND_LOW_Y - WHEEL_RADIUS)
pivotX = STEP_X + WHEEL_RADIUS; // 790
pivotY = GROUND_LOW_Y - WHEEL_RADIUS; // 405
// 轮组中心绕支点旋转:初始角度约-55°(左上方),翻转120°后约65°(右上方)
const startAngleDeg = -58;
const flipAngleDeg = startAngleDeg + easedT * 120; // 从-58°到62°
const flipAngleRad = flipAngleDeg * Math.PI / 180;
frontHubGX = pivotX + ARM_RADIUS * Math.cos(flipAngleRad);
frontHubGY = pivotY + ARM_RADIUS * Math.sin(flipAngleRad);
// 后轮组继续在地面滚动
globalBaseX = 500 + easedT * 55;
rearHubGX = globalBaseX - CHASSIS_HALF + easedT * 20;
rearHubGY = hubYLow + easedT * 5;
frontRotation = 553 + easedT * 210;
rearRotation = 573 + easedT * 140;
showPivotGlow = true;
pivotGlowAlpha = 0.45 + easedT * 0.4;
showTrajectory = true;
trajectoryAlpha = 0.3 + easedT * 0.5;
// 轨迹弧线
const trajStartX = pivotX + ARM_RADIUS * Math.cos(startAngleDeg * Math.PI / 180);
const trajStartY = pivotY + ARM_RADIUS * Math.sin(startAngleDeg * Math.PI / 180);
const trajEndX = pivotX + ARM_RADIUS * Math.cos(62 * Math.PI / 180);
const trajEndY = pivotY + ARM_RADIUS * Math.sin(62 * Math.PI / 180);
const trajMidX = (trajStartX + trajEndX) / 2 + 50;
const trajMidY = Math.min(trajStartY, trajEndY) - 60;
trajectoryD =
`M ${trajStartX} ${trajStartY} Q ${trajMidX} ${trajMidY} ${trajEndX} ${trajEndY}`;
contactFlashAlpha = 0.6 - easedT * 0.4;
contactFlashX = pivotX;
contactFlashY = pivotY;
frontHubHighlightAlpha = 0.5 + easedT * 0.4;
} else if (t < 7.0) {
// === 阶段3:前轮登台过渡 ===
const phaseT = (t - 5.5) / 1.5;
const easedT = easeInOutCubic(phaseT);
// 前轮组在台阶顶部
frontHubGX = pivotX + ARM_RADIUS * Math.cos(62 * Math.PI / 180) + easedT * 85;
frontHubGY = hubYHigh + (1 - easedT) * 8;
globalBaseX = 555 + easedT * 85;
rearHubGX = globalBaseX - CHASSIS_HALF + 20 - easedT * 15;
rearHubGY = hubYLow + 5 - easedT * 2;
frontRotation = 763 + easedT * 160;
rearRotation = 713 + easedT * 170;
pivotGlowAlpha = 0.85 - easedT * 0.55;
showPivotGlow = pivotGlowAlpha > 0.04;
trajectoryAlpha = 0.8 - easedT * 0.6;
showTrajectory = trajectoryAlpha > 0.04;
contactFlashAlpha = 0.2 - easedT * 0.2;
frontHubHighlightAlpha = 0.9 - easedT * 0.5;
} else if (t < 8.6) {
// === 阶段4:后轮组翻转 ===
const phaseT = (t - 7.0) / 1.6;
const easedT = easeOutBack(clamp(phaseT, 0, 1));
// 后轮组支点
const rPivotX = STEP_X + WHEEL_RADIUS; // 790
const rPivotY = GROUND_LOW_Y - WHEEL_RADIUS; // 405
const rStartAngleDeg = -55;
const rFlipAngleDeg = rStartAngleDeg + easedT * 118;
const rFlipAngleRad = rFlipAngleDeg * Math.PI / 180;
rearHubGX = rPivotX + ARM_RADIUS * Math.cos(rFlipAngleRad);
rearHubGY = rPivotY + ARM_RADIUS * Math.sin(rFlipAngleRad);
// 前轮组已在台阶上
globalBaseX = 640 + easedT * 50;
frontHubGX = globalBaseX + CHASSIS_HALF + easedT * 30;
frontHubGY = hubYHigh + (1 - easedT) * 3;
frontRotation = 923 + easedT * 110;
rearRotation = 883 + easedT * 220;
pivotX = rPivotX;
pivotY = rPivotY;
showPivotGlow = easedT > 0.2 && easedT < 0.92;
pivotGlowAlpha = showPivotGlow ? 0.5 : 0;
showTrajectory = easedT > 0.15 && easedT < 0.88;
trajectoryAlpha = showTrajectory ? 0.38 : 0;
const rtStartX = rPivotX + ARM_RADIUS * Math.cos(rStartAngleDeg * Math.PI / 180);
const rtStartY = rPivotY + ARM_RADIUS * Math.sin(rStartAngleDeg * Math.PI / 180);
const rtEndX = rPivotX + ARM_RADIUS * Math.cos((rStartAngleDeg + 118) * Math.PI / 180);
const rtEndY = rPivotY + ARM_RADIUS * Math.sin((rStartAngleDeg + 118) * Math.PI / 180);
const rtMidX = (rtStartX + rtEndX) / 2 + 45;
const rtMidY = Math.min(rtStartY, rtEndY) - 55;
trajectoryD = `M ${rtStartX} ${rtStartY} Q ${rtMidX} ${rtMidY} ${rtEndX} ${rtEndY}`;
contactFlashAlpha = (easedT > 0.25 && easedT < 0.75) ? 0.55 : 0;
contactFlashX = rPivotX;
contactFlashY = rPivotY;
rearHubHighlightAlpha = 0.5 + easedT * 0.35;
} else if (t < 10.0) {
// === 阶段5:整车登台 ===
const phaseT = (t - 8.6) / 1.4;
const easedT = easeInOutCubic(phaseT);
const rPivotX = STEP_X + WHEEL_RADIUS;
const rPivotY = GROUND_LOW_Y - WHEEL_RADIUS;
const rEndAngleRad = (-55 + 118) * Math.PI / 180;
const rEndX = rPivotX + ARM_RADIUS * Math.cos(rEndAngleRad);
const rEndY = rPivotY + ARM_RADIUS * Math.sin(rEndAngleRad);
rearHubGX = rEndX + easedT * 100;
rearHubGY = hubYHigh + (1 - easedT) * 5;
globalBaseX = 690 + easedT * 120;
frontHubGX = globalBaseX + CHASSIS_HALF + 30 + easedT * 10;
frontHubGY = hubYHigh;
frontRotation = 1033 + easedT * 80;
rearRotation = 1103 + easedT * 80;
pivotGlowAlpha = Math.max(0, pivotGlowAlpha - 0.06);
showPivotGlow = pivotGlowAlpha > 0.02;
trajectoryAlpha = Math.max(0, trajectoryAlpha - 0.06);
showTrajectory = trajectoryAlpha > 0.02;
contactFlashAlpha = Math.max(0, contactFlashAlpha - 0.05);
frontHubHighlightAlpha = Math.max(0, frontHubHighlightAlpha - 0.04);
rearHubHighlightAlpha = Math.max(0, rearHubHighlightAlpha - 0.04);
} else {
// === 阶段6:平稳驶离 ===
const phaseT = (t - 10.0) / (TOTAL_DURATION - 10.0);
const easedT = easeInOutQuad(phaseT);
globalBaseX = 810 + easedT * 170;
frontHubGX = globalBaseX + CHASSIS_HALF;
frontHubGY = hubYHigh;
rearHubGX = globalBaseX - CHASSIS_HALF;
rearHubGY = hubYHigh;
frontRotation = 1113 + easedT * 65;
rearRotation = 1183 + easedT * 65;
pivotGlowAlpha = 0;
showPivotGlow = false;
trajectoryAlpha = 0;
showTrajectory = false;
contactFlashAlpha = 0;
frontHubHighlightAlpha = 0;
rearHubHighlightAlpha = 0;
}
return {
frontHubGX,
frontHubGY,
rearHubGX,
rearHubGY,
frontRotation,
rearRotation,
pivotX,
pivotY,
showPivotGlow,
pivotGlowAlpha,
showTrajectory,
trajectoryAlpha,
trajectoryD,
contactFlashAlpha,
contactFlashX,
contactFlashY,
frontHubHighlightAlpha,
rearHubHighlightAlpha,
};
}
// ========== 更新SVG ==========
function updateSVG(state) {
// 前后轮组定位
frontHubGroup.setAttribute('transform',
`translate(${state.frontHubGX}, ${state.frontHubGY}) rotate(${state.frontRotation})`);
rearHubGroup.setAttribute('transform',
`translate(${state.rearHubGX}, ${state.rearHubGY}) rotate(${state.rearRotation})`);
// 底盘主梁:从后轮组中心到前轮组中心
chassisBeam.setAttribute('x1', state.rearHubGX);
chassisBeam.setAttribute('y1', state.rearHubGY);
chassisBeam.setAttribute('x2', state.frontHubGX);
chassisBeam.setAttribute('y2', state.frontHubGY);
chassisBeamHighlight.setAttribute('x1', state.rearHubGX);
chassisBeamHighlight.setAttribute('y1', state.rearHubGY);
chassisBeamHighlight.setAttribute('x2', state.frontHubGX);
chassisBeamHighlight.setAttribute('y2', state.frontHubGY);
// 货箱:挂在底盘中点,允许阻尼倾斜
const midX = (state.rearHubGX + state.frontHubGX) / 2;
const midY = (state.rearHubGY + state.frontHubGY) / 2;
const beamAngle = Math.atan2(
state.frontHubGY - state.rearHubGY,
state.frontHubGX - state.rearHubGX
) * 180 / Math.PI;
// 阻尼铰链:货箱倾斜为底盘角度的约25%(反向),最大±20°
const dampingFactor = 0.25;
const rawCargoTilt = -beamAngle * dampingFactor;
const cargoTilt = clamp(rawCargoTilt, -20, 20);
cargoGroup.setAttribute('transform',
`translate(${midX}, ${midY - 28}) rotate(${cargoTilt})`);
// 支点发光
if (state.showPivotGlow && state.pivotGlowAlpha > 0.02) {
pivotGlow.setAttribute('cx', state.pivotX);
pivotGlow.setAttribute('cy', state.pivotY);
pivotGlow.setAttribute('opacity', state.pivotGlowAlpha);
pivotGlowInner.setAttribute('cx', state.pivotX);
pivotGlowInner.setAttribute('cy', state.pivotY);
pivotGlowInner.setAttribute('opacity', state.pivotGlowAlpha * 0.85);
} else {
pivotGlow.setAttribute('opacity', '0');
pivotGlowInner.setAttribute('opacity', '0');
}
// 翻转轨迹
if (state.showTrajectory && state.trajectoryAlpha > 0.02) {
flipTrajectory.setAttribute('d', state.trajectoryD);
flipTrajectory.setAttribute('opacity', state.trajectoryAlpha);
} else {
flipTrajectory.setAttribute('opacity', '0');
}
// 接触闪光
if (state.contactFlashAlpha > 0.02) {
contactFlash.setAttribute('cx', state.contactFlashX);
contactFlash.setAttribute('cy', state.contactFlashY);
contactFlash.setAttribute('opacity', state.contactFlashAlpha);
contactFlash.setAttribute('r', 9 + state.contactFlashAlpha * 10);
} else {
contactFlash.setAttribute('opacity', '0');
}
// 轮组中心高亮
frontHubHighlight.setAttribute('cx', state.frontHubGX);
frontHubHighlight.setAttribute('cy', state.frontHubGY);
frontHubHighlight.setAttribute('opacity', state.frontHubHighlightAlpha);
rearHubHighlight.setAttribute('cx', state.rearHubGX);
rearHubHighlight.setAttribute('cy', state.rearHubGY);
rearHubHighlight.setAttribute('opacity', state.rearHubHighlightAlpha);
// 地面接触指示点
const fOnHigh = state.frontHubGY < GROUND_LOW_Y - 60;
const rOnHigh = state.rearHubGY < GROUND_LOW_Y - 60;
frontContactDot.setAttribute('cx', state.frontHubGX);
frontContactDot.setAttribute('cy', fOnHigh ? GROUND_HIGH_Y : GROUND_LOW_Y);
rearContactDot.setAttribute('cx', state.rearHubGX);
rearContactDot.setAttribute('cy', rOnHigh ? GROUND_HIGH_Y : GROUND_LOW_Y);
}
// ========== 更新UI ==========
function updateUI(t) {
const phaseInfo = getPhaseInfo(t);
if (phaseInfo && phaseInfo.dotIndex !== currentPhaseIndex) {
currentPhaseIndex = phaseInfo.dotIndex;
tagPhase.textContent = '阶段:' + phaseInfo.name;
phaseDots.forEach((dot, i) => {
dot.classList.remove('active', 'done');
if (i < currentPhaseIndex) dot.classList.add('done');
if (i === currentPhaseIndex) dot.classList.add('active');
});
}
// 动态创新标签
if (t > 2.8 && t < 5.8) {
tagInnovation.textContent = '💡 台阶成为翻转支点 · 支架随轮组整体跨越';
tagInnovation.classList.add('highlight-active');
} else if (t > 6.8 && t < 8.8) {
tagInnovation.textContent = '💡 后轮组同样利用台阶完成翻转跨越';
tagInnovation.classList.add('highlight-active');
} else {
tagInnovation.textContent = '💡 台阶从"障碍"变为翻转"支点"';
tagInnovation.classList.remove('highlight-active');
}
}
// ========== 动画循环 ==========
function animate(timestamp) {
if (!isRunning) return;
if (lastFrameTime === null) lastFrameTime = timestamp;
let delta = (timestamp - lastFrameTime) / 1000;
if (delta > 0.25) delta = 0.25;
if (delta <= 0) delta = 0.016;
lastFrameTime = timestamp;
animTime += delta * speedMultiplier;
if (animTime >= TOTAL_DURATION) {
animTime -= TOTAL_DURATION;
currentPhaseIndex = -1;
}
if (animTime < 0) animTime = 0;
const state = computeAnimationState(animTime);
updateSVG(state);
updateUI(animTime);
animationId = requestAnimationFrame(animate);
}
// ========== 启动与重置 ==========
function startAnimation() {
if (animationId) cancelAnimationFrame(animationId);
animTime = 0;
lastFrameTime = null;
currentPhaseIndex = -1;
isRunning = true;
phaseDots.forEach((dot, i) => {
dot.classList.remove('active', 'done');
if (i === 0) dot.classList.add('active');
});
currentPhaseIndex = 0;
tagPhase.textContent = '阶段:平路滚动';
tagInnovation.textContent = '💡 台阶从"障碍"变为翻转"支点"';
tagInnovation.classList.remove('highlight-active');
pivotGlow.setAttribute('opacity', '0');
pivotGlowInner.setAttribute('opacity', '0');
flipTrajectory.setAttribute('opacity', '0');
contactFlash.setAttribute('opacity', '0');
frontHubHighlight.setAttribute('opacity', '0');
rearHubHighlight.setAttribute('opacity', '0');
// 初始状态渲染一帧
const initState = computeAnimationState(0);
updateSVG(initState);
animationId = requestAnimationFrame(animate);
}
function stopAnimation() {
isRunning = false;
if (animationId) { cancelAnimationFrame(animationId);
animationId = null; }
}
// ========== 事件监听 ==========
if (document.readyState === 'interactive' || document.readyState === 'complete') {
startAnimation();
} else {
window.addEventListener('DOMContentLoaded', startAnimation);
}
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
stopAnimation();
} else {
lastFrameTime = null;
isRunning = true;
if (!animationId) animationId = requestAnimationFrame(animate);
}
});
speedSlider.addEventListener('input', () => {
speedMultiplier = parseFloat(speedSlider.value);
speedLabel.textContent = speedMultiplier.toFixed(2) + '×';
});
console.log('🚀 行星翻转轮组动画就绪 | TRIZ 最终理想解 (IFR)');
console.log(' ✓ 轮组支架与轮子始终整体联动');
console.log(' ✓ 底盘主梁动态连接前后轮组中心');
console.log(' ✓ 翻转阶段展示支点圆弧轨迹');
})();
</script>
</body>
</html>
越障动画解析
这个动画重点展示了“中间支架始终与轮子保持一体不脱离”以及台阶被用作翻转支点的关键过程。
- 核心机制:动画模拟了行星翻转轮组的越障过程。关键在于,当小轮接触台阶立面后,整个轮组(包含三个小轮和连接它们的旋转臂/支架)会整体绕台阶边缘翻转,支架随轮子一同跨越台阶,而非单独移动。
- 操作方式:动画页面加载后会自动循环播放。您可以使用右下角的速度滑块来调整动画快慢,便于仔细观察翻转瞬间。页面底部有阶段指示点,左上角会显示当前阶段名称。
- 视觉风格:采用深色工业风格背景,突出金属质感的轮组和货箱。在翻转阶段,台阶接触点会出现金色发光点和虚线运动轨迹,同时轮组中心支架会高亮显示,强化“支架随动”这一视觉印象。货箱通过阻尼铰链连接,会在跨越台阶时略微摆动,体现被动保持水平的特性。
优化建议: 您可以在代码开头的<style>标签中修改--accent等CSS变量来调整主题色;关键物理参数(如回转半径ARM_RADIUS、台阶高度STEP_HEIGHT)位于<script>标签开头的常量区域,可按需微调。
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
