独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>S-Curve Motion Profile - Fast & Stable</title>
<style>
:root {
--bg-color: #070B14;
--grid-color: rgba(41, 53, 72, 0.4);
--text-main: #E2E8F0;
--text-muted: #64748B;
--accent-fast: #00F0FF; /* 快速启动阶段颜色 */
--accent-stable: #FF9F1C; /* 阻尼减速阶段颜色 */
--accent-pos: #8B5CF6; /* 位移曲线颜色 */
--panel-bg: rgba(15, 23, 42, 0.7);
--border-color: rgba(51, 65, 85, 0.8);
}
body {
margin: 0;
padding: 0;
background-color: var(--bg-color);
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
overflow: hidden;
color: var(--text-main);
}
#animation-container {
width: 100%;
max-width: 1200px;
aspect-ratio: 16 / 9;
position: relative;
background: radial-gradient(circle at 50% 50%, #0F172A 0%, var(--bg-color) 100%);
box-shadow: 0 0 100px rgba(0, 240, 255, 0.05) inset;
border: 1px solid rgba(255, 255, 255, 0.05);
}
svg {
width: 100%;
height: 100%;
display: block;
}
/* 隐藏滚动条 */
::-webkit-scrollbar { display: none; }
/* SVG 样式精调 */
.text-sm { font-size: 12px; font-weight: 400; letter-spacing: 0.05em; }
.text-xs { font-size: 10px; font-weight: 300; letter-spacing: 0.05em; }
.text-mono { font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace; }
.glow-fast { filter: drop-shadow(0 0 6px rgba(0, 240, 255, 0.6)); }
.glow-stable { filter: drop-shadow(0 0 6px rgba(255, 159, 28, 0.6)); }
.glow-pos { filter: drop-shadow(0 0 6px rgba(139, 92, 246, 0.6)); }
.data-panel {
fill: var(--panel-bg);
stroke: var(--border-color);
stroke-width: 1;
rx: 6;
}
</style>
</head>
<body>
<div id="animation-container">
<svg viewBox="0 0 1200 675" preserveAspectRatio="xMidYMid meet" id="main-svg">
<defs>
<!-- 背景网格图案 -->
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="var(--grid-color)" stroke-width="0.5"/>
</pattern>
<pattern id="subgrid" width="10" height="10" patternUnits="userSpaceOnUse">
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="var(--grid-color)" stroke-width="0.15"/>
</pattern>
<!-- 渐变定义 -->
<linearGradient id="cylinder-grad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#334155" />
<stop offset="50%" stop-color="#94A3B8" />
<stop offset="100%" stop-color="#1E293B" />
</linearGradient>
<linearGradient id="rod-grad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#94A3B8" />
<stop offset="50%" stop-color="#F8FAFC" />
<stop offset="100%" stop-color="#64748B" />
</linearGradient>
<linearGradient id="vel-area-grad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="var(--accent-fast)" stop-opacity="0.3" />
<stop offset="100%" stop-color="var(--accent-fast)" stop-opacity="0.0" />
</linearGradient>
<linearGradient id="vel-area-stable-grad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="var(--accent-stable)" stop-opacity="0.3" />
<stop offset="100%" stop-color="var(--accent-stable)" stop-opacity="0.0" />
</linearGradient>
<!-- 滤镜发光效果 -->
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="3" result="blur" />
<feMerge>
<feMergeNode in="blur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<!-- 1. 背景网格层 -->
<rect width="100%" height="100%" fill="url(#subgrid)" />
<rect width="100%" height="100%" fill="url(#grid)" />
<!-- ==================== 顶部:执行器与物理模型 ==================== -->
<g id="actuator-group" transform="translate(100, 120)">
<!-- 导轨背景 -->
<rect x="0" y="-5" width="1000" height="10" fill="#0F172A" stroke="#1E293B" rx="2" />
<line x1="0" y1="0" x2="1000" y2="0" stroke="rgba(255,255,255,0.1)" stroke-dasharray="4 4" />
<!-- 刻度线 -->
<g stroke="#475569" stroke-width="1" opacity="0.6">
<line x1="0" y1="15" x2="0" y2="25" />
<text x="0" y="40" fill="#94A3B8" class="text-xs" text-anchor="middle">0mm</text>
<line x1="900" y1="15" x2="900" y2="25" />
<text x="900" y="40" fill="#94A3B8" class="text-xs" text-anchor="middle">120mm</text>
</g>
<!-- 执行缸体 (左侧固定) -->
<rect x="-60" y="-30" width="80" height="60" fill="url(#cylinder-grad)" rx="4" stroke="#0F172A" stroke-width="2"/>
<!-- 伺服/阻尼阀控制模块 (概念化展示) -->
<rect x="-40" y="-45" width="40" height="15" fill="#1E293B" rx="2" stroke="#475569" />
<circle id="valve-indicator" cx="-20" cy="-37.5" r="3" fill="var(--accent-fast)" class="glow-fast" />
<text x="-50" y="-55" fill="#94A3B8" class="text-xs">伺服控制 / 阻尼调节阀</text>
<!-- 动态运动部分 (推杆 + 负载) -->
<g id="moving-parts" transform="translate(0, 0)">
<!-- 活塞杆 -->
<rect x="-50" y="-8" width="100" height="16" fill="url(#rod-grad)" id="piston-rod" />
<!-- 负载滑块 -->
<rect x="0" y="-25" width="60" height="50" fill="rgba(30, 41, 59, 0.9)" stroke="var(--accent-fast)" stroke-width="2" rx="4" id="payload-block" class="glow-fast" />
<!-- 负载上的受力/状态指示 -->
<path d="M 15 -10 L 45 -10 L 45 10 L 15 10 Z" fill="rgba(0,240,255,0.1)" id="payload-inner-glow" />
<text x="30" y="4" fill="var(--text-main)" class="text-xs text-mono" text-anchor="middle" font-weight="bold">LOAD</text>
</g>
</g>
<!-- ==================== 底部:时序与状态曲线 ==================== -->
<g id="graph-group" transform="translate(100, 520)">
<!-- 坐标轴 -->
<line x1="0" y1="0" x2="1000" y2="0" stroke="#334155" stroke-width="2" /> <!-- X 轴 (时间) -->
<line x1="0" y1="0" x2="0" y2="-280" stroke="#334155" stroke-width="2" /> <!-- Y 轴 (幅值) -->
<!-- Y轴标签 (极简) -->
<text x="-15" y="-270" fill="var(--text-muted)" class="text-xs" text-anchor="end">Max</text>
<text x="-15" y="-5" fill="var(--text-muted)" class="text-xs" text-anchor="end">0</text>
<!-- X轴标签 -->
<text x="1000" y="20" fill="var(--text-muted)" class="text-xs" text-anchor="end">Time (t)</text>
<text x="900" y="20" fill="var(--text-muted)" class="text-xs" text-anchor="middle">目标位</text>
<!-- 目标位指示虚线 -->
<line x1="900" y1="0" x2="900" y2="-280" stroke="rgba(255,255,255,0.1)" stroke-dasharray="4 4" />
<line x1="0" y1="-250" x2="1000" y2="-250" stroke="rgba(139, 92, 246, 0.2)" stroke-dasharray="2 4" /> <!-- 位移上限基准 -->
<!-- 动态绘制的曲线 -->
<!-- 速度填充区域 (分为加速和减速两段颜色,通过JS遮罩或双路径实现,此处采用叠加路径方式简化) -->
<path id="vel-area-path" d="" fill="url(#vel-area-grad)" opacity="0.8"/>
<path id="vel-area-stable-path" d="" fill="url(#vel-area-stable-grad)" opacity="0.8"/>
<!-- 速度曲线 v(t) - 钟形 -->
<path id="vel-path" d="" fill="none" stroke="var(--accent-fast)" stroke-width="3" class="glow-fast" stroke-linecap="round" stroke-linejoin="round"/>
<!-- 位移曲线 s(t) - S型 -->
<path id="pos-path" d="" fill="none" stroke="var(--accent-pos)" stroke-width="2" class="glow-pos" stroke-dasharray="4 4"/>
<!-- 当前时间游标 -->
<g id="time-cursor" transform="translate(0, 0)">
<line x1="0" y1="10" x2="0" y2="-280" stroke="rgba(255,255,255,0.3)" stroke-width="1" />
<circle cx="0" cy="0" r="4" fill="var(--text-main)" id="cursor-dot-vel" />
<circle cx="0" cy="0" r="3" fill="var(--accent-pos)" id="cursor-dot-pos" />
</g>
</g>
<!-- ==================== 信息面板与数据 ==================== -->
<!-- 左上角:系统参数 -->
<g transform="translate(40, 40)">
<rect width="220" height="70" class="data-panel" />
<text x="15" y="25" fill="var(--text-main)" class="text-sm font-bold">最终理想解 (IFR) 状态</text>
<text x="15" y="45" fill="var(--text-muted)" class="text-xs text-mono">V_avg : 30 mm/s</text>
<text x="110" y="45" fill="var(--text-muted)" class="text-xs text-mono">A_max : 0.5 g</text>
<text x="15" y="60" fill="var(--text-muted)" class="text-xs">无冲击 · 自适应相变协调</text>
</g>
<!-- 右上角:实时状态监控 -->
<g transform="translate(940, 40)">
<rect width="220" height="90" class="data-panel" />
<text x="15" y="25" fill="var(--text-muted)" class="text-xs">当前系统相位</text>
<text x="15" y="48" fill="var(--accent-fast)" class="text-sm font-bold text-mono glow-fast" id="phase-text">STARTUP_ACCEL</text>
<!-- 进度条 -->
<rect x="15" y="65" width="190" height="4" fill="#1E293B" rx="2" />
<rect x="15" y="65" width="0" height="4" fill="var(--accent-fast)" rx="2" id="phase-progress" class="glow-fast" />
</g>
<!-- 图例 (极简, 置于右下角附近) -->
<g transform="translate(960, 600)">
<line x1="0" y1="0" x2="20" y2="0" stroke="var(--accent-pos)" stroke-width="2" stroke-dasharray="4 4" />
<text x="30" y="4" fill="var(--text-muted)" class="text-xs">位移 S(t)</text>
<line x1="0" y1="20" x2="20" y2="20" stroke="var(--accent-fast)" stroke-width="2" />
<text x="30" y="24" fill="var(--text-muted)" class="text-xs">速度 V(t)</text>
</g>
</svg>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
// --- 核心参数配置 ---
const totalDistance = 900; // 对应 120mm 的像素缩放距离
const animationDuration = 2500; // 单次运动时长 (ms)
const holdDuration = 1000; // 到达终点后的保持时间 (ms)
const resetDuration = 800; // 快速复位时长 (ms)
const cycleDuration = animationDuration + holdDuration + resetDuration;
const graphWidth = 900; // 图表X轴宽度
const maxVelY = 180; // 速度曲线最大Y高度 (像素)
const maxPosY = 250; // 位移曲线最大Y高度 (像素)
// --- DOM 元素引用 ---
const movingParts = document.getElementById('moving-parts');
const payloadBlock = document.getElementById('payload-block');
const pistonRod = document.getElementById('piston-rod');
const valveIndicator = document.getElementById('valve-indicator');
const payloadInnerGlow = document.getElementById('payload-inner-glow');
const velPath = document.getElementById('vel-path');
const velAreaPath = document.getElementById('vel-area-path');
const velAreaStablePath = document.getElementById('vel-area-stable-path');
const posPath = document.getElementById('pos-path');
const timeCursor = document.getElementById('time-cursor');
const cursorDotVel = document.getElementById('cursor-dot-vel');
const cursorDotPos = document.getElementById('cursor-dot-pos');
const phaseText = document.getElementById('phase-text');
const phaseProgress = document.getElementById('phase-progress');
// --- 曲线数学模型 (最小加加速度的五次多项式 S 曲线) ---
// t 范围 [0, 1]
// S(t) = 10t^3 - 15t^4 + 6t^5 (归一化位移)
// V(t) = dS/dt = 30t^2 - 60t^3 + 30t^4 (归一化速度)
function getPosition(t) {
return 10 * Math.pow(t, 3) - 15 * Math.pow(t, 4) + 6 * Math.pow(t, 5);
}
function getVelocity(t) {
// 返回归一化的速度,最大值出现在 t=0.5 处,最大值为 1.875
// 为了映射到最大高度,需要除以最大值
const maxNormVel = 1.875;
const normVel = 30 * Math.pow(t, 2) - 60 * Math.pow(t, 3) + 30 * Math.pow(t, 4);
return normVel / maxNormVel;
}
// --- 状态变量 ---
let startTime = null;
let velPoints = [];
let velStablePoints = [];
let posPoints = [];
const ACCENT_FAST = '#00F0FF';
const ACCENT_STABLE = '#FF9F1C';
// --- 主动画循环 ---
function animate(timestamp) {
if (!startTime) startTime = timestamp;
const elapsed = (timestamp - startTime) % cycleDuration;
// 阶段 1: S曲线运动 (0 -> 120mm)
if (elapsed <= animationDuration) {
const t = elapsed / animationDuration;
// 计算当前归一化数据
const s_t = getPosition(t);
const v_t = getVelocity(t);
// 像素映射
const currentX = t * graphWidth;
const currentPosY = -s_t * maxPosY;
const currentVelY = -v_t * maxVelY;
// 1. 更新物理模型位置
const actX = s_t * totalDistance;
movingParts.setAttribute('transform', `translate(${actX}, 0)`);
// 动态拉长活塞杆的左侧 (保持视觉连贯)
pistonRod.setAttribute('x', -50 - actX);
pistonRod.setAttribute('width', 100 + actX);
// 2. 状态逻辑:减速区间判断 (t > 0.6 作为阻尼介入点)
const isDecelerating = t > 0.6;
const currentColor = isDecelerating ? ACCENT_STABLE : ACCENT_FAST;
const currentGlowClass = isDecelerating ? 'glow-stable' : 'glow-fast';
// 更新UI颜色和文本
if (isDecelerating) {
phaseText.textContent = "DAMPING_DECEL";
phaseText.setAttribute('fill', ACCENT_STABLE);
phaseText.className.baseVal = "text-sm font-bold text-mono glow-stable";
valveIndicator.setAttribute('fill', ACCENT_STABLE);
valveIndicator.className.baseVal = "glow-stable";
payloadBlock.setAttribute('stroke', ACCENT_STABLE);
payloadBlock.className.baseVal = "glow-stable";
payloadInnerGlow.setAttribute('fill', 'rgba(255, 159, 28, 0.15)');
velPath.setAttribute('stroke', ACCENT_STABLE);
velPath.className.baseVal = "glow-stable";
phaseProgress.setAttribute('fill', ACCENT_STABLE);
phaseProgress.className.baseVal = "glow-stable";
} else {
phaseText.textContent = t < 0.4 ? "STARTUP_ACCEL" : "STEADY_TRANSIT";
phaseText.setAttribute('fill', ACCENT_FAST);
phaseText.className.baseVal = "text-sm font-bold text-mono glow-fast";
valveIndicator.setAttribute('fill', ACCENT_FAST);
valveIndicator.className.baseVal = "glow-fast";
payloadBlock.setAttribute('stroke', ACCENT_FAST);
payloadBlock.className.baseVal = "glow-fast";
payloadInnerGlow.setAttribute('fill', 'rgba(0, 240, 255, 0.1)');
velPath.setAttribute('stroke', ACCENT_FAST);
velPath.className.baseVal = "glow-fast";
phaseProgress.setAttribute('fill', ACCENT_FAST);
phaseProgress.className.baseVal = "glow-fast";
}
// 更新进度条
phaseProgress.setAttribute('width', t * 190);
// 3. 动态绘制图表曲线
posPoints.push(`${currentX},${currentPosY}`);
// 将速度曲线分段推入数组以实现双色填充
if (isDecelerating) {
velStablePoints.push(`${currentX},${currentVelY}`);
} else {
velPoints.push(`${currentX},${currentVelY}`);
// 确保平滑过渡,减速段的起点连接加速段的终点
if (velStablePoints.length === 0) {
velStablePoints.push(`${currentX},${currentVelY}`);
}
}
// 生成 SVG Path D 字符串
posPath.setAttribute('d', 'M 0,0 L ' + posPoints.join(' L '));
// 速度曲线线框 (合并数组绘制以保持连贯)
const fullVelPath = [...velPoints, ...velStablePoints];
if (fullVelPath.length > 0) {
velPath.setAttribute('d', 'M 0,0 L ' + fullVelPath.join(' L '));
}
// 速度填充区域
if (velPoints.length > 0) {
velAreaPath.setAttribute('d', `M 0,0 L ${velPoints.join(' L ')} L ${velPoints[velPoints.length-1].split(',')[0]},0 Z`);
}
if (velStablePoints.length > 0) {
const startX = velStablePoints[0].split(',')[0];
velAreaStablePath.setAttribute('d', `M ${startX},0 L ${velStablePoints.join(' L ')} L ${currentX},0 Z`);
}
// 更新游标位置
timeCursor.setAttribute('transform', `translate(${currentX}, 0)`);
cursorDotVel.setAttribute('cy', currentVelY);
cursorDotPos.setAttribute('cy', currentPosY);
}
// 阶段 2: 保持状态 (Hold)
else if (elapsed <= animationDuration + holdDuration) {
phaseText.textContent = "TARGET_REACHED";
phaseText.setAttribute('fill', var(--text-main));
phaseText.className.baseVal = "text-sm font-bold text-mono";
valveIndicator.setAttribute('fill', '#475569');
valveIndicator.className.baseVal = "";
payloadBlock.setAttribute('stroke', '#64748B');
payloadBlock.className.baseVal = "";
payloadInnerGlow.setAttribute('fill', 'rgba(255, 255, 255, 0.05)');
velPath.setAttribute('stroke', '#64748B');
velPath.className.baseVal = "";
phaseProgress.setAttribute('fill', '#64748B');
phaseProgress.className.baseVal = "";
}
// 阶段 3: 快速复位 (重置数据准备下一轮)
else {
phaseText.textContent = "SYSTEM_RESET";
const resetT = (elapsed - animationDuration - holdDuration) / resetDuration;
// 使用缓出曲线复位
const easeOut = 1 - Math.pow(1 - resetT, 3);
const actX = (1 - easeOut) * totalDistance;
movingParts.setAttribute('transform', `translate(${actX}, 0)`);
pistonRod.setAttribute('x', -50 - actX);
pistonRod.setAttribute('width', 100 + actX);
phaseProgress.setAttribute('width', (1 - easeOut) * 190);
// 在复位快结束时清空路径数据
if (resetT > 0.9) {
velPoints = [];
velStablePoints = [];
posPoints = [];
velPath.setAttribute('d', '');
posPath.setAttribute('d', '');
velAreaPath.setAttribute('d', '');
velAreaStablePath.setAttribute('d', '');
timeCursor.setAttribute('transform', `translate(0, 0)`);
}
}
requestAnimationFrame(animate);
}
// 启动动画
requestAnimationFrame(animate);
});
</script>
</body>
</html>
积分规则:每轮对话扣10分
等待动画代码生成...
