独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>顶置随动柔性同步带系统原理动画</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Noto+Sans+SC:wght@400;500;700&display=swap');
:root {
--bg-color: #0b0e14;
--grid-color: rgba(30, 41, 59, 0.5);
--accent-cyan: #00f0ff;
--accent-amber: #ffaa00;
--accent-red: #ff2a6d;
--text-main: #e2e8f0;
--text-muted: #94a3b8;
--mech-dark: #1e293b;
--mech-light: #334155;
--glass-color: rgba(186, 230, 253, 0.2);
--glass-border: rgba(186, 230, 253, 0.8);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-color);
color: var(--text-main);
font-family: 'Noto Sans SC', sans-serif;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
background-image:
linear-gradient(var(--grid-color) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
background-size: 40px 40px;
background-position: center center;
}
/* Subtle noise texture */
body::before {
content: "";
position: fixed;
top: 0; left: 0; width: 100vw; height: 100vh;
background: url('data:image/svg+xml;utf8,%3Csvg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"%3E%3Cfilter id="noiseFilter"%3E%3CfeTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/%3E%3C/filter%3E%3Crect width="100%25" height="100%25" filter="url(%23noiseFilter)" opacity="0.03"/%3E%3C/svg%3E');
pointer-events: none;
z-index: 10;
}
#animation-container {
width: 100%;
max-width: 1400px;
height: 80vh;
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
svg {
width: 100%;
height: 100%;
filter: drop-shadow(0 0 20px rgba(0, 0, 0, 0.5));
}
/* Typography & Overlays */
.overlay-panel {
position: absolute;
background: rgba(11, 14, 20, 0.85);
border: 1px solid rgba(0, 240, 255, 0.2);
backdrop-filter: blur(8px);
padding: 16px;
border-radius: 8px;
z-index: 5;
pointer-events: none;
}
.panel-top-left { top: 20px; left: 20px; border-left: 3px solid var(--accent-cyan); }
.panel-bottom-right { bottom: 20px; right: 20px; border-right: 3px solid var(--accent-amber); text-align: right; }
.hud-text {
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
color: var(--accent-cyan);
letter-spacing: 1px;
margin-bottom: 4px;
text-transform: uppercase;
}
.title {
font-size: 18px;
font-weight: 700;
color: #fff;
margin-bottom: 8px;
}
.desc {
font-size: 13px;
color: var(--text-muted);
line-height: 1.5;
max-width: 300px;
}
.data-row {
display: flex;
justify-content: space-between;
margin-top: 8px;
font-size: 12px;
border-bottom: 1px dashed rgba(255,255,255,0.1);
padding-bottom: 4px;
}
.data-label { color: var(--text-muted); }
.data-value { font-family: 'JetBrains Mono', monospace; font-weight: 700; }
.val-cyan { color: var(--accent-cyan); }
.val-amber { color: var(--accent-amber); }
/* Status Indicator */
.status-indicator {
position: absolute;
top: 20px;
right: 20px;
display: flex;
align-items: center;
gap: 8px;
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
color: var(--text-main);
background: rgba(11, 14, 20, 0.8);
padding: 8px 12px;
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: var(--accent-cyan);
box-shadow: 0 0 8px var(--accent-cyan);
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; box-shadow: 0 0 8px var(--accent-cyan); }
50% { opacity: 0.4; box-shadow: 0 0 2px var(--accent-cyan); }
100% { opacity: 1; box-shadow: 0 0 8px var(--accent-cyan); }
}
.hidden { opacity: 0; transition: opacity 0.3s; }
.visible { opacity: 1; transition: opacity 0.3s; }
</style>
</head>
<body>
<div id="animation-container">
<!-- Top Left Info -->
<div class="overlay-panel panel-top-left">
<div class="hud-text">SYS.IFR.01 // Kinematic Visualization</div>
<div class="title">顶置随动柔性同步带系统</div>
<div class="desc">构建上下夹击的“汉堡包”式搬运架构,消除滚动摩擦产生的相对滑动。</div>
</div>
<!-- Bottom Right Telemetry -->
<div class="overlay-panel panel-bottom-right">
<div class="hud-text">Real-time Telemetry</div>
<div class="data-row">
<span class="data-label">V_top (顶部线速度)</span>
<span class="data-value val-cyan" id="v-top-display">0.000 m/s</span>
</div>
<div class="data-row">
<span class="data-label">V_bottom (底部线速度)</span>
<span class="data-value val-cyan" id="v-bot-display">0.000 m/s</span>
</div>
<div class="data-row">
<span class="data-label">ΔV (速度差)</span>
<span class="data-value val-amber">< 0.01 mm/s</span>
</div>
<div class="data-row">
<span class="data-label">PU海绵压缩量</span>
<span class="data-value val-amber" id="comp-display">0.0 mm</span>
</div>
<div class="data-row">
<span class="data-label">表面摩擦状态</span>
<span class="data-value val-cyan" id="friction-display">Static</span>
</div>
</div>
<!-- System Status -->
<div class="status-indicator">
<div class="status-dot"></div>
<span id="phase-status">System Standby</span>
</div>
<svg viewBox="0 0 1200 600" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- Gradients -->
<linearGradient id="beltGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#475569" />
<stop offset="50%" stop-color="#1e293b" />
<stop offset="100%" stop-color="#0f172a" />
</linearGradient>
<linearGradient id="glassGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(186, 230, 253, 0.4)" />
<stop offset="50%" stop-color="rgba(186, 230, 253, 0.1)" />
<stop offset="100%" stop-color="rgba(186, 230, 253, 0.3)" />
</linearGradient>
<!-- Patterns -->
<pattern id="spongePattern" width="10" height="10" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<line x1="0" y1="0" x2="0" y2="10" stroke="#d97706" stroke-width="2" opacity="0.4" />
</pattern>
<pattern id="beltTeeth" width="20" height="10" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="10" height="10" fill="#0f172a" />
<rect x="10" y="0" width="10" height="10" fill="#334155" />
</pattern>
<!-- Filters -->
<filter id="glowCyan" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="5" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="glowAmber" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feComponentTransfer in="blur" result="glow">
<feFuncR type="linear" slope="1.5" />
<feFuncG type="linear" slope="1" />
<feFuncB type="linear" slope="0" />
</feComponentTransfer>
<feComposite in="SourceGraphic" in2="glow" operator="over" />
</filter>
<!-- Arrow Marker -->
<marker id="arrowRed" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 10 5 L 0 9 z" fill="var(--accent-red)" />
</marker>
<marker id="arrowCyan" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 1 L 10 5 L 0 9 z" fill="var(--accent-cyan)" />
</marker>
</defs>
<!-- Grid Lines (Background Tech Details) -->
<g stroke="rgba(255,255,255,0.05)" stroke-width="1">
<line x1="100" y1="0" x2="100" y2="600" stroke-dasharray="4 4"/>
<line x1="1100" y1="0" x2="1100" y2="600" stroke-dasharray="4 4"/>
<line x1="0" y1="300" x2="1200" y2="300" stroke-dasharray="2 6"/>
</g>
<!-- Sync Mechanism (Background) -->
<g opacity="0.3">
<path d="M 150 150 L 150 450" stroke="var(--text-muted)" stroke-width="4" stroke-dasharray="8 4" />
<path d="M 1050 150 L 1050 450" stroke="var(--text-muted)" stroke-width="4" stroke-dasharray="8 4" />
<text x="160" y="300" fill="var(--text-muted)" font-family="JetBrains Mono" font-size="10" transform="rotate(-90 160 300)">PHYSICAL SYNC SHAFT</text>
</g>
<!-- Bottom Conveyor -->
<g id="bottom-system">
<!-- Pulleys -->
<circle cx="150" cy="450" r="40" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="4" />
<circle cx="1050" cy="450" r="40" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="4" />
<circle cx="150" cy="450" r="10" fill="var(--accent-cyan)" />
<circle cx="1050" cy="450" r="10" fill="var(--mech-light)" />
<!-- Rotating elements inside pulleys -->
<g id="pulley-spokes-bot-1" transform="translate(150, 450)">
<line x1="-30" y1="0" x2="30" y2="0" stroke="var(--mech-light)" stroke-width="4"/>
<line x1="0" y1="-30" x2="0" y2="30" stroke="var(--mech-light)" stroke-width="4"/>
</g>
<g id="pulley-spokes-bot-2" transform="translate(1050, 450)">
<line x1="-30" y1="0" x2="30" y2="0" stroke="var(--mech-light)" stroke-width="4"/>
<line x1="0" y1="-30" x2="0" y2="30" stroke="var(--mech-light)" stroke-width="4"/>
</g>
<!-- Bottom Belt Path -->
<path d="M 150 410 L 1050 410 A 40 40 0 0 1 1050 490 L 150 490 A 40 40 0 0 1 150 410 Z" fill="none" stroke="url(#beltGrad)" stroke-width="12" />
<!-- Animated Belt Teeth -->
<rect id="belt-bot-teeth" x="150" y="405" width="900" height="10" fill="url(#beltTeeth)" clip-path="url(#belt-clip)" />
</g>
<!-- Top Conveyor (Synchronized Flexible Belt) -->
<g id="top-system">
<!-- Pulleys -->
<circle cx="150" cy="150" r="40" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="4" />
<circle cx="1050" cy="150" r="40" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="4" />
<circle cx="150" cy="150" r="10" fill="var(--accent-cyan)" />
<circle cx="1050" cy="150" r="10" fill="var(--mech-light)" />
<g id="pulley-spokes-top-1" transform="translate(150, 150)">
<line x1="-30" y1="0" x2="30" y2="0" stroke="var(--mech-light)" stroke-width="4"/>
<line x1="0" y1="-30" x2="0" y2="30" stroke="var(--mech-light)" stroke-width="4"/>
</g>
<g id="pulley-spokes-top-2" transform="translate(1050, 150)">
<line x1="-30" y1="0" x2="30" y2="0" stroke="var(--mech-light)" stroke-width="4"/>
<line x1="0" y1="-30" x2="0" y2="30" stroke="var(--mech-light)" stroke-width="4"/>
</g>
<!-- Base Belt -->
<path d="M 150 110 L 1050 110 A 40 40 0 0 1 1050 190 L 150 190 A 40 40 0 0 1 150 110 Z" fill="none" stroke="url(#beltGrad)" stroke-width="12" />
<rect id="belt-top-teeth" x="150" y="185" width="900" height="10" fill="url(#beltTeeth)" />
<!-- Dynamic PU Sponge Layer -->
<!-- The path 'd' attribute will be updated via JS to simulate compression -->
<path id="pu-sponge" d="" fill="url(#spongePattern)" stroke="var(--accent-amber)" stroke-width="2" filter="url(#glowAmber)" opacity="0.9" />
<!-- Sponge Label -->
<text x="600" y="225" fill="var(--accent-amber)" font-family="Noto Sans SC" font-size="12" font-weight="700" text-anchor="middle" letter-spacing="2">高回弹聚氨酯(PU)柔性层</text>
</g>
<!-- The Glass Object -->
<g id="glass-group">
<!-- Subtle glow when clamped -->
<rect id="glass-glow" x="-10" y="385" width="220" height="30" fill="var(--accent-cyan)" opacity="0" filter="url(#glowCyan)" />
<rect id="glass-plate" x="0" y="390" width="200" height="20" fill="url(#glassGrad)" stroke="var(--glass-border)" stroke-width="2" />
<!-- Glass internal reflection lines -->
<line id="glass-refl-1" x1="10" y1="395" x2="190" y2="395" stroke="rgba(255,255,255,0.4)" stroke-width="1" />
<text id="glass-label" x="100" y="405" fill="#fff" font-family="JetBrains Mono" font-size="10" text-anchor="middle" dominant-baseline="middle">GLASS SUBSTRATE</text>
</g>
<!-- Vector Annotations (Dynamically positioned and shown) -->
<g id="vectors" class="hidden">
<!-- Top Velocity Arrow -->
<path id="v-top-arrow" d="M 0 375 L 60 375" stroke="var(--accent-red)" stroke-width="3" fill="none" marker-end="url(#arrowRed)" />
<text id="v-top-text" x="65" y="370" fill="var(--accent-red)" font-family="JetBrains Mono" font-size="12" font-weight="bold">v1</text>
<!-- Bottom Velocity Arrow -->
<path id="v-bot-arrow" d="M 0 425 L 60 425" stroke="var(--accent-cyan)" stroke-width="3" fill="none" marker-end="url(#arrowCyan)" />
<text id="v-bot-text" x="65" y="435" fill="var(--accent-cyan)" font-family="JetBrains Mono" font-size="12" font-weight="bold">v2</text>
<!-- Equal Sign -->
<text id="v-eq-text" x="45" y="405" fill="#fff" font-family="JetBrains Mono" font-size="14" font-weight="bold" text-anchor="middle" dominant-baseline="middle" filter="url(#glowCyan)">v1 = v2</text>
<!-- Pressure Arrows (Pointing inwards) -->
<g id="pressure-arrows" opacity="0.6">
<!-- Top down -->
<path d="M 100 370 L 100 385" stroke="var(--accent-amber)" stroke-width="2" marker-end="url(#arrowCyan)" />
<path d="M 150 370 L 150 385" stroke="var(--accent-amber)" stroke-width="2" marker-end="url(#arrowCyan)" />
<!-- Bot up -->
<path d="M 100 430 L 100 415" stroke="var(--accent-cyan)" stroke-width="2" marker-end="url(#arrowCyan)" />
<path d="M 150 430 L 150 415" stroke="var(--accent-cyan)" stroke-width="2" marker-end="url(#arrowCyan)" />
</g>
</g>
<!-- Macro Zoom Line (Indicating zero gap/compression) -->
<g id="macro-view" opacity="0">
<circle cx="0" cy="390" r="25" fill="none" stroke="var(--accent-amber)" stroke-width="1" stroke-dasharray="2 2" />
<line x1="0" y1="365" x2="30" y2="300" stroke="var(--accent-amber)" stroke-width="1" stroke-dasharray="2 2" />
<text x="35" y="295" fill="var(--accent-amber)" font-family="Noto Sans SC" font-size="11">接触面零相对滑动</text>
</g>
<!-- Entrance Funnel Indicator -->
<g id="funnel-ind" opacity="0">
<path d="M 140 370 Q 150 380 180 388" fill="none" stroke="var(--text-muted)" stroke-width="1.5" stroke-dasharray="3 3" marker-end="url(#arrowCyan)" />
<text x="130" y="360" fill="var(--text-muted)" font-family="Noto Sans SC" font-size="11">喇叭状倒角入口</text>
</g>
</svg>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
// -- Elements --
const sponge = document.getElementById('pu-sponge');
const glassGroup = document.getElementById('glass-group');
const glassPlate = document.getElementById('glass-plate');
const glassGlow = document.getElementById('glass-glow');
const spokesBot1 = document.getElementById('pulley-spokes-bot-1');
const spokesBot2 = document.getElementById('pulley-spokes-bot-2');
const spokesTop1 = document.getElementById('pulley-spokes-top-1');
const spokesTop2 = document.getElementById('pulley-spokes-top-2');
const beltBotTeeth = document.getElementById('belt-bot-teeth');
const beltTopTeeth = document.getElementById('belt-top-teeth');
const vectors = document.getElementById('vectors');
const pressureArrows = document.getElementById('pressure-arrows');
const vTopArrow = document.getElementById('v-top-arrow');
const vBotArrow = document.getElementById('v-bot-arrow');
const vTopText = document.getElementById('v-top-text');
const vBotText = document.getElementById('v-bot-text');
const vEqText = document.getElementById('v-eq-text');
const macroView = document.getElementById('macro-view');
const funnelInd = document.getElementById('funnel-ind');
const phaseStatus = document.getElementById('phase-status');
const vTopDisplay = document.getElementById('v-top-display');
const vBotDisplay = document.getElementById('v-bot-display');
const compDisplay = document.getElementById('comp-display');
const frictionDisplay = document.getElementById('friction-display');
// -- Configuration Math --
// Y coordinates
const botBeltY = 410; // Surface of bottom belt
const topBeltBaseY = 190; // Bottom of the rigid top belt
const glassHeight = 20;
const glassY = 390; // Top of glass (410 - 20)
// Sponge configurations
const spongeUncompressedY = 395; // Reaches down to 395 (leaves 5px gap to glass top mathematically, but visually we compress)
const spongeCompressedY = 390; // Compresses to glass top (390)
const maxCompression = spongeUncompressedY - spongeCompressedY; // 5 units visual
const spongeStartX = 150;
const spongeEndX = 1050;
const spongePathSegments = 100;
const spongeStep = (spongeEndX - spongeStartX) / spongePathSegments;
// Animation variables
let glassX = -250;
const glassWidth = 200;
const endX = 1250;
const baseVelocity = 3.5; // pixels per frame roughly
let time = 0;
// -- Functions --
// Generate the dynamic SVG path for the PU sponge
function updateSpongePath(currentGlassX) {
let path = `M ${spongeStartX} ${topBeltBaseY} L ${spongeEndX} ${topBeltBaseY}`; // Top rigid edge
let bottomPoints = [];
const chamferDist = 40; // Soft ramp for sponge compression
for (let i = spongePathSegments; i >= 0; i--) {
let px = spongeStartX + i * spongeStep;
let py = spongeUncompressedY;
let distToFront = currentGlassX - px;
let distToBack = px - (currentGlassX + glassWidth);
if (px >= currentGlassX && px <= currentGlassX + glassWidth) {
// Fully compressed over glass
py = spongeCompressedY;
} else if (distToFront > 0 && distToFront < chamferDist) {
// Ramp down at front
let ratio = distToFront / chamferDist;
// Use a smoothstep curve for nicer deformation
ratio = ratio * ratio * (3 - 2 * ratio);
py = spongeCompressedY + maxCompression * ratio;
} else if (distToBack > 0 && distToBack < chamferDist) {
// Ramp up at back
let ratio = distToBack / chamferDist;
ratio = ratio * ratio * (3 - 2 * ratio);
py = spongeCompressedY + maxCompression * ratio;
}
// Add slight random noise to uncompressed bottom to look like sponge
if (py === spongeUncompressedY) {
py += Math.sin(px * 0.5) * 0.5;
}
bottomPoints.push(`${px} ${py}`);
}
path += ` L ${spongeEndX} ${spongeUncompressedY}`; // Right edge down
path += ` L ${bottomPoints.join(' L ')}`; // The dynamic bottom edge
path += ` Z`;
sponge.setAttribute('d', path);
}
// Main Animation Loop
function animate() {
time++;
// Kinematics logic
glassX += baseVelocity;
// Reset loop
if (glassX > endX) {
glassX = -250;
}
// Center of glass for relative calculations
let gCenterX = glassX + glassWidth / 2;
// 1. Update Glass Position
glassGroup.setAttribute('transform', `translate(${glassX}, 0)`);
// 2. Update Sponge Deformation
updateSpongePath(glassX);
// 3. Rotate Pulleys and Move Belt Patterns
// Calculate rotation based on circumference (r=40, C=251.2)
let rotation = (glassX / 251.2) * 360;
spokesBot1.setAttribute('transform', `translate(150, 450) rotate(${rotation})`);
spokesBot2.setAttribute('transform', `translate(1050, 450) rotate(${rotation})`);
// Top pulleys counter-rotate (or same if mapped directly, visual aesthetic)
spokesTop1.setAttribute('transform', `translate(150, 150) rotate(${rotation})`);
spokesTop2.setAttribute('transform', `translate(1050, 150) rotate(${rotation})`);
// Move belt teeth pattern
let beltOffset = -(glassX % 20); // Width of pattern is 20
beltBotTeeth.setAttribute('x', 150 + beltOffset);
beltTopTeeth.setAttribute('x', 150 + beltOffset);
// 4. State Machine & Visual Directives (IFR focusing)
// State: Entering (Funnel)
if (gCenterX > 50 && gCenterX < 250) {
phaseStatus.innerText = "Phase 1: Infeed (Funnel)";
phaseStatus.style.color = "var(--text-main)";
funnelInd.setAttribute('opacity', (gCenterX - 50) / 100);
vTopDisplay.innerText = "1.500 m/s";
vBotDisplay.innerText = "1.500 m/s";
compDisplay.innerText = "1.2 mm";
frictionDisplay.innerText = "Engaging";
glassGlow.setAttribute('opacity', '0');
vectors.classList.add('hidden');
macroView.setAttribute('opacity', '0');
}
// State: Fully Engaged (The Ideal Final Result)
else if (gCenterX >= 250 && gCenterX <= 950) {
phaseStatus.innerText = "Phase 2: Sync Transport";
phaseStatus.style.color = "var(--accent-cyan)";
funnelInd.setAttribute('opacity', '0');
// Telemetry update
vTopDisplay.innerText = "1.500 m/s";
vBotDisplay.innerText = "1.500 m/s";
compDisplay.innerText = "3.0 mm (Target)";
frictionDisplay.innerText = "Zero Relative";
frictionDisplay.style.color = "var(--accent-cyan)";
// Show vectors attached to glass
vectors.classList.remove('hidden');
vectors.setAttribute('transform', `translate(${glassX + 60}, 0)`);
// Show macro zoom indicator at center
if (gCenterX > 400 && gCenterX < 800) {
macroView.setAttribute('opacity', '1');
macroView.setAttribute('transform', `translate(${gCenterX}, 0)`);
} else {
macroView.setAttribute('opacity', '0');
}
// Pulse glow to show clamping force
let glowIntensity = 0.5 + Math.sin(time * 0.1) * 0.2;
glassGlow.setAttribute('opacity', glowIntensity.toString());
}
// State: Exiting
else if (gCenterX > 950 && gCenterX < 1150) {
phaseStatus.innerText = "Phase 3: Outfeed";
phaseStatus.style.color = "var(--text-main)";
vectors.classList.add('hidden');
macroView.setAttribute('opacity', '0');
glassGlow.setAttribute('opacity', '0');
compDisplay.innerText = "Rebounding";
}
// Off-screen
else {
phaseStatus.innerText = "System Standby";
phaseStatus.style.color = "var(--text-muted)";
vTopDisplay.innerText = "0.000 m/s";
vBotDisplay.innerText = "0.000 m/s";
compDisplay.innerText = "0.0 mm";
frictionDisplay.innerText = "None";
frictionDisplay.style.color = "var(--text-muted)";
}
requestAnimationFrame(animate);
}
// Initialize
updateSpongePath(glassX);
requestAnimationFrame(animate);
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>顶置随动柔性同步带 - 零滑动物理原理可视化</title>
<style>
:root {
--bg-color: #050b14;
--grid-color: rgba(30, 58, 138, 0.2);
--glass-color: #06b6d4;
--glass-fill: rgba(6, 182, 212, 0.25);
--sponge-color: #f59e0b;
--sponge-fill: rgba(245, 158, 11, 0.65);
--belt-dark: #1e293b;
--belt-teeth: #334155;
--gear-color: #475569;
--vector-color: #ef4444;
--text-muted: #64748b;
--text-highlight: #10b981;
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color: var(--bg-color);
display: flex;
align-items: center;
justify-content: center;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
overflow: hidden;
color: #fff;
}
.canvas-container {
width: 100%;
max-width: 1200px;
aspect-ratio: 16/9;
position: relative;
box-shadow: 0 0 100px rgba(6, 182, 212, 0.05);
border: 1px solid rgba(255, 255, 255, 0.05);
background-image:
linear-gradient(var(--grid-color) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
background-size: 20px 20px;
background-position: center center;
border-radius: 8px;
}
svg {
width: 100%;
height: 100%;
display: block;
}
/* 边缘信息面板定位 */
.info-panel {
position: absolute;
font-size: 11px;
line-height: 1.6;
color: var(--text-muted);
pointer-events: none;
backdrop-filter: blur(4px);
background: rgba(5, 11, 20, 0.6);
padding: 8px 12px;
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 4px;
}
.info-top-left { top: 20px; left: 20px; }
.info-top-right { top: 20px; right: 20px; text-align: right; }
.info-bottom-left { bottom: 20px; left: 20px; }
.info-bottom-right { bottom: 20px; right: 20px; text-align: right; }
.highlight {
color: var(--text-highlight);
font-weight: bold;
}
.alert {
color: var(--vector-color);
}
.cyan-text {
color: var(--glass-color);
}
.amber-text {
color: var(--sponge-color);
}
.title {
font-size: 14px;
color: #e2e8f0;
margin-bottom: 4px;
letter-spacing: 1px;
}
</style>
</head>
<body>
<div class="canvas-container">
<!-- 边缘数据面板 -->
<div class="info-panel info-top-left">
<div class="title">IFR 系统状态监控 (最终理想解)</div>
<div>运转模式: 汉堡包式夹击同步传输</div>
<div>摩擦状态: <span class="highlight">绝对相对静止 (ΔV ≈ 0)</span></div>
</div>
<div class="info-panel info-top-right">
<div class="title">动态参数遥测</div>
<div>V_top (顶部海绵带): <span class="highlight">200.00 mm/s</span></div>
<div>V_bot (底部主控带): <span class="highlight">200.00 mm/s</span></div>
<div>速度同步偏差: <span class="highlight alert">< 0.01 mm/s</span></div>
</div>
<div class="info-panel info-bottom-left">
<div class="title">构架级配置参数</div>
<div>柔性面料: 聚氨酯(PU)高回弹海绵 <span class="amber-text">[15-20mm厚度]</span></div>
<div>形变吸收: 吸收2-3mm厚度公差与机械震动</div>
</div>
<div class="info-panel info-bottom-right">
<div class="title">表面保护机理</div>
<div>面压分布: 柔性面积广阔 <span class="cyan-text">[接触应力降至最低]</span></div>
<div>破坏因子消除: 滚动摩擦=0, 滑动擦伤=0</div>
</div>
<!-- 核心SVG可视化容器 -->
<svg id="main-svg" viewBox="0 0 1000 562.5" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 辉光滤镜 -->
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="glow-strong" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<!-- 玻璃材质渐变 -->
<linearGradient id="glass-grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="rgba(6, 182, 212, 0.4)" />
<stop offset="50%" stop-color="rgba(6, 182, 212, 0.1)" />
<stop offset="100%" stop-color="rgba(6, 182, 212, 0.5)" />
</linearGradient>
<!-- 海绵PU材质纹理 -->
<pattern id="sponge-pattern" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<circle cx="4" cy="4" r="1.5" fill="#d97706" opacity="0.4" />
</pattern>
<!-- 齿轮齿定义 -->
<g id="gear-teeth">
<path d="M -3,-25 L 3,-25 L 4,-20 L -4,-20 Z" fill="var(--gear-color)" />
<!-- JS将在此旋转复制生成完整齿轮 -->
</g>
<!-- 箭头标记 -->
<marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0,1 L 8,5 L 0,9 Z" fill="var(--vector-color)" />
</marker>
</defs>
<!-- 静态机械支架/背景元素 -->
<g id="structure" opacity="0.3">
<rect x="150" y="210" width="700" height="15" fill="#334155" rx="4" />
<rect x="150" y="335" width="700" height="15" fill="#334155" rx="4" />
<!-- 支撑柱 -->
<rect x="180" y="50" width="10" height="160" fill="#475569" />
<rect x="810" y="50" width="10" height="160" fill="#475569" />
<rect x="180" y="350" width="10" height="150" fill="#475569" />
<rect x="810" y="350" width="10" height="150" fill="#475569" />
</g>
<!-- 底部主控同步带组 -->
<g id="bottom-system">
<!-- 底部刚性导轨 -->
<rect x="150" y="320" width="700" height="15" fill="var(--belt-dark)" rx="7.5" />
<!-- 底部皮带运动层 (虚线模拟表面纹理) -->
<path id="bottom-belt-surface" d="M 150 320 L 850 320 M 850 335 L 150 335"
stroke="var(--belt-teeth)" stroke-width="4" stroke-dasharray="10 5" fill="none" />
<!-- 底部传动轮 -->
<g id="gear-bot-left" transform="translate(150, 327.5)">
<circle r="18" fill="var(--gear-color)" />
<circle r="8" fill="#1e293b" />
</g>
<g id="gear-bot-right" transform="translate(850, 327.5)">
<circle r="18" fill="var(--gear-color)" />
<circle r="8" fill="#1e293b" />
</g>
</g>
<!-- 顶部PU海绵同步带组 -->
<g id="top-system">
<!-- 顶部刚性基座带 -->
<rect x="150" y="225" width="700" height="15" fill="var(--belt-dark)" rx="7.5" />
<!-- 顶部皮带运动层 -->
<path id="top-belt-surface" d="M 150 225 L 850 225 M 850 240 L 150 240"
stroke="var(--belt-teeth)" stroke-width="4" stroke-dasharray="10 5" fill="none" />
<!-- 高回弹PU海绵层 (由JS动态计算形变路径) -->
<!-- 默认厚度 20mm (Y: 240 -> 260) -->
<path id="sponge-layer" d="" fill="var(--sponge-fill)" stroke="var(--sponge-color)" stroke-width="1" />
<!-- 覆盖纹理 -->
<path id="sponge-layer-pattern" d="" fill="url(#sponge-pattern)" style="pointer-events: none;" />
<!-- 顶部传动轮 -->
<g id="gear-top-left" transform="translate(150, 232.5)">
<circle r="18" fill="var(--gear-color)" />
<circle r="8" fill="#1e293b" />
</g>
<g id="gear-top-right" transform="translate(850, 232.5)">
<circle r="18" fill="var(--gear-color)" />
<circle r="8" fill="#1e293b" />
</g>
</g>
<!-- 工作载荷:玻璃面板 -->
<!-- 玻璃基准 Y:300(顶部),320(底部。紧贴底部皮带) -->
<g id="glass-sheet" transform="translate(-300, 0)">
<rect x="0" y="300" width="240" height="20" fill="url(#glass-grad)" stroke="var(--glass-color)" stroke-width="2" filter="url(#glow)" />
<line x1="10" y1="305" x2="230" y2="305" stroke="#fff" stroke-width="1" opacity="0.5" />
<line x1="10" y1="315" x2="230" y2="315" stroke="#fff" stroke-width="1" opacity="0.2" />
<text x="120" y="314" fill="#fff" font-size="12" text-anchor="middle" font-weight="bold" opacity="0.9">精密玻璃</text>
</g>
<!-- 动态分析覆层 (当玻璃进入中心区域时显示) -->
<g id="analysis-overlay" opacity="0">
<!-- 速度矢量分析 -->
<g id="vector-group" transform="translate(0, 0)">
<!-- 顶部矢量 -->
<line id="vec-top" x1="0" y1="285" x2="80" y2="285" stroke="var(--vector-color)" stroke-width="3" marker-end="url(#arrow)" filter="url(#glow-strong)"/>
<!-- 底部矢量 -->
<line id="vec-bot" x1="0" y1="335" x2="80" y2="335" stroke="var(--vector-color)" stroke-width="3" marker-end="url(#arrow)" filter="url(#glow-strong)"/>
<!-- 同步连线与文字 -->
<line x1="0" y1="285" x2="0" y2="335" stroke="var(--vector-color)" stroke-width="1" stroke-dasharray="3 3" opacity="0.6"/>
<rect x="-40" y="302" width="100" height="16" fill="rgba(5, 11, 20, 0.8)" rx="2"/>
<text x="10" y="314" fill="var(--text-highlight)" font-size="10" text-anchor="middle" font-weight="bold">v1 = v2</text>
</g>
<!-- 海绵压缩量标注 -->
<g id="compression-mark" transform="translate(0, 0)">
<line x1="-10" y1="260" x2="20" y2="260" stroke="var(--sponge-color)" stroke-width="1" />
<line x1="-10" y1="300" x2="20" y2="300" stroke="var(--sponge-color)" stroke-width="1" />
<line x1="5" y1="260" x2="5" y2="300" stroke="var(--sponge-color)" stroke-width="1" marker-start="url(#arrow)" marker-end="url(#arrow)" />
<rect x="15" y="272" width="60" height="16" fill="rgba(5, 11, 20, 0.8)" rx="2"/>
<text x="45" y="284" fill="var(--sponge-color)" font-size="10" text-anchor="middle">压缩: 3mm</text>
</g>
</g>
</svg>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// 初始化齿轮绘制
const gearTeethCount = 12;
const gearBase = document.getElementById('gear-teeth');
const pathNode = gearBase.querySelector('path');
for(let i=1; i<gearTeethCount; i++) {
const clone = pathNode.cloneNode();
clone.setAttribute('transform', `rotate(${i * (360/gearTeethCount)})`);
gearBase.appendChild(clone);
}
// 获取DOM元素
const bottomBeltSurf = document.getElementById('bottom-belt-surface');
const topBeltSurf = document.getElementById('top-belt-surface');
const spongeLayer = document.getElementById('sponge-layer');
const spongeLayerPat = document.getElementById('sponge-layer-pattern');
const glassSheet = document.getElementById('glass-sheet');
const gearTL = document.getElementById('gear-top-left');
const gearTR = document.getElementById('gear-top-right');
const gearBL = document.getElementById('gear-bot-left');
const gearBR = document.getElementById('gear-bot-right');
const analysisOverlay = document.getElementById('analysis-overlay');
const vectorGroup = document.getElementById('vector-group');
const compressionMark = document.getElementById('compression-mark');
// 物理常量配置
const duration = 7000; // 一个完整的动画周期(ms)
const vSpeed = 200; // 模拟速度 px/s
const pulleyRadius = 18;
const circumference = 2 * Math.PI * pulleyRadius; // ~113.1px
const omega = (vSpeed / circumference) * 360; // deg/s
const spongeRestingY = 260; // 海绵未压缩时的底边Y
const spongeBaseY = 240; // 海绵固定在皮带上的顶边Y
const spongeChamferWidth = 40; // 喇叭状倒角宽度
const glassWidth = 240;
const glassTopY = 300; // 玻璃顶边Y
const gapY = glassTopY - 250; // 需要压缩的深度计算
// 修正:此时海绵需压缩至Y=300,原resting可以设为Y=305(产生5mm虚拟压缩感以增强视觉)
const visualRestingY = 305;
const visualCompressedY = 300;
let isRunning = true;
// 核心渲染循环
function animate(time) {
if(!isRunning) return;
// 1. 计算系统基础运转动画 (永动)
const dashOffset = -(time / 1000) * vSpeed;
bottomBeltSurf.setAttribute('stroke-dashoffset', dashOffset);
topBeltSurf.setAttribute('stroke-dashoffset', dashOffset);
const currentRotation = (time / 1000) * omega;
gearTL.setAttribute('transform', `translate(150, 232.5) rotate(${currentRotation})`);
gearTR.setAttribute('transform', `translate(850, 232.5) rotate(${currentRotation})`);
gearBL.setAttribute('transform', `translate(150, 327.5) rotate(${currentRotation})`);
gearBR.setAttribute('transform', `translate(850, 327.5) rotate(${currentRotation})`);
// 2. 计算玻璃的相对位置
const t = (time % duration) / duration;
// 让玻璃从左侧场外(-300) 移动到右侧场外(1100)
const glassX = -350 + t * 1600;
glassSheet.setAttribute('transform', `translate(${glassX}, 0)`);
// 3. 计算PU海绵的动态形变路径 (核心破除矛盾与IFR体现)
let dPath = `M 150 ${spongeBaseY} `;
// 遍历构建底部轮廓
for(let x = 150; x <= 850; x += 10) {
// 默认深度 (包含喇叭口倒角逻辑)
let currentDepth = visualRestingY;
if(x < 150 + spongeChamferWidth) {
currentDepth = spongeBaseY + (visualRestingY - spongeBaseY) * ((x - 150) / spongeChamferWidth);
} else if (x > 850 - spongeChamferWidth) {
currentDepth = spongeBaseY + (visualRestingY - spongeBaseY) * ((850 - x) / spongeChamferWidth);
}
// 计算与玻璃的碰撞检测及柔性挤压
const gLeft = glassX;
const gRight = glassX + glassWidth;
const compressionRamp = 30; // 海绵响应玻璃边缘的渐变宽度
if (x >= gLeft && x <= gRight) {
currentDepth = Math.min(currentDepth, visualCompressedY);
} else if (x < gLeft && x >= gLeft - compressionRamp) {
let ratio = (gLeft - x) / compressionRamp;
let ease = 1 - ratio * ratio; // 简单缓动曲线平滑过渡
let compY = visualCompressedY + (visualRestingY - visualCompressedY) * ease;
currentDepth = Math.min(currentDepth, compY);
} else if (x > gRight && x <= gRight + compressionRamp) {
let ratio = (x - gRight) / compressionRamp;
let ease = 1 - ratio * ratio;
let compY = visualCompressedY + (visualRestingY - visualCompressedY) * ease;
currentDepth = Math.min(currentDepth, compY);
}
dPath += `L ${x} ${currentDepth} `;
}
// 闭合海绵路径
dPath += `L 850 ${spongeBaseY} Z`;
spongeLayer.setAttribute('d', dPath);
spongeLayerPat.setAttribute('d', dPath);
// 4. 分析覆层 (视觉引导 IFR)
// 当玻璃完全进入有效区域(X在200到600之间)时,显示同步矢量和微观数据
if (glassX > 250 && glassX < 550) {
analysisOverlay.setAttribute('opacity', '1');
// 让矢量组跟随玻璃运动中心
const centerPoint = glassX + glassWidth / 2;
vectorGroup.setAttribute('transform', `translate(${centerPoint - 40}, 0)`);
// 让形变标注跟随玻璃前沿
compressionMark.setAttribute('transform', `translate(${glassX + 60}, 0)`);
} else {
// 柔和淡出可使用CSS transition,这里用简单阈值
analysisOverlay.setAttribute('opacity', '0');
}
requestAnimationFrame(animate);
}
// 启动动画循环
requestAnimationFrame(animate);
// 确保页面切换或iframe重载时的稳定性
window.addEventListener('focus', () => {
if(!isRunning) {
isRunning = true;
requestAnimationFrame(animate);
}
});
});
</script>
</body>
</html>
<!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>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Noto+Sans+SC:wght@300;500;700&display=swap');
:root {
/* 赛博工业风配色方案 (Cyber-Industrial) */
--bg-base: #05070a;
--bg-grid: rgba(26, 36, 54, 0.6);
--hud-bg: rgba(8, 12, 18, 0.85);
--hud-border: rgba(0, 255, 157, 0.2);
--glass-cyan: #00e5ff;
--sponge-amber: #ffaa00;
--sync-green: #00ff9d;
--mech-dark: #121926;
--mech-light: #334155;
--text-main: #e2e8f0;
--text-dim: #64748b;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background-color: var(--bg-base);
color: var(--text-main);
font-family: 'Noto Sans SC', sans-serif;
width: 100vw;
height: 100vh;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
background-image:
linear-gradient(var(--bg-grid) 1px, transparent 1px),
linear-gradient(90deg, var(--bg-grid) 1px, transparent 1px);
background-size: 50px 50px;
background-position: center;
}
/* 动画主容器,保证巨大且居中 */
#sim-container {
width: 100%;
height: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
svg {
width: 100%;
height: 100%;
max-width: 1800px;
max-height: 1000px;
filter: drop-shadow(0 0 30px rgba(0,0,0,0.8));
}
/* 边缘HUD信息面板 - 严格控制字号和位置,绝不遮挡中心 */
.hud-panel {
position: absolute;
background: var(--hud-bg);
border: 1px solid var(--hud-border);
backdrop-filter: blur(10px);
padding: 16px;
border-radius: 6px;
pointer-events: auto; /* 允许交互 */
z-index: 10;
min-width: 260px;
}
.hud-top-left { top: 24px; left: 24px; border-top: 3px solid var(--glass-cyan); }
.hud-top-right { top: 24px; right: 24px; border-top: 3px solid var(--sync-green); }
.hud-bottom-center { bottom: 24px; left: 50%; transform: translateX(-50%); border-bottom: 3px solid var(--sponge-amber); display: flex; gap: 24px;}
.hud-title {
font-size: 13px;
font-weight: 700;
color: #fff;
margin-bottom: 6px;
letter-spacing: 1px;
text-transform: uppercase;
}
.hud-subtitle {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--sync-green);
margin-bottom: 12px;
opacity: 0.8;
}
.hud-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
padding: 6px 0;
border-bottom: 1px solid rgba(255,255,255,0.05);
}
.hud-row:last-child { border-bottom: none; }
.hud-label { color: var(--text-dim); }
.hud-value { font-family: 'JetBrains Mono', monospace; font-weight: 700; }
.val-cyan { color: var(--glass-cyan); }
.val-green { color: var(--sync-green); }
.val-amber { color: var(--sponge-amber); }
/* 交互控件 */
.control-group {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 200px;
}
.control-header {
display: flex;
justify-content: space-between;
font-size: 11px;
color: var(--text-dim);
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: rgba(255,255,255,0.1);
height: 4px;
border-radius: 2px;
outline: none;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--sync-green);
cursor: pointer;
box-shadow: 0 0 10px var(--sync-green);
}
/* SVG 内联文本样式 */
.svg-text { font-family: 'Noto Sans SC', sans-serif; font-size: 12px; fill: var(--text-dim); }
.svg-mono { font-family: 'JetBrains Mono', monospace; font-size: 10px; }
</style>
</head>
<body>
<div id="sim-container">
<!-- 左上:TRIZ 理想解概念 -->
<div class="hud-panel hud-top-left">
<div class="hud-title">最终理想解 (IFR) 架构</div>
<div class="hud-subtitle">TRIZ: 消除系统矛盾的最终状态</div>
<p style="font-size: 11px; color: var(--text-dim); line-height: 1.6; margin-bottom: 8px;">
突破传统压轮的局部线接触摩擦,<br>
构建<strong>“绝对相对静止”</strong>的汉堡包式面夹击。<br>
巧妙利用高回弹PU海绵进行微观形变补偿,<br>
在极低系统复杂度下彻底消除滑动擦伤。
</p>
<div class="hud-row">
<span class="hud-label">矛盾参数:</span>
<span class="hud-value" style="color:#ff4444;">表面损伤 vs 压紧力</span>
</div>
<div class="hud-row">
<span class="hud-label">IFR状态:</span>
<span class="hud-value val-green">接触点 ΔV 恒等于 0</span>
</div>
</div>
<!-- 右上:遥测数据面板 -->
<div class="hud-panel hud-top-right">
<div class="hud-title">系统实时遥测 (Telemetry)</div>
<div class="hud-subtitle">SYNC VECTOR MONITORING</div>
<div class="hud-row">
<span class="hud-label">底部主轴线速度 (V1)</span>
<span class="hud-value val-cyan" id="disp-v1">1.500 m/s</span>
</div>
<div class="hud-row">
<span class="hud-label">顶部伺服线速度 (V2)</span>
<span class="hud-value val-cyan" id="disp-v2">1.500 m/s</span>
</div>
<div class="hud-row">
<span class="hud-label">上下皮带速度差 (|V1-V2|)</span>
<span class="hud-value val-green">< 0.01 mm/s</span>
</div>
<div class="hud-row">
<span class="hud-label">瞬时滚动摩擦力</span>
<span class="hud-value val-green">0.00 N</span>
</div>
</div>
<!-- 底部:交互控制区 -->
<div class="hud-panel hud-bottom-center">
<div class="control-group">
<div class="control-header">
<span>主控线速度倍率</span>
<span id="disp-speed-val" class="val-green">1.0x</span>
</div>
<input type="range" id="ctrl-speed" min="0.2" max="3.0" step="0.1" value="1.0">
</div>
<div class="control-group">
<div class="control-header">
<span>PU 海绵压缩量模拟</span>
<span id="disp-comp-val" class="val-amber">3.0 mm</span>
</div>
<input type="range" id="ctrl-comp" min="1.0" max="8.0" step="0.5" value="3.0">
</div>
</div>
<!-- 核心视区 (高保真SVG) -->
<svg viewBox="0 0 1600 800" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- 材质渐变 -->
<linearGradient id="grad-glass" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(0, 229, 255, 0.4)" />
<stop offset="50%" stop-color="rgba(0, 229, 255, 0.05)" />
<stop offset="100%" stop-color="rgba(0, 229, 255, 0.2)" />
</linearGradient>
<linearGradient id="grad-belt" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#475569" />
<stop offset="50%" stop-color="#1e293b" />
<stop offset="100%" stop-color="#0f172a" />
</linearGradient>
<!-- PU海绵微观纹理 (网格+噪点感) -->
<pattern id="pat-sponge" width="8" height="8" patternUnits="userSpaceOnUse" patternTransform="rotate(15)">
<circle cx="4" cy="4" r="2" fill="#d97706" opacity="0.4" />
<path d="M0,0 L8,8 M8,0 L0,8" stroke="#b45309" stroke-width="0.5" opacity="0.3"/>
</pattern>
<!-- 齿形带纹理 -->
<pattern id="pat-teeth" width="24" height="12" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="12" height="12" fill="#0f172a" />
<rect x="12" y="0" width="12" height="12" fill="#334155" />
</pattern>
<!-- 光效滤镜 -->
<filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="glow-green" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<!-- 标记箭头 -->
<marker id="arrow-green" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="5" markerHeight="5" orient="auto">
<path d="M0,1 L10,5 L0,9 Z" fill="var(--sync-green)" />
</marker>
<marker id="arrow-amber" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto">
<path d="M0,2 L8,5 L0,8 Z" fill="var(--sponge-amber)" />
</marker>
</defs>
<!-- 坐标辅助线 (背景层) -->
<g stroke="rgba(255,255,255,0.05)" stroke-width="1">
<line x1="200" y1="0" x2="200" y2="800" stroke-dasharray="4 8"/>
<line x1="1400" y1="0" x2="1400" y2="800" stroke-dasharray="4 8"/>
<line x1="0" y1="400" x2="1600" y2="400" stroke-dasharray="10 10"/>
<text x="210" y="30" class="svg-mono">X:200.0 (IN-FEED ZONE)</text>
<text x="1410" y="30" class="svg-mono">X:1400.0 (OUT-FEED ZONE)</text>
<text x="10" y="390" class="svg-mono">Y:400.0 (SYNC AXIS)</text>
</g>
<!-- 底层机构:物理耦合/独立伺服轴线 -->
<g opacity="0.2">
<path d="M 250 200 L 250 600" stroke="var(--sync-green)" stroke-width="3" stroke-dasharray="10 5" />
<path d="M 1350 200 L 1350 600" stroke="var(--sync-green)" stroke-width="3" stroke-dasharray="10 5" />
</g>
<!-- =================底部主传输同步带================= -->
<g id="mech-bottom">
<!-- 支撑架 -->
<rect x="250" y="520" width="1100" height="20" fill="var(--mech-dark)" rx="10" />
<!-- 传动轮 -->
<circle cx="250" cy="530" r="50" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="6" />
<circle cx="1350" cy="530" r="50" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="6" />
<circle cx="250" cy="530" r="12" fill="var(--glass-cyan)" />
<!-- 轮辐 (用于旋转动画) -->
<g id="wheel-bot-L" transform="translate(250,530)">
<line x1="-35" y1="0" x2="35" y2="0" stroke="var(--mech-light)" stroke-width="6"/>
<line x1="0" y1="-35" x2="0" y2="35" stroke="var(--mech-light)" stroke-width="6"/>
</g>
<g id="wheel-bot-R" transform="translate(1350,530)">
<line x1="-35" y1="0" x2="35" y2="0" stroke="var(--mech-light)" stroke-width="6"/>
<line x1="0" y1="-35" x2="0" y2="35" stroke="var(--mech-light)" stroke-width="6"/>
</g>
<!-- 皮带本体 -->
<!-- 上表面 Y = 530 - 50 = 480 -->
<path d="M 250 480 L 1350 480 A 50 50 0 0 1 1400 530 A 50 50 0 0 1 1350 580 L 250 580 A 50 50 0 0 1 200 530 A 50 50 0 0 1 250 480 Z" fill="none" stroke="url(#grad-belt)" stroke-width="16" />
<!-- 运动齿纹 -->
<rect id="belt-teeth-bot" x="250" y="472" width="1100" height="16" fill="url(#pat-teeth)" />
<text x="800" y="620" class="svg-text" text-anchor="middle" fill="var(--text-dim)">底部刚性主同步带 (V1)</text>
</g>
<!-- =================顶部柔性跟随同步带================= -->
<g id="mech-top">
<!-- 支撑架 -->
<rect x="250" y="260" width="1100" height="20" fill="var(--mech-dark)" rx="10" />
<!-- 传动轮 -->
<circle cx="250" cy="270" r="50" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="6" />
<circle cx="1350" cy="270" r="50" fill="var(--mech-dark)" stroke="var(--mech-light)" stroke-width="6" />
<circle cx="1350" cy="270" r="12" fill="var(--sync-green)" /> <!-- 独立伺服标识 -->
<!-- 轮辐 -->
<g id="wheel-top-L" transform="translate(250,270)">
<line x1="-35" y1="0" x2="35" y2="0" stroke="var(--mech-light)" stroke-width="6"/>
<line x1="0" y1="-35" x2="0" y2="35" stroke="var(--mech-light)" stroke-width="6"/>
</g>
<g id="wheel-top-R" transform="translate(1350,270)">
<line x1="-35" y1="0" x2="35" y2="0" stroke="var(--mech-light)" stroke-width="6"/>
<line x1="0" y1="-35" x2="0" y2="35" stroke="var(--mech-light)" stroke-width="6"/>
</g>
<!-- 顶部刚性皮带本体 -->
<!-- 下表面 Y = 270 + 50 = 320 -->
<path d="M 250 220 L 1350 220 A 50 50 0 0 1 1400 270 A 50 50 0 0 1 1350 320 L 250 320 A 50 50 0 0 1 200 270 A 50 50 0 0 1 250 220 Z" fill="none" stroke="url(#grad-belt)" stroke-width="16" />
<rect id="belt-teeth-top" x="250" y="312" width="1100" height="16" fill="url(#pat-teeth)" />
<!-- 高回弹 PU 海绵层 (动态路径绘制) -->
<!-- 基准顶边: 320, 基准底边(未压缩): 380 (代表厚度60像素,约20mm比例) -->
<path id="sponge-path" d="" fill="url(#pat-sponge)" stroke="var(--sponge-amber)" stroke-width="1.5" opacity="0.9" />
<text x="800" y="200" class="svg-text" text-anchor="middle" fill="var(--text-dim)">顶置伺服/物理耦合同步带 (V2)</text>
<text x="800" y="350" class="svg-text" text-anchor="middle" fill="var(--sponge-amber)" font-weight="700">高回弹聚氨酯(PU)柔性层 15~20mm</text>
</g>
<!-- =================负载实体:精密玻璃================= -->
<g id="load-glass">
<!-- 玻璃本体,置于底层皮带表面 Y=480 上 -->
<!-- 高度 24px,所以顶面 Y = 456 -->
<!-- 注意:为了表现压缩,PU海绵未压缩底面设为 470,玻璃顶面设为 456,形成 14px 视觉压缩量 -->
<!-- 发光底层代表压紧受力状态 -->
<rect id="glass-glow" x="0" y="456" width="400" height="24" fill="var(--glass-cyan)" opacity="0" filter="url(#glow-cyan)" />
<rect id="glass-body" x="0" y="456" width="400" height="24" fill="url(#grad-glass)" stroke="rgba(0, 229, 255, 0.8)" stroke-width="2" />
<line x1="20" y1="462" x2="380" y2="462" stroke="#fff" stroke-width="1" opacity="0.4" />
<text x="200" y="473" class="svg-text" fill="#fff" text-anchor="middle" dominant-baseline="middle" font-weight="700" letter-spacing="2">精密玻璃基板</text>
<!-- 相对速度矢量线 (绑定在玻璃上移动,突出零滑动) -->
<g id="vector-arrows" opacity="0">
<!-- 上方同步矢量 -->
<line x1="200" y1="440" x2="300" y2="440" stroke="var(--sync-green)" stroke-width="3" marker-end="url(#arrow-green)" filter="url(#glow-green)"/>
<text x="210" y="432" class="svg-mono" fill="var(--sync-green)" font-weight="700">V_top</text>
<!-- 下方同步矢量 -->
<line x1="200" y1="496" x2="300" y2="496" stroke="var(--sync-green)" stroke-width="3" marker-end="url(#arrow-green)" filter="url(#glow-green)"/>
<text x="210" y="512" class="svg-mono" fill="var(--sync-green)" font-weight="700">V_bot</text>
<!-- 绝对静止连线 -->
<line x1="200" y1="440" x2="200" y2="496" stroke="var(--sync-green)" stroke-width="2" stroke-dasharray="4 4"/>
<rect x="230" y="458" width="80" height="20" fill="var(--bg-base)" rx="4" stroke="var(--sync-green)" stroke-width="1"/>
<text x="270" y="472" class="svg-mono" fill="var(--sync-green)" text-anchor="middle" font-weight="bold">ΔV = 0</text>
</g>
<!-- 压缩量标注 -->
<g id="comp-annotation" opacity="0">
<line x1="-30" y1="456" x2="0" y2="456" stroke="var(--sponge-amber)" stroke-width="1" stroke-dasharray="2 2"/>
<line x1="-30" y1="470" x2="0" y2="470" stroke="var(--sponge-amber)" stroke-width="1" stroke-dasharray="2 2"/>
<line x1="-15" y1="456" x2="-15" y2="470" stroke="var(--sponge-amber)" stroke-width="2" marker-start="url(#arrow-amber)" marker-end="url(#arrow-amber)"/>
<text x="-35" y="468" class="svg-mono" fill="var(--sponge-amber)" text-anchor="end" id="comp-text">3mm压缩</text>
</g>
</g>
<!-- 喇叭状倒角入口指示 (静态,高亮区域) -->
<g id="funnel-indicator" opacity="0.6">
<path d="M 180 390 Q 250 400 280 430" fill="none" stroke="var(--text-dim)" stroke-width="2" stroke-dasharray="5 5" marker-end="url(#arrow-amber)" />
<text x="180" y="380" class="svg-text" text-anchor="middle">帧1:喇叭状倒角引导入口</text>
</g>
</svg>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
// == DOM 元素获取 ==
const glassGroup = document.getElementById('load-glass');
const glassGlow = document.getElementById('glass-glow');
const spongePath = document.getElementById('sponge-path');
const beltTeethTop = document.getElementById('belt-teeth-top');
const beltTeethBot = document.getElementById('belt-teeth-bot');
const wTopL = document.getElementById('wheel-top-L');
const wTopR = document.getElementById('wheel-top-R');
const wBotL = document.getElementById('wheel-bot-L');
const wBotR = document.getElementById('wheel-bot-R');
const vectorArrows = document.getElementById('vector-arrows');
const compAnnotation = document.getElementById('comp-annotation');
const funnelIndicator = document.getElementById('funnel-indicator');
const compText = document.getElementById('comp-text');
// 控件与遥测面板
const ctrlSpeed = document.getElementById('ctrl-speed');
const ctrlComp = document.getElementById('ctrl-comp');
const dispSpeedVal = document.getElementById('disp-speed-val');
const dispCompVal = document.getElementById('disp-comp-val');
const dispV1 = document.getElementById('disp-v1');
const dispV2 = document.getElementById('disp-v2');
// == 系统物理常量 ==
const simWidth = 1600;
const glassWidth = 400;
const topBeltY = 320; // 刚性皮带底边
const uncompressedY = 470; // 海绵未压缩状态底边
const glassTopY = 456; // 玻璃表面Y (底层皮带 Y480 - 玻璃厚度24)
const spongeStartX = 250;
const spongeEndX = 1350;
const wheelRadius = 50;
const circumference = 2 * Math.PI * wheelRadius;
// == 状态变量 ==
let glassX = -500;
let baseVelocity = 4; // px/frame
let currentCompression = parseFloat(ctrlComp.value);
// 控件监听
ctrlSpeed.addEventListener('input', (e) => {
dispSpeedVal.innerText = parseFloat(e.target.value).toFixed(1) + 'x';
});
ctrlComp.addEventListener('input', (e) => {
currentCompression = parseFloat(e.target.value);
dispCompVal.innerText = currentCompression.toFixed(1) + ' mm';
compText.innerText = currentCompression.toFixed(1) + 'mm 压缩';
});
// == 核心计算函数 ==
// 动态生成 PU 海绵的形变路径
function updateSpongeDeformation(gX) {
// 视觉映射:滑块3mm对应实际14px的视觉压缩(470-456=14)。
// 这里进行比例转换,让滑块动态改变视觉压缩深度。
// 假设 max compression = 8mm -> 24px, 1mm -> 3px.
const visualCompression = currentCompression * 3;
const dynamicUncompressedY = glassTopY + visualCompression; // 抬高未压缩底边以适应滑块
let d = `M ${spongeStartX} ${topBeltY} `;
const steps = 100;
const stepWidth = (spongeEndX - spongeStartX) / steps;
// 下边缘生成
let bottomPoints = [];
for(let i=0; i<=steps; i++) {
let px = spongeStartX + i * stepWidth;
let py = dynamicUncompressedY;
// 1. 进出料两端的喇叭状倒角 (自然缓和形变)
const chamferLen = 80;
if (px < spongeStartX + chamferLen) {
let ratio = (px - spongeStartX) / chamferLen;
py = topBeltY + (dynamicUncompressedY - topBeltY) * Math.sin(ratio * Math.PI / 2);
} else if (px > spongeEndX - chamferLen) {
let ratio = (spongeEndX - px) / chamferLen;
py = topBeltY + (dynamicUncompressedY - topBeltY) * Math.sin(ratio * Math.PI / 2);
}
// 2. 玻璃挤压逻辑 (核心破矛盾机制)
const gLeft = gX;
const gRight = gX + glassWidth;
const ramp = 40; // 海绵形变的平滑坡度
if (px >= gLeft && px <= gRight) {
// 绝对面接触,压平到玻璃表面
py = Math.min(py, glassTopY);
} else if (px >= gLeft - ramp && px < gLeft) {
// 前端挤压坡度
let ratio = (px - (gLeft - ramp)) / ramp;
// Smoothstep ease
ratio = ratio * ratio * (3 - 2 * ratio);
let targetY = dynamicUncompressedY - (dynamicUncompressedY - glassTopY) * ratio;
py = Math.min(py, targetY);
} else if (px > gRight && px <= gRight + ramp) {
// 后端回弹坡度
let ratio = ((gRight + ramp) - px) / ramp;
ratio = ratio * ratio * (3 - 2 * ratio);
let targetY = dynamicUncompressedY - (dynamicUncompressedY - glassTopY) * ratio;
py = Math.min(py, targetY);
}
bottomPoints.push(`${px},${py}`);
}
d += `L ${spongeEndX} ${topBeltY} `;
// 反向连接下边缘形成闭合多边形
for(let i = bottomPoints.length - 1; i >= 0; i--) {
d += `L ${bottomPoints[i]} `;
}
d += "Z";
spongePath.setAttribute('d', d);
}
// == 主动画循环 ==
let lastTime = 0;
function animate(time) {
// 帧率独立化计算基础
const deltaTime = time - lastTime;
lastTime = time;
// 应用速度倍率
let speedMult = parseFloat(ctrlSpeed.value);
let currentVel = baseVelocity * speedMult;
// 推进玻璃位置
glassX += currentVel;
if(glassX > simWidth + 200) {
glassX = -600; // 循环
}
const gCenterX = glassX + glassWidth / 2;
// 1. 移动载荷
glassGroup.setAttribute('transform', `translate(${glassX}, 0)`);
// 2. 更新海绵形变(IFR核心展示)
updateSpongeDeformation(glassX);
// 3. 同步运动机构 (车轮和皮带纹理)
// 距离转角度
let rotation = (glassX / circumference) * 360;
wTopL.setAttribute('transform', `translate(250,270) rotate(${rotation})`);
wTopR.setAttribute('transform', `translate(1350,270) rotate(${rotation})`);
wBotL.setAttribute('transform', `translate(250,530) rotate(${rotation})`);
wBotR.setAttribute('transform', `translate(1350,530) rotate(${rotation})`);
// 纹理平移 (使用取模保持在 pattern width = 24 内无缝循环)
let beltOffset = -(glassX % 24);
beltTeethTop.setAttribute('x', 250 + beltOffset);
beltTeethBot.setAttribute('x', 250 + beltOffset);
// 4. 时序逻辑与视觉引导控制
// 进料前
if (glassX < 150) {
funnelIndicator.setAttribute('opacity', '0.6');
vectorArrows.setAttribute('opacity', '0');
compAnnotation.setAttribute('opacity', '0');
glassGlow.setAttribute('opacity', '0');
}
// 咬合瞬态 & 高速稳定运输区 (展示理想解状态)
else if (glassX >= 150 && glassX <= 1000) {
funnelIndicator.setAttribute('opacity', '0');
// 淡入矢量与标注
let fade = Math.min((glassX - 150) / 100, 1);
vectorArrows.setAttribute('opacity', fade.toString());
compAnnotation.setAttribute('opacity', fade.toString());
// 施加面压时的辉光闪烁感
let pulse = 0.4 + 0.2 * Math.sin(time / 200);
glassGlow.setAttribute('opacity', (fade * pulse).toString());
}
// 出料与回弹
else {
vectorArrows.setAttribute('opacity', '0');
compAnnotation.setAttribute('opacity', '0');
glassGlow.setAttribute('opacity', '0');
}
// 5. 更新遥测数字面板
let realSpeed = (1.5 * speedMult).toFixed(3);
dispV1.innerText = `${realSpeed} m/s`;
dispV2.innerText = `${realSpeed} m/s`;
requestAnimationFrame(animate);
}
// 初始化启动
requestAnimationFrame(animate);
});
</script>
</body>
</html>
<!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>
<style>
/* ==========================================================================
Aesthetic Theme: Precision Industrial / Cleanroom Telemetry
Tone: Dark, High-contrast, Analytical, Technical
========================================================================== */
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=Noto+Sans+SC:wght@400;500;700&display=swap');
:root {
--bg-base: #06090e;
--grid-line: rgba(30, 41, 59, 0.6);
--cyan-glow: #00f0ff;
--cyan-dim: rgba(0, 240, 255, 0.2);
--orange-alert: #ffaa00;
--orange-dim: rgba(255, 170, 0, 0.2);
--green-safe: #00ff88;
--red-danger: #ff2a6d;
--mech-dark: #0f172a;
--mech-mid: #1e293b;
--mech-light: #334155;
--text-main: #e2e8f0;
--text-muted: #64748b;
--panel-bg: rgba(6, 9, 14, 0.85);
--panel-border: rgba(51, 65, 85, 0.8);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-base);
color: var(--text-main);
font-family: 'Noto Sans SC', sans-serif;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
background-image:
linear-gradient(var(--grid-line) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
background-size: 30px 30px;
background-position: center center;
}
/* Ambient scanline overlay */
body::after {
content: "";
position: fixed;
top: 0; left: 0; width: 100vw; height: 100vh;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.1) 50%);
background-size: 100% 4px;
pointer-events: none;
z-index: 999;
opacity: 0.3;
}
#app-container {
width: 100%;
max-width: 1440px;
height: 85vh;
position: relative;
display: flex;
justify-content: center;
align-items: center;
box-shadow: inset 0 0 100px rgba(0, 240, 255, 0.05);
border: 1px solid var(--panel-border);
border-radius: 8px;
background: radial-gradient(circle at center, rgba(30, 41, 59, 0.2) 0%, transparent 70%);
}
/* --------------------------------------------------------------------------
HUD & Data Panels (Strict spatial control, small fonts)
-------------------------------------------------------------------------- */
.hud-panel {
position: absolute;
background: var(--panel-bg);
backdrop-filter: blur(10px);
border: 1px solid var(--panel-border);
padding: 12px 16px;
border-radius: 6px;
z-index: 10;
font-size: 12px;
pointer-events: none;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
}
.hud-title {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 8px;
border-bottom: 1px solid rgba(255,255,255,0.1);
padding-bottom: 4px;
}
.top-left { top: 20px; left: 20px; border-left: 3px solid var(--cyan-glow); }
.top-right { top: 20px; right: 20px; border-right: 3px solid var(--green-safe); text-align: right; }
.bottom-right { bottom: 20px; right: 20px; border-right: 3px solid var(--orange-alert); }
.data-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
margin-bottom: 6px;
}
.data-label { color: var(--text-main); }
.data-value {
font-family: 'JetBrains Mono', monospace;
font-weight: 700;
}
.val-cyan { color: var(--cyan-glow); }
.val-green { color: var(--green-safe); }
.val-orange { color: var(--orange-alert); }
/* --------------------------------------------------------------------------
Interactive Control Panel
-------------------------------------------------------------------------- */
.control-panel {
position: absolute;
bottom: 20px;
left: 20px;
background: var(--panel-bg);
backdrop-filter: blur(10px);
border: 1px solid var(--panel-border);
border-left: 3px solid var(--text-muted);
padding: 12px 16px;
border-radius: 6px;
z-index: 20;
display: flex;
flex-direction: column;
gap: 12px;
width: 260px;
}
.control-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.control-header {
display: flex;
justify-content: space-between;
font-size: 11px;
color: var(--text-main);
}
.control-val-display {
font-family: 'JetBrains Mono', monospace;
color: var(--cyan-glow);
}
/* Custom Range Slider */
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
}
input[type=range]:focus { outline: none; }
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
background: var(--mech-light);
border-radius: 2px;
}
input[type=range]::-webkit-slider-thumb {
height: 12px;
width: 12px;
border-radius: 50%;
background: var(--cyan-glow);
cursor: pointer;
-webkit-appearance: none;
margin-top: -4px;
box-shadow: 0 0 10px var(--cyan-dim);
transition: transform 0.1s;
}
input[type=range]::-webkit-slider-thumb:hover {
transform: scale(1.2);
}
/* --------------------------------------------------------------------------
SVG Canvas
-------------------------------------------------------------------------- */
svg {
width: 100%;
height: 100%;
display: block;
}
/* Transitions for smooth data updates */
#ui-status-text { transition: color 0.3s ease; }
</style>
</head>
<body>
<div id="app-container">
<!-- Top Left: Concept Identity -->
<div class="hud-panel top-left">
<div class="hud-title">System Concept // IFR Architecture</div>
<div style="font-size: 14px; font-weight: 700; margin-bottom: 4px;">顶置随动柔性同步带系统</div>
<div style="color: var(--text-muted); max-width: 250px; line-height: 1.4;">
构建“汉堡包”式双轴同步搬运,通过柔性接触面消除一切相对滑动,达成表面零损伤的最终理想解。
</div>
</div>
<!-- Top Right: Kinematic Telemetry -->
<div class="hud-panel top-right">
<div class="hud-title">Live Kinematics // Zero Slip</div>
<div class="data-row">
<span class="data-label">主伺服速度 (V_bot):</span>
<span class="data-value val-cyan" id="disp-v-bot">0.000 mm/s</span>
</div>
<div class="data-row">
<span class="data-label">随动轴速度 (V_top):</span>
<span class="data-value val-cyan" id="disp-v-top">0.000 mm/s</span>
</div>
<div class="data-row" style="margin-top: 4px; border-top: 1px dashed rgba(255,255,255,0.1); padding-top: 4px;">
<span class="data-label">运转速度差 (|ΔV|):</span>
<span class="data-value val-green">< 0.01 mm/s</span>
</div>
<div class="data-row">
<span class="data-label">界面摩擦力状态:</span>
<span class="data-value val-green" id="disp-friction">绝对静止</span>
</div>
</div>
<!-- Bottom Right: Mechanical State -->
<div class="hud-panel bottom-right">
<div class="hud-title">Material Physics // PU Sponge</div>
<div class="data-row">
<span class="data-label">海绵基础厚度:</span>
<span class="data-value val-orange">20.0 mm</span>
</div>
<div class="data-row">
<span class="data-label">当前绝对压缩量:</span>
<span class="data-value val-orange" id="disp-comp">0.0 mm</span>
</div>
<div class="data-row">
<span class="data-label">面接触压强分布:</span>
<span class="data-value val-cyan">均匀柔性</span>
</div>
</div>
<!-- Bottom Left: Interactive Controls -->
<div class="control-panel">
<div class="hud-title" style="margin-bottom: 12px;">System Override // Variables</div>
<div class="control-group">
<div class="control-header">
<span>系统输送线速度</span>
<span class="control-val-display" id="val-speed">1.5x</span>
</div>
<input type="range" id="ctrl-speed" min="0" max="3" step="0.1" value="1.5">
</div>
<div class="control-group">
<div class="control-header">
<span>玻璃基板厚度 (模拟公差)</span>
<span class="control-val-display" id="val-thickness">4.0 mm</span>
</div>
<input type="range" id="ctrl-thickness" min="1" max="6" step="0.1" value="4.0">
</div>
</div>
<!-- MAIN SVG CANVAS -->
<svg id="main-svg" viewBox="0 0 1200 600" preserveAspectRatio="xMidYMid meet">
<defs>
<!-- Patterns -->
<pattern id="sponge-pattern" width="12" height="12" patternUnits="userSpaceOnUse" patternTransform="rotate(15)">
<circle cx="6" cy="6" r="1.5" fill="var(--orange-alert)" opacity="0.3" />
<circle cx="2" cy="2" r="1" fill="var(--orange-alert)" opacity="0.2" />
<circle cx="10" cy="10" r="2" fill="var(--orange-alert)" opacity="0.15" />
</pattern>
<pattern id="belt-teeth" width="16" height="8" patternUnits="userSpaceOnUse">
<rect x="0" y="0" width="8" height="8" fill="var(--mech-dark)" />
<rect x="8" y="0" width="8" height="8" fill="var(--mech-light)" />
</pattern>
<pattern id="glass-grid" width="20" height="20" patternUnits="userSpaceOnUse">
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="rgba(255,255,255,0.05)" stroke-width="1" />
</pattern>
<!-- Gradients -->
<linearGradient id="glass-grad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(0, 240, 255, 0.4)" />
<stop offset="20%" stop-color="rgba(0, 240, 255, 0.1)" />
<stop offset="80%" stop-color="rgba(0, 240, 255, 0.1)" />
<stop offset="100%" stop-color="rgba(0, 240, 255, 0.3)" />
</linearGradient>
<linearGradient id="belt-base-grad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#475569" />
<stop offset="50%" stop-color="#1e293b" />
<stop offset="100%" stop-color="#0f172a" />
</linearGradient>
<!-- Filters -->
<filter id="glow-cyan" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="4" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="glow-orange" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<!-- Markers -->
<marker id="arrow-cyan" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 2 L 10 5 L 0 8 z" fill="var(--cyan-glow)" />
</marker>
<marker id="arrow-orange" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 2 L 10 5 L 0 8 z" fill="var(--orange-alert)" />
</marker>
</defs>
<!-- Background Engineering Grid Details -->
<g opacity="0.15">
<line x1="200" y1="0" x2="200" y2="600" stroke="#fff" stroke-dasharray="4 4" />
<line x1="1000" y1="0" x2="1000" y2="600" stroke="#fff" stroke-dasharray="4 4" />
<text x="210" y="50" fill="#fff" font-family="JetBrains Mono" font-size="10">ENTRANCE BOUNDARY</text>
<text x="1010" y="50" fill="#fff" font-family="JetBrains Mono" font-size="10">EXIT BOUNDARY</text>
<!-- Centerline -->
<line x1="0" y1="300" x2="1200" y2="300" stroke="var(--cyan-glow)" stroke-dasharray="10 10" opacity="0.5" />
</g>
<!-- Mechanism Background Linkage -->
<g opacity="0.4">
<!-- Physical sync shaft representation -->
<path d="M 150 150 L 150 450" stroke="var(--text-muted)" stroke-width="2" stroke-dasharray="4 4" />
<path d="M 1050 150 L 1050 450" stroke="var(--text-muted)" stroke-width="2" stroke-dasharray="4 4" />
<rect x="146" y="280" width="8" height="40" fill="var(--mech-light)" />
<rect x="1046" y="280" width="8" height="40" fill="var(--mech-light)" />
<text x="160" y="305" fill="var(--text-muted)" font-family="JetBrains Mono" font-size="9">GEAR/SHAFT SYNC</text>
</g>
<!-- ================= BOTTOM ASSEMBLY ================= -->
<g id="bottom-assembly" transform="translate(0, 0)">
<!-- Belt path structure -->
<rect x="150" y="375" width="900" height="15" fill="url(#belt-base-grad)" rx="7.5" />
<rect id="belt-bot-anim" x="150" y="367" width="900" height="8" fill="url(#belt-teeth)" />
<!-- Pulleys -->
<g transform="translate(150, 382.5)">
<circle r="35" fill="var(--mech-mid)" stroke="var(--mech-dark)" stroke-width="4" />
<circle r="25" fill="var(--mech-light)" />
<g id="pulley-bot-l"><line x1="-25" y1="0" x2="25" y2="0" stroke="var(--mech-dark)" stroke-width="6"/><line x1="0" y1="-25" x2="0" y2="25" stroke="var(--mech-dark)" stroke-width="6"/></g>
<circle r="8" fill="var(--cyan-glow)" filter="url(#glow-cyan)" opacity="0.5"/>
</g>
<g transform="translate(1050, 382.5)">
<circle r="35" fill="var(--mech-mid)" stroke="var(--mech-dark)" stroke-width="4" />
<circle r="25" fill="var(--mech-light)" />
<g id="pulley-bot-r"><line x1="-25" y1="0" x2="25" y2="0" stroke="var(--mech-dark)" stroke-width="6"/><line x1="0" y1="-25" x2="0" y2="25" stroke="var(--mech-dark)" stroke-width="6"/></g>
<circle r="8" fill="var(--text-muted)"/>
</g>
<text x="150" y="440" fill="var(--text-muted)" font-family="JetBrains Mono" font-size="10" text-anchor="middle">MAIN SERVO</text>
</g>
<!-- ================= TOP ASSEMBLY ================= -->
<g id="top-assembly" transform="translate(0, 0)">
<!-- Rigid belt backing -->
<rect x="150" y="210" width="900" height="15" fill="url(#belt-base-grad)" rx="7.5" />
<rect id="belt-top-anim" x="150" y="225" width="900" height="8" fill="url(#belt-teeth)" />
<!-- Dynamic PU Sponge Layer (Rendered via JS) -->
<!-- The top flat line is at Y=233 (225 + 8) -->
<!-- The uncompressed bottom line is at Y=263 (30mm thickness logic mapped to 20mm physical) -->
<path id="pu-sponge" d="" fill="var(--orange-dim)" stroke="var(--orange-alert)" stroke-width="1.5" />
<path id="pu-sponge-pattern" d="" fill="url(#sponge-pattern)" style="pointer-events: none; mix-blend-mode: overlay;" />
<!-- Pulleys -->
<g transform="translate(150, 217.5)">
<circle r="35" fill="var(--mech-mid)" stroke="var(--mech-dark)" stroke-width="4" />
<circle r="25" fill="var(--mech-light)" />
<g id="pulley-top-l"><line x1="-25" y1="0" x2="25" y2="0" stroke="var(--mech-dark)" stroke-width="6"/><line x1="0" y1="-25" x2="0" y2="25" stroke="var(--mech-dark)" stroke-width="6"/></g>
<circle r="8" fill="var(--text-muted)"/>
</g>
<g transform="translate(1050, 217.5)">
<circle r="35" fill="var(--mech-mid)" stroke="var(--mech-dark)" stroke-width="4" />
<circle r="25" fill="var(--mech-light)" />
<g id="pulley-top-r"><line x1="-25" y1="0" x2="25" y2="0" stroke="var(--mech-dark)" stroke-width="6"/><line x1="0" y1="-25" x2="0" y2="25" stroke="var(--mech-dark)" stroke-width="6"/></g>
<circle r="8" fill="var(--text-muted)"/>
</g>
<text x="150" y="160" fill="var(--text-muted)" font-family="JetBrains Mono" font-size="10" text-anchor="middle">SYNC FOLLOWER</text>
</g>
<!-- ================= THE GLASS (Payload) ================= -->
<!-- Glass moves left to right. Bottom rests on bottom belt (Y=367) -->
<g id="glass-group" transform="translate(-300, 0)">
<!-- Glowing contact surface effect (top) -->
<rect id="glass-contact-glow" x="-5" y="0" width="260" height="4" fill="var(--cyan-glow)" filter="url(#glow-cyan)" opacity="0" />
<!-- Main Glass Body -->
<!-- Y and Height will be updated by JS based on thickness -->
<rect id="glass-body" x="0" y="347" width="250" height="20" fill="url(#glass-grad)" stroke="var(--cyan-glow)" stroke-width="1.5" />
<rect id="glass-pattern" x="0" y="347" width="250" height="20" fill="url(#glass-grid)" opacity="0.3" pointer-events="none" />
<text id="glass-label" x="125" y="360" fill="#fff" font-family="Noto Sans SC" font-size="11" font-weight="700" text-anchor="middle" dominant-baseline="middle" letter-spacing="2">GLASS</text>
</g>
<!-- ================= DYNAMIC ANNOTATIONS (Vectors & Highlights) ================= -->
<g id="annotations" opacity="0" style="transition: opacity 0.3s ease;">
<!-- Top Vector -->
<g id="vec-top-group">
<path d="M -40 0 L 30 0" stroke="var(--orange-alert)" stroke-width="2" fill="none" marker-end="url(#arrow-orange)" filter="url(#glow-orange)"/>
<text x="-45" y="4" fill="var(--orange-alert)" font-family="JetBrains Mono" font-size="11" text-anchor="end">v1</text>
</g>
<!-- Bottom Vector -->
<g id="vec-bot-group">
<path d="M -40 0 L 30 0" stroke="var(--cyan-glow)" stroke-width="2" fill="none" marker-end="url(#arrow-cyan)" filter="url(#glow-cyan)"/>
<text x="-45" y="4" fill="var(--cyan-glow)" font-family="JetBrains Mono" font-size="11" text-anchor="end">v2</text>
</g>
<!-- Equals Sign -->
<g id="vec-equals">
<line x1="-15" y1="-8" x2="-15" y2="8" stroke="#fff" stroke-width="1" stroke-dasharray="2 2" opacity="0.5"/>
<text x="-15" y="0" fill="#fff" font-family="JetBrains Mono" font-size="12" font-weight="bold" text-anchor="middle" dominant-baseline="middle" filter="url(#glow-cyan)">=</text>
</g>
<!-- Compression Macro View Indicator -->
<g id="compression-indicator" opacity="0">
<path d="M 0 0 Q 30 -30 60 -10" fill="none" stroke="var(--orange-alert)" stroke-width="1" stroke-dasharray="2 2" />
<rect x="60" y="-20" width="80" height="20" fill="rgba(6,9,14,0.9)" border="1px solid var(--orange-alert)"/>
<text x="100" y="-6" fill="var(--orange-alert)" font-family="Noto Sans SC" font-size="10" text-anchor="middle">柔性形变包覆</text>
</g>
</g>
<!-- Funnel Infeed Indicator -->
<g id="funnel-indicator" opacity="0">
<path d="M 120 263 Q 150 263 180 255" fill="none" stroke="var(--text-main)" stroke-width="1.5" stroke-dasharray="3 3" marker-end="url(#arrow-cyan)"/>
<text x="110" y="266" fill="var(--text-main)" font-family="Noto Sans SC" font-size="10" text-anchor="end">倒角入口</text>
</g>
</svg>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
// --- Elements ---
const ctrlSpeed = document.getElementById('ctrl-speed');
const ctrlThickness = document.getElementById('ctrl-thickness');
const valSpeed = document.getElementById('val-speed');
const valThickness = document.getElementById('val-thickness');
const dispVBot = document.getElementById('disp-v-bot');
const dispVTop = document.getElementById('disp-v-top');
const dispComp = document.getElementById('disp-comp');
const dispFriction = document.getElementById('disp-friction');
const svgGlassGroup = document.getElementById('glass-group');
const svgGlassBody = document.getElementById('glass-body');
const svgGlassPattern = document.getElementById('glass-pattern');
const svgGlassLabel = document.getElementById('glass-label');
const svgGlassContactGlow = document.getElementById('glass-contact-glow');
const puSponge = document.getElementById('pu-sponge');
const puSpongePattern = document.getElementById('pu-sponge-pattern');
const beltBotAnim = document.getElementById('belt-bot-anim');
const beltTopAnim = document.getElementById('belt-top-anim');
const pulleyBotL = document.getElementById('pulley-bot-l');
const pulleyBotR = document.getElementById('pulley-bot-r');
const pulleyTopL = document.getElementById('pulley-top-l');
const pulleyTopR = document.getElementById('pulley-top-r');
const annotations = document.getElementById('annotations');
const vecTopGroup = document.getElementById('vec-top-group');
const vecBotGroup = document.getElementById('vec-bot-group');
const vecEquals = document.getElementById('vec-equals');
const compIndicator = document.getElementById('compression-indicator');
const funnelIndicator = document.getElementById('funnel-indicator');
// --- Geometric Constants ---
const beltTopBaseY = 233; // Where sponge attaches
const spongeRestY = 265; // Default sponge bottom (Thickness = 32 visual units mapping to 20mm)
const beltBotY = 367; // Where glass sits
const glassWidth = 250;
const routeStartX = -300;
const routeEndX = 1300;
const spongeStartX = 150;
const spongeEndX = 1050;
// Base physical mapping
// UI Thickness 1.0mm ~ 6.0mm -> Visual Height 10px ~ 60px (multiplier x10)
const thicknessVisualMult = 10;
// UI Speed 0.0x ~ 3.0x -> Visual Pixels/frame base = 2
const speedVisualBase = 2.5;
// --- State ---
let state = {
glassX: routeStartX,
speedMult: parseFloat(ctrlSpeed.value),
glassThicknessBase: parseFloat(ctrlThickness.value), // mm
time: 0
};
// --- Event Listeners ---
ctrlSpeed.addEventListener('input', (e) => {
state.speedMult = parseFloat(e.target.value);
valSpeed.innerText = state.speedMult.toFixed(1) + 'x';
});
ctrlThickness.addEventListener('input', (e) => {
state.glassThicknessBase = parseFloat(e.target.value);
valThickness.innerText = state.glassThicknessBase.toFixed(1) + ' mm';
});
// --- Path Generation Algorithm ---
// Calculates the dynamic deformation of the PU sponge
function updateSpongePath(glassX, glassTopY) {
let d = `M ${spongeStartX} ${beltTopBaseY} L ${spongeEndX} ${beltTopBaseY} L ${spongeEndX} ${spongeRestY} `;
const pointsCount = 100;
const step = (spongeEndX - spongeStartX) / pointsCount;
const gLeft = glassX;
const gRight = glassX + glassWidth;
// Funnel / Chamfer settings
const chamferDist = 60;
// Generate bottom edge points right to left
let bottomPoints = [];
for(let i = pointsCount; i >= 0; i--) {
let px = spongeStartX + i * step;
let py = spongeRestY;
// Deformation logic
if (px >= gLeft && px <= gRight) {
// Directly above glass -> Fully compressed to glassTopY
// Ensure it doesn't compress past its base (safety)
py = Math.max(beltTopBaseY + 5, Math.min(spongeRestY, glassTopY));
} else if (px < gLeft && px > gLeft - chamferDist) {
// Front ramp (smoothstep interpolation)
let t = (gLeft - px) / chamferDist;
let ease = t * t * (3 - 2 * t); // Smoothstep
let targetY = Math.max(beltTopBaseY + 5, Math.min(spongeRestY, glassTopY));
py = targetY + (spongeRestY - targetY) * ease;
} else if (px > gRight && px < gRight + chamferDist) {
// Back ramp
let t = (px - gRight) / chamferDist;
let ease = t * t * (3 - 2 * t);
let targetY = Math.max(beltTopBaseY + 5, Math.min(spongeRestY, glassTopY));
py = targetY + (spongeRestY - targetY) * ease;
}
bottomPoints.push(`${px},${py}`);
}
d += `L ${bottomPoints.join(' L ')} Z`;
puSponge.setAttribute('d', d);
puSpongePattern.setAttribute('d', d);
}
// --- Main Animation Loop ---
function animate() {
state.time++;
// 1. Calculate Core Kinematics
let moveAmount = speedVisualBase * state.speedMult;
state.glassX += moveAmount;
if (state.glassX > routeEndX) {
state.glassX = routeStartX; // Reset loop
}
// 2. Geometry Updates
let visualHeight = state.glassThicknessBase * thicknessVisualMult;
let glassTopY = beltBotY - visualHeight;
// Update Glass Object
svgGlassGroup.setAttribute('transform', `translate(${state.glassX}, 0)`);
svgGlassBody.setAttribute('y', glassTopY);
svgGlassBody.setAttribute('height', visualHeight);
svgGlassPattern.setAttribute('y', glassTopY);
svgGlassPattern.setAttribute('height', visualHeight);
svgGlassLabel.setAttribute('y', glassTopY + visualHeight/2);
svgGlassContactGlow.setAttribute('y', glassTopY - 2);
// 3. Update Sponge Deformation
updateSpongePath(state.glassX, glassTopY);
// 4. Update Mechanical Rotations & Belt Patterns
// Belt pattern repeats every 16px
let beltOffset = -(state.glassX % 16);
beltBotAnim.setAttribute('x', spongeStartX + beltOffset);
beltTopAnim.setAttribute('x', spongeStartX + beltOffset);
// Pulley rotation (circumference logic approximation)
let rotation = (state.glassX * 1.5) % 360;
pulleyBotL.setAttribute('transform', `rotate(${rotation})`);
pulleyBotR.setAttribute('transform', `rotate(${rotation})`);
// Top pulleys sync (same speed, opposite direction visually or same depending on gear routing. Assuming same direction transport)
pulleyTopL.setAttribute('transform', `rotate(${rotation})`);
pulleyTopR.setAttribute('transform', `rotate(${rotation})`);
// 5. Calculate Live Telemetry & Phase State
// Calculate physical compression
let compressionVisual = Math.max(0, spongeRestY - glassTopY);
let compressionPhysical = compressionVisual / 10; // map back to mm
let realSpeed = 800 * state.speedMult; // mapping to real-world mm/s
let phase = "standby";
let gCenter = state.glassX + glassWidth/2;
if (gCenter > 0 && gCenter < spongeStartX + 50) {
phase = "infeed";
} else if (gCenter >= spongeStartX + 50 && gCenter <= spongeEndX - 50) {
phase = "transport";
} else if (gCenter > spongeEndX - 50 && gCenter < 1200) {
phase = "outfeed";
}
// 6. Visual Directives (IFR Focus)
if (phase === "transport") {
annotations.style.opacity = "1";
// Position Vectors
let vecX = state.glassX + glassWidth/2 + 30;
vecTopGroup.setAttribute('transform', `translate(${vecX}, ${glassTopY - 10})`);
vecBotGroup.setAttribute('transform', `translate(${vecX}, ${beltBotY + 15})`);
vecEquals.setAttribute('transform', `translate(${vecX - 60}, ${(glassTopY + beltBotY)/2})`);
// Glow effect mapping
svgGlassContactGlow.setAttribute('opacity', compressionPhysical > 0 ? '1' : '0');
// Data Update
dispVBot.innerText = realSpeed.toFixed(3) + " mm/s";
dispVTop.innerText = realSpeed.toFixed(3) + " mm/s"; // Identical
dispComp.innerText = compressionPhysical.toFixed(1) + " mm";
if (compressionPhysical > 0) {
dispFriction.innerText = "绝对静止 (夹持)";
dispFriction.style.color = "var(--green-safe)";
dispComp.style.color = "var(--orange-alert)";
compIndicator.setAttribute('opacity', '1');
compIndicator.setAttribute('transform', `translate(${state.glassX + 20}, ${glassTopY})`);
} else {
dispFriction.innerText = "无接触";
dispFriction.style.color = "var(--text-muted)";
dispComp.style.color = "var(--text-muted)";
compIndicator.setAttribute('opacity', '0');
}
funnelIndicator.setAttribute('opacity', '0');
} else if (phase === "infeed") {
annotations.style.opacity = "0";
svgGlassContactGlow.setAttribute('opacity', '0');
funnelIndicator.setAttribute('opacity', '1');
dispComp.innerText = "进入倒角区...";
dispComp.style.color = "var(--text-main)";
dispVBot.innerText = realSpeed.toFixed(3) + " mm/s";
dispVTop.innerText = realSpeed.toFixed(3) + " mm/s";
} else {
annotations.style.opacity = "0";
svgGlassContactGlow.setAttribute('opacity', '0');
funnelIndicator.setAttribute('opacity', '0');
if(state.speedMult === 0) {
dispVBot.innerText = "0.000 mm/s";
dispVTop.innerText = "0.000 mm/s";
} else {
dispVBot.innerText = realSpeed.toFixed(3) + " mm/s";
dispVTop.innerText = realSpeed.toFixed(3) + " mm/s";
}
dispComp.innerText = "0.0 mm";
dispComp.style.color = "var(--text-muted)";
dispFriction.innerText = "无工作负载";
dispFriction.style.color = "var(--text-muted)";
}
// Vector arrows scaling based on speed
let arrowLen = 30 + state.speedMult * 20;
vecTopGroup.querySelector('path').setAttribute('d', `M -40 0 L ${arrowLen} 0`);
vecBotGroup.querySelector('path').setAttribute('d', `M -40 0 L ${arrowLen} 0`);
requestAnimationFrame(animate);
}
// Start loop
requestAnimationFrame(animate);
});
</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
