独立渲染引擎就绪引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rotary Folding Kinematics - IFR</title>
<link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@500;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<style>
:root {
--bg-color: #050914;
--grid-color: rgba(6, 182, 212, 0.05);
--neon-blue: #06b6d4;
--neon-orange: #f97316;
--metallic-dark: #1e293b;
--metallic-light: #334155;
--foil-color: #f8fafc;
--vacuum-intensity: 1;
--anim-speed: 1;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-color);
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;
font-family: 'Rajdhani', sans-serif;
color: white;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
/* Vignette overlay */
body::after {
content: '';
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: radial-gradient(circle at center, transparent 30%, var(--bg-color) 100%);
pointer-events: none;
z-index: 0;
}
.container {
position: relative;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
z-index: 1;
}
svg {
width: 100%;
height: 100%;
max-width: 1400px;
max-height: 900px;
filter: drop-shadow(0 0 30px rgba(6, 182, 212, 0.1));
}
/* Typography & UI Panels */
.hud-panel {
position: absolute;
background: rgba(15, 23, 42, 0.6);
backdrop-filter: blur(12px);
border: 1px solid rgba(6, 182, 212, 0.2);
padding: 24px;
border-radius: 4px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
border-left: 4px solid var(--neon-blue);
}
.hud-top-left { top: 40px; left: 40px; }
.hud-bottom-right { bottom: 40px; right: 40px; border-left: none; border-right: 4px solid var(--neon-orange); }
h1 {
font-size: 2rem;
text-transform: uppercase;
letter-spacing: 2px;
margin-bottom: 8px;
color: #fff;
text-shadow: 0 0 10px rgba(6, 182, 212, 0.5);
}
h2 {
font-size: 1.2rem;
color: var(--neon-blue);
margin-bottom: 20px;
}
.data-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
font-family: 'Share Tech Mono', monospace;
font-size: 1.1rem;
}
.data-label { color: #94a3b8; }
.data-value { color: var(--neon-blue); font-weight: bold; text-shadow: 0 0 8px rgba(6, 182, 212, 0.6); }
.data-value.orange { color: var(--neon-orange); text-shadow: 0 0 8px rgba(249, 115, 22, 0.6); }
/* Controls */
.controls {
margin-top: 24px;
padding-top: 20px;
border-top: 1px solid rgba(255,255,255,0.1);
}
.control-group {
margin-bottom: 16px;
}
.control-header {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-size: 0.9rem;
color: #cbd5e1;
text-transform: uppercase;
}
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 16px;
width: 8px;
background: var(--neon-blue);
cursor: pointer;
box-shadow: 0 0 10px var(--neon-blue);
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 2px;
cursor: pointer;
background: rgba(255,255,255,0.2);
}
/* SVG Inner Styling */
.g-roller { transform-origin: center; }
.foil-stroke {
fill: none;
stroke: var(--foil-color);
stroke-width: 6;
stroke-linecap: round;
stroke-linejoin: round;
filter: drop-shadow(0 0 6px rgba(255,255,255,0.8));
}
.vacuum-zone {
fill: none;
stroke: var(--neon-blue);
stroke-width: 16;
stroke-dasharray: 4 12;
opacity: calc(0.4 * var(--vacuum-intensity));
filter: drop-shadow(0 0 8px var(--neon-blue));
transition: opacity 0.3s;
}
.annotation-line {
stroke: rgba(255,255,255,0.3);
stroke-width: 1;
stroke-dasharray: 4 4;
}
.annotation-text {
font-family: 'Rajdhani', sans-serif;
fill: #94a3b8;
font-size: 14px;
letter-spacing: 1px;
}
.annotation-title {
fill: var(--neon-blue);
font-weight: bold;
font-size: 16px;
}
/* Animations */
@keyframes spin-cw { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes spin-ccw { from { transform: rotate(0deg); } to { transform: rotate(-360deg); } }
.spin-continuous-cw { animation: spin-cw linear infinite; animation-duration: calc(10s / var(--anim-speed)); }
.spin-continuous-ccw { animation: spin-ccw linear infinite; animation-duration: calc(10s / var(--anim-speed)); }
.blade-strike {
transition: transform 0.05s cubic-bezier(0.1, 0.9, 0.2, 1);
}
</style>
</head>
<body>
<div class="container">
<!-- Main Kinematics SVG -->
<svg viewBox="0 0 1200 800" id="visualizer">
<defs>
<filter id="glow-blue" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" 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>
<radialGradient id="grad-main-roller" cx="50%" cy="50%" r="50%">
<stop offset="70%" stop-color="#1e293b" />
<stop offset="100%" stop-color="#334155" />
</radialGradient>
<linearGradient id="grad-foil" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#cbd5e1" />
<stop offset="50%" stop-color="#ffffff" />
<stop offset="100%" stop-color="#cbd5e1" />
</linearGradient>
<!-- Pattern for mechanical texture -->
<pattern id="mech-pattern" width="20" height="20" patternUnits="userSpaceOnUse">
<circle cx="10" cy="10" r="2" fill="rgba(0,0,0,0.2)"/>
</pattern>
</defs>
<!-- Background Grid Rings -->
<g opacity="0.1" stroke="#fff" stroke-width="1" fill="none">
<circle cx="450" cy="400" r="300" stroke-dasharray="10 20"/>
<circle cx="850" cy="400" r="250" stroke-dasharray="5 15"/>
</g>
<!-- ================= Annotations ================= -->
<g id="annotations" transform="translate(0,0)">
<!-- IFR Point 1 -->
<path d="M 450 160 L 350 100 L 200 100" class="annotation-line" />
<circle cx="450" cy="160" r="4" fill="var(--neon-blue)" />
<text x="200" y="80" class="annotation-title">TRIZ IFR: PURE ROTATION</text>
<text x="200" y="100" class="annotation-text">Eliminates reciprocating stop-motion.</text>
<text x="200" y="120" class="annotation-text">0 vibration at high velocity.</text>
<!-- IFR Point 2 -->
<path d="M 700 400 L 800 250 L 980 250" class="annotation-line" />
<circle cx="700" cy="400" r="4" fill="var(--neon-orange)" />
<text x="980" y="210" class="annotation-title" text-anchor="end" fill="var(--neon-orange)">RESOURCE: INTERNAL BLADE</text>
<text x="980" y="230" class="annotation-text" text-anchor="end">Utilizes hidden roller space.</text>
<text x="980" y="250" class="annotation-text" text-anchor="end">Instantly transfers specific fold line.</text>
</g>
<!-- ================= Machinery Base ================= -->
<!-- Main Vacuum Roller (Center: 450, 400, Radius: 200) -->
<g id="main-roller-assembly">
<!-- Vacuum indicator arc (Top to Right) -->
<path d="M 450 200 A 200 200 0 0 1 650 400" class="vacuum-zone" />
<circle cx="450" cy="400" r="200" fill="url(#grad-main-roller)" stroke="#475569" stroke-width="2"/>
<circle cx="450" cy="400" r="190" fill="url(#mech-pattern)"/>
<!-- Rotating Core of Main Roller -->
<g class="spin-continuous-cw" style="transform-origin: 450px 400px;">
<!-- Spokes -->
<line x1="250" y1="400" x2="650" y2="400" stroke="#0f172a" stroke-width="10"/>
<line x1="450" y1="200" x2="450" y2="600" stroke="#0f172a" stroke-width="10"/>
<circle cx="450" cy="400" r="40" fill="#334155" stroke="#0f172a" stroke-width="4"/>
<!-- The Hidden Blade Housing (rotates with roller) -->
<!-- We will dynamically trigger the blade extension via JS -->
<g id="blade-housing" transform="translate(450, 400)">
<rect x="-10" y="-180" width="20" height="180" fill="#0f172a" />
<!-- Actual Blade -->
<path id="active-blade" d="M -8 -180 L 8 -180 L 12 -220 L -12 -220 Z" fill="var(--neon-orange)" filter="url(#glow-orange)" class="blade-strike" transform="translate(0,0)" />
</g>
</g>
</g>
<!-- Secondary Folding Roller (Center: 800, 400, Radius: 140) -->
<g id="sec-roller-assembly">
<circle cx="800" cy="400" r="140" fill="url(#grad-main-roller)" stroke="#475569" stroke-width="2"/>
<!-- Rotating Core -->
<g class="spin-continuous-ccw" style="transform-origin: 800px 400px;">
<circle cx="800" cy="400" r="130" fill="none" stroke="#334155" stroke-width="4" stroke-dasharray="20 10"/>
<circle cx="800" cy="400" r="30" fill="#334155" stroke="#0f172a" stroke-width="4"/>
<!-- Gripper slots -->
<path d="M 660 390 L 680 400 L 660 410 Z" fill="var(--neon-blue)" filter="url(#glow-blue)"/>
<path d="M 940 390 L 920 400 L 940 410 Z" fill="var(--neon-blue)"/>
</g>
</g>
<!-- Flying Shear Rotary Cutter (Top Left, Center: 450, 150) -->
<g id="cutter-assembly" transform="translate(450, 140)">
<circle cx="0" cy="0" r="50" fill="#1e293b" stroke="var(--neon-orange)" stroke-width="2"/>
<g class="spin-continuous-cw" style="transform-origin: 0px 0px; animation-duration: calc(5s / var(--anim-speed));">
<path d="M -5 -50 L 5 -50 L 10 -70 L -10 -70 Z" fill="var(--neon-orange)" filter="url(#glow-orange)"/>
<path d="M -5 50 L 5 50 L 10 70 L -10 70 Z" fill="#334155" />
<circle cx="0" cy="0" r="15" fill="#475569"/>
</g>
</g>
<!-- ================= Foil Material Dynamics ================= -->
<g id="foil-system">
<!-- 1. Continuous Input Feed -->
<path d="M 450 -50 L 450 200" class="foil-stroke" stroke-dasharray="20 10" id="feed-line"/>
<!--
Dynamic Foil Segments controlled by JS to ensure perfect kinematic timing.
We use separate paths for different phases of the foil's journey.
-->
<!-- Piece A -->
<g id="piece-A">
<!-- Phase 1: On Main Roller -->
<path id="piece-A-arc" d="" class="foil-stroke" />
<!-- Phase 2: Folding Transfer -->
<path id="piece-A-fold" d="" class="foil-stroke" />
</g>
<!-- Piece B (Pipeline overlap) -->
<g id="piece-B">
<path id="piece-B-arc" d="" class="foil-stroke" />
<path id="piece-B-fold" d="" class="foil-stroke" />
</g>
</g>
<!-- Nip Point Highlight -->
<circle cx="650" cy="400" r="8" fill="var(--neon-orange)" filter="url(#glow-orange)" opacity="0.8">
<animate attributeName="r" values="6;10;6" dur="2s" repeatCount="indefinite" />
</circle>
</svg>
<!-- UI Overlays -->
<div class="hud-panel hud-top-left">
<h1>Rotary Fold</h1>
<h2>Kinematic IFR System</h2>
<div class="data-row">
<span class="data-label">Line Velocity</span>
<span class="data-value" id="val-speed">25.0 m/min</span>
</div>
<div class="data-row">
<span class="data-label">Main Vacuum</span>
<span class="data-value" id="val-vacuum">-65.0 kPa</span>
</div>
<div class="data-row">
<span class="data-label">Cut Length</span>
<span class="data-value">620 mm</span>
</div>
<div class="data-row">
<span class="data-label">Offset Fold</span>
<span class="data-value orange">315 / 305</span>
</div>
<div class="controls">
<div class="control-group">
<div class="control-header">
<span>System Speed</span>
<span id="label-speed">1.0x</span>
</div>
<input type="range" id="slider-speed" min="0.1" max="2.0" step="0.1" value="1.0">
</div>
<div class="control-group">
<div class="control-header">
<span>Vacuum Pressure</span>
<span id="label-vacuum">100%</span>
</div>
<input type="range" id="slider-vacuum" min="0" max="100" value="100">
</div>
</div>
</div>
<div class="hud-panel hud-bottom-right">
<div class="data-row" style="margin-bottom: 4px;">
<span class="data-label" style="color:#cbd5e1;">SYSTEM STATUS</span>
</div>
<div class="data-row">
<span class="data-value" style="color:#22c55e; text-shadow: 0 0 10px rgba(34, 197, 94, 0.5);">● CONTINUOUS FLOW ACTIVE</span>
</div>
<div style="font-size: 0.85rem; color: #64748b; margin-top: 10px; max-width: 250px;">
Foil tears and vibrations eliminated. Fold accuracy maintained by pure rotational synchronicity.
</div>
</div>
</div>
<script>
/**
* Core Kinematic Engine
* Implements the exact mechanical sequence via procedural SVG path generation.
*/
// Configuration Constants
const R_MAIN = 200;
const C_MAIN = { x: 450, y: 400 };
const R_SEC = 140;
const C_SEC = { x: 800, y: 400 };
const NIP_X = 650;
const CYCLE_TIME_BASE = 2000; // ms for one full piece cycle
// State variables
let progress = 0; // 0 to 1 over CYCLE_TIME
let lastTime = performance.now();
// DOM Elements
const blade = document.getElementById('active-blade');
const bladeHousing = document.getElementById('blade-housing');
const feedLine = document.getElementById('feed-line');
const pieces = [
{ arc: document.getElementById('piece-A-arc'), fold: document.getElementById('piece-A-fold'), offset: 0 },
{ arc: document.getElementById('piece-B-arc'), fold: document.getElementById('piece-B-fold'), offset: 0.5 }
];
// Controls
const rootStyle = document.documentElement.style;
let speedMult = 1.0;
document.getElementById('slider-speed').addEventListener('input', (e) => {
speedMult = parseFloat(e.target.value);
rootStyle.setProperty('--anim-speed', speedMult);
document.getElementById('label-speed').innerText = speedMult.toFixed(1) + 'x';
document.getElementById('val-speed').innerText = (25.0 * speedMult).toFixed(1) + ' m/min';
});
document.getElementById('slider-vacuum').addEventListener('input', (e) => {
let val = parseInt(e.target.value);
rootStyle.setProperty('--vacuum-intensity', val / 100);
document.getElementById('label-vacuum').innerText = val + '%';
document.getElementById('val-vacuum').innerText = '-' + (65.0 * (val/100)).toFixed(1) + ' kPa';
});
// Math Helpers
const toRad = deg => deg * Math.PI / 180;
const getPointOnCircle = (cx, cy, r, angleDeg) => {
return {
x: cx + r * Math.cos(toRad(angleDeg)),
y: cy + r * Math.sin(toRad(angleDeg))
};
};
/**
* Main Render Loop
*/
function animate(time) {
let delta = time - lastTime;
lastTime = time;
// Advance progress
progress += (delta / CYCLE_TIME_BASE) * speedMult;
if (progress >= 1.0) progress -= 1.0;
// Animate continuous feed line moving down
let feedOffset = -(progress * 100) % 30;
feedLine.style.strokeDashoffset = feedOffset;
// Update Blade Rotation (Housing rotates continuously with Main Roller)
// We want the blade to be at 0 degrees (Right) exactly when a piece's fold point reaches there.
// A piece's fold point reaches Right at piece local time p = 0.5.
// We have 2 pieces per full cycle of the system array, meaning the roller actually
// processes 2 pieces per 360 deg rotation, or we can just spin the blade to match.
// Let's manually sync the blade housing rotation so it aligns at Nip point (Angle 0).
let bladeAngle = (progress * 360) - 180; // When prog=0.5, angle=0 (Right). When prog=1.0, angle=180 (Left)
bladeHousing.setAttribute('transform', `translate(450, 400) rotate(${bladeAngle})`);
// Trigger Blade Extrusion
// Striking zone: slightly before and after p = 0.5 or p = 1.0 (for piece B)
let isStriking = (Math.abs(progress - 0.5) < 0.03) || (progress > 0.97) || (progress < 0.03);
if (isStriking) {
blade.setAttribute('transform', 'translate(0, -30)'); // Shoot out
} else {
blade.setAttribute('transform', 'translate(0, 0)'); // Retract
}
// Update Foil Pieces
pieces.forEach(piece => {
// Local progress for this specific piece (0 to 1)
let p = (progress + piece.offset) % 1.0;
updatePieceLogic(p, piece.arc, piece.fold);
});
requestAnimationFrame(animate);
}
/**
* Calculates and draws the SVG paths for a foil piece based on its lifecycle progress (0 to 1)
* p = 0.0 : Cut occurs at Top (-90 deg). Piece starts moving.
* p = 0.5 : Center of piece reaches Nip point (0 deg). Blade strikes.
* p > 0.5 : Piece is folded and dragged into Sec Roller.
*/
function updatePieceLogic(p, arcElem, foldElem) {
// Foil parameters (Visual scaling)
const arcLengthDeg = 75; // Total angular length of cut piece on main roller
const foldOffsetDeg = arcLengthDeg / 2; // Fold is roughly in the middle
if (p < 0.5) {
// PHASE 1: Wrapped on Main Roller
arcElem.style.display = 'block';
foldElem.style.display = 'none';
// Head of the piece travels from Top (-90) to past the Nip (0)
// When p=0, head is at -90 + arcLengthDeg. Tail is at -90.
// We want the FOLD point to reach 0 at p=0.5.
// Fold point angle = -90 + p * 180;
let foldAngle = -90 + p * 180;
let headAngle = foldAngle + foldOffsetDeg;
let tailAngle = foldAngle - foldOffsetDeg;
let startPt = getPointOnCircle(C_MAIN.x, C_MAIN.y, R_MAIN, tailAngle);
let endPt = getPointOnCircle(C_MAIN.x, C_MAIN.y, R_MAIN, headAngle);
// Draw arc
let largeArcFlag = headAngle - tailAngle > 180 ? 1 : 0;
let path = `M ${startPt.x} ${startPt.y} A ${R_MAIN} ${R_MAIN} 0 ${largeArcFlag} 1 ${endPt.x} ${endPt.y}`;
arcElem.setAttribute('d', path);
} else {
// PHASE 2: Transfer and Fold
arcElem.style.display = 'none';
foldElem.style.display = 'block';
// p goes from 0.5 to 1.0. Map this to 0 to 1 for fold animation.
let foldProg = (p - 0.5) * 2;
// Fade out near the end to simulate exiting the system
if (foldProg > 0.8) {
foldElem.style.opacity = 1 - ((foldProg - 0.8) * 5);
} else {
foldElem.style.opacity = 1;
}
// Fold point moves onto Sec Roller.
// Sec Roller rotates Counter-Clockwise. Nip is at 180 deg for Sec Roller.
// Fold point travels from 180 deg downwards towards 90 deg.
let secFoldAngle = 180 - (foldProg * 120); // Travels 120 degrees visually
let pFold = getPointOnCircle(C_SEC.x, C_SEC.y, R_SEC, secFoldAngle);
// Calculate trailing tail (still on Main Roller initially, then peels off)
// The tail was at -foldOffsetDeg when fold hit 0. It continues moving with Main R.
let tailMainAngle = -foldOffsetDeg + foldProg * 180;
let path = "";
if (tailMainAngle < 0) {
// Tail is still glued to main roller
let pTail = getPointOnCircle(C_MAIN.x, C_MAIN.y, R_MAIN, tailMainAngle);
let pNip = {x: NIP_X, y: C_MAIN.y};
// Arc from tail to Nip, then curve to Fold point
path = `M ${pTail.x} ${pTail.y} A ${R_MAIN} ${R_MAIN} 0 0 1 ${pNip.x} ${pNip.y} Q ${pNip.x+10} ${pNip.y+20} ${pFold.x} ${pFold.y}`;
} else {
// Tail has peeled off, dragging through the air
// Fake a trailing end that follows the fold point
let trailX = pFold.x - 30 * (1-foldProg);
let trailY = pFold.y - 80 * (1-foldProg);
path = `M ${trailX} ${trailY} Q ${trailX+20} ${trailY+40} ${pFold.x} ${pFold.y}`;
}
// Add the leading head (hanging downwards/wrapping sec roller)
// Head was ahead of Nip, now it gets pulled backwards/downwards.
let headX = pFold.x + 30 * Math.cos(toRad(secFoldAngle - 90));
let headY = pFold.y + 100 * (1 - foldProg * 0.5); // hangs down, gets shorter as it wraps
path += ` Q ${pFold.x+20} ${pFold.y+40} ${headX} ${headY}`;
foldElem.setAttribute('d', path);
}
}
// Initialize and start animation immediately on load
window.addEventListener('DOMContentLoaded', () => {
requestAnimationFrame(animate);
});
</script>
</body>
</html>
积分规则:第一轮对话扣减8分,后续每轮扣6分
等待动画代码生成...
