Private
Public Access
765 lines
29 KiB
HTML
Executable File
765 lines
29 KiB
HTML
Executable File
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>U-Parametrics Demo</title>
|
|
<style>
|
|
:root {
|
|
--space: 8px;
|
|
--radius: 5px;
|
|
|
|
--gray: #6c757d;
|
|
--gray-bg: #f5f5f5;
|
|
--blue: #007acc;
|
|
--green: #28a745;
|
|
--white: white;
|
|
--dark: #333;
|
|
|
|
--max-width: 1400px;
|
|
--sidebar: 400px;
|
|
}
|
|
|
|
* { box-sizing: border-box; }
|
|
|
|
body {
|
|
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
max-width: var(--max-width);
|
|
margin: 0 auto;
|
|
padding: var(--space);
|
|
background: var(--gray-bg);
|
|
}
|
|
|
|
.container {
|
|
background: var(--white);
|
|
padding: calc(var(--space) * 1.5);
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
.main-layout {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: calc(var(--space) * 1.5);
|
|
}
|
|
|
|
.api-column {
|
|
background: var(--gray-bg);
|
|
padding: var(--space);
|
|
border-radius: var(--radius);
|
|
border-left: 4px solid var(--green);
|
|
}
|
|
|
|
h1 {
|
|
color: var(--dark);
|
|
text-align: center;
|
|
margin-bottom: calc(var(--space) * 1.5);
|
|
grid-column: 1 / -1;
|
|
}
|
|
|
|
h2 {
|
|
color: var(--gray);
|
|
border-bottom: 2px solid #e0e0e0;
|
|
padding-bottom: var(--space);
|
|
margin-top: calc(var(--space) * 1.5);
|
|
}
|
|
|
|
.demo-section {
|
|
margin: var(--space) 0;
|
|
padding: var(--space);
|
|
background: var(--gray-bg);
|
|
border-radius: var(--radius);
|
|
border-left: 4px solid var(--blue);
|
|
}
|
|
|
|
.controls, .input-group {
|
|
display: flex;
|
|
gap: var(--space);
|
|
margin: 15px 0;
|
|
}
|
|
|
|
.controls { flex-wrap: wrap; }
|
|
.input-group { align-items: center; }
|
|
.feature-grid { display: grid; gap: var(--space); margin: var(--space) 0; }
|
|
|
|
button {
|
|
background: var(--blue);
|
|
color: var(--white);
|
|
border: none;
|
|
padding: var(--space) var(--space);
|
|
border-radius: var(--radius);
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
button:hover { filter: brightness(0.9); }
|
|
button:disabled { background: #ccc; cursor: not-allowed; }
|
|
|
|
input[type="text"], input[type="number"], input[type="range"] {
|
|
padding: var(--space);
|
|
border: 1px solid #ddd;
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
input[type="number"] {
|
|
width: 80px;
|
|
}
|
|
|
|
input[type="range"] {
|
|
flex: 1;
|
|
}
|
|
|
|
.status, .code-example {
|
|
font-family: monospace;
|
|
padding: var(--space);
|
|
border-radius: var(--radius);
|
|
margin: var(--space) 0;
|
|
}
|
|
|
|
.status {
|
|
background: var(--dark);
|
|
color: #0f0;
|
|
white-space: pre-wrap;
|
|
max-height: 150px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.code-example {
|
|
background: #2d3748;
|
|
color: #e2e8f0;
|
|
padding: 15px;
|
|
overflow-x: auto;
|
|
margin: var(--space) 0;
|
|
}
|
|
|
|
.highlight { color: #68d391; }
|
|
.keyword { color: #fbb6ce; }
|
|
.string { color: #fbd38d; }
|
|
|
|
.api-section { margin: 15px 0; }
|
|
.api-section h3 {
|
|
color: var(--green);
|
|
margin: 0 0 var(--space) 0;
|
|
}
|
|
|
|
.api-method {
|
|
font-family: monospace;
|
|
margin: 3px 0;
|
|
color: var(--gray);
|
|
}
|
|
|
|
.api-method .method-name { color: var(--blue); font-weight: bold; }
|
|
.api-method .return-type { color: #6f42c1; }
|
|
.api-method .param { color: #e83e8c; }
|
|
|
|
.api-description {
|
|
margin: var(--space) 0;
|
|
}
|
|
|
|
.api-options {
|
|
color: var(--gray);
|
|
}
|
|
|
|
.help-text {
|
|
color: var(--gray);
|
|
margin: var(--space) 0;
|
|
}
|
|
|
|
.code-comment {
|
|
color: #68d391;
|
|
}
|
|
|
|
.color-box {
|
|
display: inline-block;
|
|
width: 30px;
|
|
height: 30px;
|
|
margin: 5px;
|
|
border: 1px solid #000;
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
canvas {
|
|
border: 1px solid #ddd;
|
|
border-radius: var(--radius);
|
|
margin: var(--space) 0;
|
|
}
|
|
|
|
.function-list {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
|
gap: var(--space);
|
|
margin: var(--space) 0;
|
|
}
|
|
|
|
.function-btn {
|
|
padding: var(--space);
|
|
cursor: pointer;
|
|
border: 1px solid #ddd;
|
|
text-align: center;
|
|
border-radius: var(--radius);
|
|
background: var(--white);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.function-btn:hover {
|
|
background-color: #f0f0f0;
|
|
}
|
|
|
|
.function-btn.active {
|
|
background-color: var(--green);
|
|
color: var(--white);
|
|
border-color: var(--green);
|
|
}
|
|
|
|
@media (max-width: 1200px) {
|
|
.main-layout { grid-template-columns: 1fr; }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>U-Parametrics Demo</h1>
|
|
|
|
<div class="main-layout">
|
|
<div class="demo-column">
|
|
<h2>Function Visualizer</h2>
|
|
<div class="demo-section">
|
|
<h3>Interactive Easing Functions</h3>
|
|
<div class="function-list" id="function-buttons"></div>
|
|
<canvas id="curve-canvas" width="400" height="250"></canvas>
|
|
<div class="input-group">
|
|
<label>Speed:</label>
|
|
<input type="range" id="speed-slider" min="0.5" max="5" step="0.1" value="2">
|
|
<span id="speed-value">2.0</span>s
|
|
</div>
|
|
<div class="controls">
|
|
<button onclick="toggleAnimation()" id="anim-btn">Start Animation</button>
|
|
<button onclick="resetAnimation()">Reset</button>
|
|
</div>
|
|
<div class="status" id="function-info"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="api-column">
|
|
<h2>Core Functions</h2>
|
|
|
|
<div class="api-section">
|
|
<h3>Basic Easing</h3>
|
|
<div class="api-method"><span class="method-name">lerp</span>(<span class="param">t, a, b</span>) → <span class="return-type">number</span></div>
|
|
<div class="api-description">
|
|
Linear interpolation from a to b
|
|
</div>
|
|
<div class="api-method"><span class="method-name">sin1</span>(<span class="param">t</span>) → <span class="return-type">number</span></div>
|
|
<div class="api-description">
|
|
Sine wave easing (0 to 1)
|
|
</div>
|
|
<div class="api-method"><span class="method-name">flip1</span>(<span class="param">t</span>) → <span class="return-type">number</span></div>
|
|
<div class="api-description">
|
|
Inverts the value (1-t)
|
|
</div>
|
|
</div>
|
|
|
|
<div class="api-section">
|
|
<h3>Smooth Curves</h3>
|
|
<div class="api-method"><span class="method-name">smooth_start21</span>(<span class="param">t</span>) → <span class="return-type">number</span></div>
|
|
<div class="api-description">
|
|
Quadratic acceleration
|
|
</div>
|
|
<div class="api-method"><span class="method-name">smooth_start31</span>(<span class="param">t</span>) → <span class="return-type">number</span></div>
|
|
<div class="api-description">
|
|
Cubic acceleration
|
|
</div>
|
|
<div class="api-method"><span class="method-name">parabola1</span>(<span class="param">t</span>) → <span class="return-type">number</span></div>
|
|
<div class="api-description">
|
|
Parabolic curve
|
|
</div>
|
|
</div>
|
|
|
|
<div class="api-section">
|
|
<h3>Examples</h3>
|
|
<pre class="code-example">
|
|
<span class="keyword">const</span> position = <span class="highlight">lerp</span>(time, startPos, endPos);
|
|
|
|
<span class="keyword">const</span> alpha = <span class="highlight">sin1</span>(time);
|
|
|
|
<span class="keyword">const</span> ease = <span class="highlight">smooth_start21</span>(progress);
|
|
</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="main-layout">
|
|
<div class="demo-column">
|
|
<h2>Function Comparison</h2>
|
|
<div class="demo-section">
|
|
<h3>Compare Multiple Functions</h3>
|
|
<canvas id="comparison-canvas" width="400" height="300"></canvas>
|
|
<div class="controls">
|
|
<button onclick="addFunctionToComparison('smooth_start21')">Smooth Start²</button>
|
|
<button onclick="addFunctionToComparison('smooth_start31')">Smooth Start³</button>
|
|
<button onclick="addFunctionToComparison('parabola1')">Parabola</button>
|
|
<button onclick="addFunctionToComparison('sin1')">Sine</button>
|
|
<button onclick="clearComparison()">Clear All</button>
|
|
</div>
|
|
<div class="status" id="comparison-legend"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="api-column">
|
|
<h2>Advanced Functions</h2>
|
|
|
|
<div class="api-section">
|
|
<h3>Parametric Functions</h3>
|
|
<div class="api-method"><span class="method-name">logistic1</span>(<span class="param">t, a</span>) → <span class="return-type">number</span></div>
|
|
<div class="api-description">
|
|
S-curve with steepness parameter
|
|
</div>
|
|
<div class="api-method"><span class="method-name">tanh1</span>(<span class="param">t, a</span>) → <span class="return-type">number</span></div>
|
|
<div class="api-description">
|
|
Hyperbolic tangent curve
|
|
</div>
|
|
<div class="api-method"><span class="method-name">step1</span>(<span class="param">t, a, b</span>) → <span class="return-type">number</span></div>
|
|
<div class="api-description">
|
|
Step function with transition range
|
|
</div>
|
|
</div>
|
|
|
|
<div class="api-section">
|
|
<h3>Complex Functions</h3>
|
|
<div class="api-method"><span class="method-name">bezier31</span>(<span class="param">t, b, c</span>) → <span class="return-type">number</span></div>
|
|
<div class="api-description">
|
|
Cubic Bezier curve
|
|
</div>
|
|
<div class="api-method"><span class="method-name">bounce_stop1</span>(<span class="param">t</span>) → <span class="return-type">number</span></div>
|
|
<div class="api-description">
|
|
Bouncing deceleration
|
|
</div>
|
|
<div class="api-method"><span class="method-name">chain1</span>(<span class="param">t, ...funcs</span>) → <span class="return-type">number</span></div>
|
|
<div class="api-description">
|
|
Chain multiple functions in sequence
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="main-layout">
|
|
<div class="demo-column">
|
|
<h2>Animation Examples</h2>
|
|
<div class="demo-section">
|
|
<h3>Bouncing Ball</h3>
|
|
<canvas id="ball-canvas" width="400" height="150"></canvas>
|
|
<div class="controls">
|
|
<button onclick="animateBall('parabola1')">Parabola</button>
|
|
<button onclick="animateBall('smooth_start31')">Cubic</button>
|
|
<button onclick="animateBall('bounce_stop1')">Bounce</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="demo-section">
|
|
<h3>Loading Progress</h3>
|
|
<canvas id="loading-canvas" width="400" height="50"></canvas>
|
|
<div class="controls">
|
|
<button onclick="animateLoading('lerp')">Linear</button>
|
|
<button onclick="animateLoading('logistic1')">Logistic</button>
|
|
<button onclick="animateLoading('smooth_start21')">Acceleration</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="demo-section">
|
|
<h3>Fade Transition</h3>
|
|
<canvas id="fade-canvas" width="400" height="100"></canvas>
|
|
<div class="controls">
|
|
<button onclick="animateFade('sin1')">Sine</button>
|
|
<button onclick="animateFade('tanh1')">Tanh</button>
|
|
<button onclick="animateFade('smooth_step21')">Smooth Step</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="api-column">
|
|
<h2>Custom Parameters</h2>
|
|
|
|
<div class="api-section">
|
|
<h3>Parametric Control</h3>
|
|
<canvas id="custom-canvas" width="400" height="250"></canvas>
|
|
<div class="input-group">
|
|
<label>Logistic A:</label>
|
|
<input type="range" id="logistic-a" min="1" max="20" step="0.5" value="10" oninput="updateCustomFunction()">
|
|
<span id="logistic-a-value">10</span>
|
|
</div>
|
|
<div class="input-group">
|
|
<label>Step Position:</label>
|
|
<input type="range" id="step-pos" min="0" max="1" step="0.05" value="0.5" oninput="updateCustomFunction()">
|
|
<span id="step-pos-value">0.5</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="api-section">
|
|
<h3>Usage Examples</h3>
|
|
<pre class="code-example">
|
|
<span class="code-comment">// Custom logistic curve</span>
|
|
<span class="keyword">const</span> curve = <span class="highlight">logistic1</span>(t, <span class="string">15</span>);
|
|
|
|
<span class="code-comment">// Stepped animation</span>
|
|
<span class="keyword">const</span> step = <span class="highlight">step1</span>(t, <span class="string">0.3</span>, <span class="string">0.7</span>);
|
|
|
|
<span class="code-comment">// Bezier timing</span>
|
|
<span class="keyword">const</span> ease = <span class="highlight">bezier31</span>(t, <span class="string">0.8</span>, <span class="string">0.2</span>);
|
|
</pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="u-parametrics.js"></script>
|
|
<script>
|
|
// Utility functions for common operations
|
|
function getElement(id) {
|
|
return document.getElementById(id);
|
|
}
|
|
|
|
function getFloatValue(id) {
|
|
return parseFloat(getElement(id).value);
|
|
}
|
|
|
|
function updateTextContent(id, value) {
|
|
getElement(id).textContent = value;
|
|
}
|
|
|
|
function getCanvasContext(canvasId) {
|
|
const canvas = getElement(canvasId);
|
|
const ctx = canvas.getContext('2d');
|
|
return { canvas, ctx };
|
|
}
|
|
|
|
function getCanvasDimensions(canvas, margin = 30) {
|
|
const width = canvas.width;
|
|
const height = canvas.height;
|
|
const plotWidth = width - 2 * margin;
|
|
const plotHeight = height - 2 * margin;
|
|
return { width, height, margin, plotWidth, plotHeight };
|
|
}
|
|
|
|
function clearCanvasWithDimensions(canvasId, margin = 30) {
|
|
const { canvas, ctx } = getCanvasContext(canvasId);
|
|
const dimensions = getCanvasDimensions(canvas, margin);
|
|
ctx.clearRect(0, 0, dimensions.width, dimensions.height);
|
|
return { canvas, ctx, ...dimensions };
|
|
}
|
|
|
|
function drawGrid(ctx, dimensions) {
|
|
const { width, height, margin, plotWidth, plotHeight } = dimensions;
|
|
|
|
ctx.strokeStyle = '#f0f0f0';
|
|
ctx.lineWidth = 1;
|
|
for (let i = 0; i <= 10; i++) {
|
|
const x = margin + (i / 10) * plotWidth;
|
|
const y = margin + (i / 10) * plotHeight;
|
|
|
|
// Vertical grid lines
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, margin);
|
|
ctx.lineTo(x, height - margin);
|
|
ctx.stroke();
|
|
|
|
// Horizontal grid lines
|
|
ctx.beginPath();
|
|
ctx.moveTo(margin, y);
|
|
ctx.lineTo(width - margin, y);
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
function drawAxes(ctx, dimensions) {
|
|
const { width, height, margin } = dimensions;
|
|
|
|
ctx.strokeStyle = '#333';
|
|
ctx.lineWidth = 2;
|
|
ctx.beginPath();
|
|
ctx.moveTo(margin, margin);
|
|
ctx.lineTo(margin, height - margin);
|
|
ctx.lineTo(width - margin, height - margin);
|
|
ctx.stroke();
|
|
}
|
|
|
|
function drawAxisLabels(ctx, dimensions) {
|
|
const { width, height, margin } = dimensions;
|
|
|
|
ctx.fillStyle = '#333';
|
|
ctx.font = '12px Arial';
|
|
ctx.fillText('0', margin - 15, height - margin + 5);
|
|
ctx.fillText('1', margin - 15, margin + 5);
|
|
ctx.fillText('0', margin - 5, height - margin + 15);
|
|
ctx.fillText('1', width - margin - 5, height - margin + 15);
|
|
}
|
|
|
|
function drawFunctionCurve(ctx, dimensions, func, color = '#e74c3c', lineWidth = 3) {
|
|
const { margin, plotWidth, plotHeight, height } = dimensions;
|
|
|
|
ctx.strokeStyle = color;
|
|
ctx.lineWidth = lineWidth;
|
|
ctx.beginPath();
|
|
|
|
for (let i = 0; i <= plotWidth; i++) {
|
|
const t = i / plotWidth;
|
|
const value = clamp1(func(t));
|
|
const x = margin + i;
|
|
const y = height - margin - value * plotHeight;
|
|
|
|
if (i === 0) {
|
|
ctx.moveTo(x, y);
|
|
} else {
|
|
ctx.lineTo(x, y);
|
|
}
|
|
}
|
|
ctx.stroke();
|
|
}
|
|
|
|
function setupPlotCanvas(canvasId, margin = 30) {
|
|
const { canvas, ctx, ...dimensions } = clearCanvasWithDimensions(canvasId, margin);
|
|
drawGrid(ctx, { ...dimensions, margin });
|
|
drawAxes(ctx, { ...dimensions, margin });
|
|
return { canvas, ctx, ...dimensions };
|
|
}
|
|
|
|
function createAnimationLoop(canvasId, animationFunction, duration = 1, step = 0.01) {
|
|
const { canvas, ctx } = getCanvasContext(canvasId);
|
|
let t = 0;
|
|
|
|
function frame() {
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
animationFunction(ctx, canvas, t);
|
|
|
|
t += step;
|
|
if (t <= duration) {
|
|
requestAnimationFrame(frame);
|
|
}
|
|
}
|
|
frame();
|
|
}
|
|
|
|
const easingFunctions = {
|
|
'lerp': { func: (t) => lerp(t), name: 'Linear', desc: 'Constant rate of change' },
|
|
'sin1': { func: sin1, name: 'Sine', desc: 'Smooth start and end' },
|
|
'smooth_start21': { func: smooth_start21, name: 'Smooth Start²', desc: 'Slow start, fast end' },
|
|
'smooth_start31': { func: smooth_start31, name: 'Smooth Start³', desc: 'Very slow start, very fast end' },
|
|
'parabola1': { func: parabola1, name: 'Parabola', desc: 'Fast start and end, slow middle' },
|
|
'flip1': { func: flip1, name: 'Flip', desc: 'Inverts the value (1-t)' },
|
|
'step1': { func: (t) => step1(t), name: 'Step', desc: 'Sharp transition at midpoint' },
|
|
'logistic1': { func: (t) => logistic1(t), name: 'Logistic', desc: 'S-curve transition' },
|
|
'tanh1': { func: (t) => tanh1(t), name: 'Tanh', desc: 'Smooth S-curve' },
|
|
'bounce_stop1': { func: bounce_stop1, name: 'Bounce', desc: 'Bouncing deceleration' },
|
|
'smooth_step21': { func: smooth_step21, name: 'Smooth Step²', desc: 'Smooth acceleration and deceleration' },
|
|
'bezier31': { func: (t) => bezier31(t), name: 'Bezier³', desc: 'Cubic Bezier curve' }
|
|
};
|
|
|
|
let currentFunction = 'sin1';
|
|
let animationRunning = false;
|
|
let animationTime = 0;
|
|
let comparisonFunctions = [];
|
|
const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57', '#ff9ff3', '#54a0ff'];
|
|
|
|
function log(message, outputId = 'function-info') {
|
|
const output = getElement(outputId);
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
output.textContent += `[${timestamp}] ${message}\n`;
|
|
output.scrollTop = output.scrollHeight;
|
|
}
|
|
|
|
function clearOutput(outputId) {
|
|
getElement(outputId).textContent = '';
|
|
}
|
|
|
|
function initializeFunctionButtons() {
|
|
const container = getElement('function-buttons');
|
|
Object.keys(easingFunctions).forEach(key => {
|
|
const btn = document.createElement('div');
|
|
btn.className = `function-btn ${key === currentFunction ? 'active' : ''}`;
|
|
btn.textContent = easingFunctions[key].name;
|
|
btn.onclick = () => selectFunction(key);
|
|
btn.id = `btn-${key}`;
|
|
container.appendChild(btn);
|
|
});
|
|
}
|
|
|
|
function selectFunction(funcName) {
|
|
document.querySelectorAll('.function-btn').forEach(btn => btn.classList.remove('active'));
|
|
getElement(`btn-${funcName}`).classList.add('active');
|
|
|
|
currentFunction = funcName;
|
|
drawCurve();
|
|
updateFunctionInfo();
|
|
}
|
|
|
|
function updateFunctionInfo() {
|
|
clearOutput('function-info');
|
|
const info = easingFunctions[currentFunction];
|
|
log(`${info.name}: ${info.desc}`, 'function-info');
|
|
}
|
|
|
|
function drawCurve() {
|
|
const { canvas, ctx, ...dimensions } = setupPlotCanvas('curve-canvas');
|
|
const func = easingFunctions[currentFunction].func;
|
|
|
|
// Draw the main function curve
|
|
drawFunctionCurve(ctx, dimensions, func);
|
|
|
|
// Draw animation point if running
|
|
if (animationRunning) {
|
|
const t = (animationTime % 1);
|
|
const value = clamp1(func(t));
|
|
const x = dimensions.margin + t * dimensions.plotWidth;
|
|
const y = dimensions.height - dimensions.margin - value * dimensions.plotHeight;
|
|
|
|
ctx.fillStyle = '#3498db';
|
|
ctx.beginPath();
|
|
ctx.arc(x, y, 6, 0, 2 * Math.PI);
|
|
ctx.fill();
|
|
}
|
|
|
|
drawAxisLabels(ctx, dimensions);
|
|
}
|
|
|
|
function toggleAnimation() {
|
|
const btn = getElement('anim-btn');
|
|
if (animationRunning) {
|
|
animationRunning = false;
|
|
btn.textContent = 'Start Animation';
|
|
} else {
|
|
animationRunning = true;
|
|
btn.textContent = 'Stop Animation';
|
|
animate();
|
|
}
|
|
}
|
|
|
|
function resetAnimation() {
|
|
animationTime = 0;
|
|
drawCurve();
|
|
}
|
|
|
|
function animate() {
|
|
if (!animationRunning) return;
|
|
|
|
const speed = getFloatValue('speed-slider');
|
|
animationTime += 0.016 * speed;
|
|
|
|
drawCurve();
|
|
requestAnimationFrame(animate);
|
|
}
|
|
|
|
getElement('speed-slider').oninput = function() {
|
|
updateTextContent('speed-value', this.value);
|
|
};
|
|
|
|
function addFunctionToComparison(funcName) {
|
|
if (!comparisonFunctions.includes(funcName)) {
|
|
comparisonFunctions.push(funcName);
|
|
drawComparison();
|
|
updateComparisonLegend();
|
|
}
|
|
}
|
|
|
|
function clearComparison() {
|
|
comparisonFunctions = [];
|
|
clearOutput('comparison-legend');
|
|
drawComparison();
|
|
}
|
|
|
|
function drawComparison() {
|
|
const { canvas, ctx, ...dimensions } = setupPlotCanvas('comparison-canvas');
|
|
|
|
comparisonFunctions.forEach((funcName, index) => {
|
|
const func = easingFunctions[funcName].func;
|
|
const color = colors[index % colors.length];
|
|
drawFunctionCurve(ctx, dimensions, func, color);
|
|
});
|
|
}
|
|
|
|
function updateComparisonLegend() {
|
|
clearOutput('comparison-legend');
|
|
comparisonFunctions.forEach((funcName, index) => {
|
|
const color = colors[index % colors.length];
|
|
const name = easingFunctions[funcName].name;
|
|
log(`${name}`, 'comparison-legend');
|
|
});
|
|
}
|
|
|
|
function animateBall(easingFunc) {
|
|
const func = easingFunctions[easingFunc].func;
|
|
|
|
createAnimationLoop('ball-canvas', (ctx, canvas, t) => {
|
|
const value = func(t);
|
|
const x = 20 + (canvas.width - 40) * t;
|
|
const y = canvas.height - 20 - value * (canvas.height - 40);
|
|
|
|
ctx.fillStyle = '#e74c3c';
|
|
ctx.beginPath();
|
|
ctx.arc(x, y, 10, 0, 2 * Math.PI);
|
|
ctx.fill();
|
|
});
|
|
}
|
|
|
|
function animateLoading(easingFunc) {
|
|
const func = easingFunctions[easingFunc].func;
|
|
|
|
createAnimationLoop('loading-canvas', (ctx, canvas, t) => {
|
|
ctx.fillStyle = '#f0f0f0';
|
|
ctx.fillRect(10, 15, canvas.width - 20, 20);
|
|
|
|
const progress = func(t);
|
|
ctx.fillStyle = '#4ecdc4';
|
|
ctx.fillRect(10, 15, progress * (canvas.width - 20), 20);
|
|
});
|
|
}
|
|
|
|
function animateFade(easingFunc) {
|
|
const func = easingFunctions[easingFunc].func;
|
|
|
|
createAnimationLoop('fade-canvas', (ctx, canvas, t) => {
|
|
const alpha = func(t);
|
|
ctx.fillStyle = `rgba(52, 152, 219, ${alpha})`;
|
|
ctx.fillRect(50, 25, 100, 50);
|
|
|
|
ctx.fillStyle = '#333';
|
|
ctx.font = '12px Arial';
|
|
ctx.fillText(`Alpha: ${alpha.toFixed(2)}`, 10, 90);
|
|
});
|
|
}
|
|
|
|
function updateCustomFunction() {
|
|
const aValue = getFloatValue('logistic-a');
|
|
const stepPos = getFloatValue('step-pos');
|
|
|
|
updateTextContent('logistic-a-value', aValue);
|
|
updateTextContent('step-pos-value', stepPos);
|
|
|
|
drawCustomFunctions(aValue, stepPos);
|
|
}
|
|
|
|
function drawCustomFunctions(aValue, stepPos) {
|
|
const { canvas, ctx, ...dimensions } = setupPlotCanvas('custom-canvas');
|
|
|
|
// Draw logistic function with custom parameter
|
|
drawFunctionCurve(ctx, dimensions, (t) => logistic1(t, aValue), '#e74c3c');
|
|
|
|
// Draw step function with custom position
|
|
drawFunctionCurve(ctx, dimensions, (t) => step1(t, stepPos), '#3498db');
|
|
|
|
// Draw legend
|
|
ctx.fillStyle = '#e74c3c';
|
|
ctx.font = '12px Arial';
|
|
ctx.fillText(`Logistic (a=${aValue})`, dimensions.margin, dimensions.height - 10);
|
|
ctx.fillStyle = '#3498db';
|
|
ctx.fillText(`Step (pos=${stepPos})`, dimensions.margin + 150, dimensions.height - 10);
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initializeFunctionButtons();
|
|
drawCurve();
|
|
updateFunctionInfo();
|
|
drawComparison();
|
|
updateCustomFunction();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|