Test files für LifeWave mit anime.js
This commit is contained in:
parent
c6dd063eee
commit
9c48a17152
7 changed files with 3573 additions and 0 deletions
1060
test/anime-points-animation.html
Normal file
1060
test/anime-points-animation.html
Normal file
File diff suppressed because it is too large
Load diff
452
test/anime-points-drag.html
Normal file
452
test/anime-points-drag.html
Normal file
|
|
@ -0,0 +1,452 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Welle Hintergrund (SVG)</title>
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
margin: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
border: 1px solid #ccc; /* Optional: Rahmen zur Visualisierung */
|
||||||
|
overflow: visible; /* Damit Punkte am Rand nicht abgeschnitten werden */
|
||||||
|
}
|
||||||
|
|
||||||
|
.point {
|
||||||
|
fill: #000000; /* Farbe der Punkte */
|
||||||
|
opacity: 0.8; /* Punkte sichtbar machen */
|
||||||
|
r: 3.5; /* Einheitliche Punktgröße */
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
fill: none; /* Keine Füllung für die Linie */
|
||||||
|
stroke-width: 4; /* Dickere Hauptlinie */
|
||||||
|
}
|
||||||
|
|
||||||
|
.offset-spline {
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 2.5; /* Dickere mittlere Linien */
|
||||||
|
stroke-opacity: 0.6; /* Angepasste Transparenz */
|
||||||
|
}
|
||||||
|
|
||||||
|
.offset-spline-far {
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 1.2; /* Etwas dickere äußere Linien */
|
||||||
|
stroke-opacity: 0.4; /* Höhere Transparenz */
|
||||||
|
}
|
||||||
|
.offset-spline-far-2 {
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 1; /* Etwas dickere äußere Linien */
|
||||||
|
stroke-opacity: 0.2; /* Höhere Transparenz */
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Welleneffekt (SVG)</h1>
|
||||||
|
|
||||||
|
<svg width="600" height="200" id="point-canvas">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="lineGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
|
<stop offset="0%" style="stop-color:rgb(226, 245, 172);stop-opacity:1" />
|
||||||
|
<stop offset="35%" style="stop-color:rgb(135, 241, 36);stop-opacity:1" />
|
||||||
|
<stop offset="70%" style="stop-color:rgb(62, 200, 232);stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:rgb(4, 162, 254);stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', initializeApp);
|
||||||
|
|
||||||
|
// Konstanten und Konfiguration
|
||||||
|
const CONFIG = {
|
||||||
|
svgNS: "http://www.w3.org/2000/svg",
|
||||||
|
pointRadius: 4,
|
||||||
|
offsetNear: 5,
|
||||||
|
offsetFar: 10,
|
||||||
|
minOffsetFactor: 0.2,
|
||||||
|
splineTension: 1.3,
|
||||||
|
baseYPositions: [100, 70, 110, 80, 130, 90, 100],
|
||||||
|
initialAnimDuration: 2500,
|
||||||
|
contAnimDuration: 1000,
|
||||||
|
waveAmplitude: 12,
|
||||||
|
isDraggable: true,
|
||||||
|
animationEnabled: true
|
||||||
|
};
|
||||||
|
|
||||||
|
// Globale Variablen
|
||||||
|
let svg, points, pointElements = [], pathElements = {}, currentAnimation, isAnimating = false;
|
||||||
|
let draggedPointIndex = -1;
|
||||||
|
|
||||||
|
function initializeApp() {
|
||||||
|
// DOM-Referenzen
|
||||||
|
svg = document.getElementById('point-canvas');
|
||||||
|
|
||||||
|
// Punkte definieren
|
||||||
|
initPoints();
|
||||||
|
updateVisualization();
|
||||||
|
addControls();
|
||||||
|
|
||||||
|
// Animation starten, wenn aktiviert
|
||||||
|
if (CONFIG.animationEnabled) {
|
||||||
|
startWaveAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Punkte initialisieren
|
||||||
|
function initPoints() {
|
||||||
|
const xStep = 580 / (CONFIG.baseYPositions.length - 1);
|
||||||
|
|
||||||
|
// Punkte erstellen
|
||||||
|
points = CONFIG.baseYPositions.map((y, i) => {
|
||||||
|
return { x: 20 + i * xStep, y };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Punktelemente erstellen
|
||||||
|
pointElements = points.map((p, index) => {
|
||||||
|
const circle = createSVGElement('circle', {
|
||||||
|
'class': 'point',
|
||||||
|
'cx': p.x,
|
||||||
|
'cy': p.y,
|
||||||
|
'r': CONFIG.pointRadius,
|
||||||
|
'cursor': CONFIG.isDraggable ? 'grab' : 'default'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Drag-and-Drop-Funktionalität hinzufügen
|
||||||
|
if (CONFIG.isDraggable) {
|
||||||
|
setupDraggable(circle, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.appendChild(circle);
|
||||||
|
return circle;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hilfsfunktion zur Berechnung von Normalen
|
||||||
|
function calculateNormals(points) {
|
||||||
|
return points.map((p1, i, arr) => {
|
||||||
|
let dx, dy;
|
||||||
|
|
||||||
|
if (i === 0) {
|
||||||
|
dx = arr[1].x - arr[0].x;
|
||||||
|
dy = arr[1].y - arr[0].y;
|
||||||
|
} else if (i === arr.length - 1) {
|
||||||
|
dx = arr[i].x - arr[i-1].x;
|
||||||
|
dy = arr[i].y - arr[i-1].y;
|
||||||
|
} else {
|
||||||
|
const p0 = arr[i - 1];
|
||||||
|
const p2 = arr[i + 1];
|
||||||
|
dx = p2.x - p0.x;
|
||||||
|
dy = p2.y - p0.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normale berechnen (-dy, dx) und normalisieren
|
||||||
|
const length = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
return length > 0
|
||||||
|
? { x: -dy / length, y: dx / length }
|
||||||
|
: { x: 0, y: 0 };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funktion zum Generieren von Offset-Spline-Pfaden
|
||||||
|
function generateOffsetSplinePath(originalPoints, normals, offsetDistance) {
|
||||||
|
if (originalPoints.length < 2) return "";
|
||||||
|
|
||||||
|
const { minOffsetFactor, splineTension } = CONFIG;
|
||||||
|
|
||||||
|
// Startpunkt mit Offset
|
||||||
|
const startNormal = normals[0];
|
||||||
|
const startOffsetX = originalPoints[0].x + startNormal.x * offsetDistance * minOffsetFactor;
|
||||||
|
const startOffsetY = originalPoints[0].y + startNormal.y * offsetDistance * minOffsetFactor;
|
||||||
|
let d = `M ${startOffsetX} ${startOffsetY}`;
|
||||||
|
|
||||||
|
// Bézier-Kurve für jeden Punkt generieren
|
||||||
|
for (let i = 0; i < originalPoints.length - 1; i++) {
|
||||||
|
const p0 = i > 0 ? originalPoints[i - 1] : originalPoints[0];
|
||||||
|
const p1 = originalPoints[i];
|
||||||
|
const p2 = originalPoints[i + 1];
|
||||||
|
const p3 = i < originalPoints.length - 2 ? originalPoints[i + 2] : originalPoints[originalPoints.length - 1];
|
||||||
|
|
||||||
|
// Kontrollpunkte berechnen
|
||||||
|
const cp1x = p1.x + (p2.x - p0.x) / 6 * splineTension;
|
||||||
|
const cp1y = p1.y + (p2.y - p0.y) / 6 * splineTension;
|
||||||
|
const cp2x = p2.x - (p3.x - p1.x) / 6 * splineTension;
|
||||||
|
const cp2y = p2.y - (p3.y - p1.y) / 6 * splineTension;
|
||||||
|
|
||||||
|
// Normalen verwenden
|
||||||
|
const n1 = normals[i];
|
||||||
|
const n2 = normals[i+1];
|
||||||
|
|
||||||
|
// Offset-Punkte berechnen
|
||||||
|
const offsetCp1x = cp1x + n1.x * offsetDistance;
|
||||||
|
const offsetCp1y = cp1y + n1.y * offsetDistance;
|
||||||
|
const offsetCp2x = cp2x + n2.x * offsetDistance;
|
||||||
|
const offsetCp2y = cp2y + n2.y * offsetDistance;
|
||||||
|
const offsetP2x = p2.x + n2.x * offsetDistance * minOffsetFactor;
|
||||||
|
const offsetP2y = p2.y + n2.y * offsetDistance * minOffsetFactor;
|
||||||
|
|
||||||
|
// Kubisches Bézier-Segment hinzufügen
|
||||||
|
d += ` C ${offsetCp1x} ${offsetCp1y}, ${offsetCp2x} ${offsetCp2y}, ${offsetP2x} ${offsetP2y}`;
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hilfsfunktion zum Erstellen von SVG-Elementen
|
||||||
|
function createSVGElement(type, attributes = {}) {
|
||||||
|
const element = document.createElementNS(CONFIG.svgNS, type);
|
||||||
|
Object.entries(attributes).forEach(([key, value]) => {
|
||||||
|
element.setAttribute(key, value);
|
||||||
|
});
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drag-and-Drop-Funktionalität
|
||||||
|
function setupDraggable(element, pointIndex) {
|
||||||
|
element.addEventListener('mousedown', (e) => startDrag(e, pointIndex));
|
||||||
|
element.addEventListener('touchstart', (e) => startDrag(e, pointIndex), { passive: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
function startDrag(event, pointIndex) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// Stoppe Animationen während des Ziehens
|
||||||
|
if (currentAnimation) {
|
||||||
|
currentAnimation.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
draggedPointIndex = pointIndex;
|
||||||
|
|
||||||
|
// Cursor-Stil ändern
|
||||||
|
document.querySelectorAll('.point').forEach(p => p.setAttribute('cursor', 'grab'));
|
||||||
|
pointElements[pointIndex].setAttribute('cursor', 'grabbing');
|
||||||
|
|
||||||
|
// Event-Listener für Bewegung und Loslassen hinzufügen
|
||||||
|
document.addEventListener('mousemove', moveDrag);
|
||||||
|
document.addEventListener('touchmove', moveDrag, { passive: false });
|
||||||
|
document.addEventListener('mouseup', endDrag);
|
||||||
|
document.addEventListener('touchend', endDrag);
|
||||||
|
document.addEventListener('mouseleave', endDrag);
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveDrag(event) {
|
||||||
|
if (draggedPointIndex === -1) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// Mouse- oder Touch-Position ermitteln
|
||||||
|
const clientX = event.clientX || (event.touches && event.touches[0].clientX);
|
||||||
|
const clientY = event.clientY || (event.touches && event.touches[0].clientY);
|
||||||
|
|
||||||
|
if (clientX === undefined || clientY === undefined) return;
|
||||||
|
|
||||||
|
// Position relativ zum SVG berechnen
|
||||||
|
const svgRect = svg.getBoundingClientRect();
|
||||||
|
const x = clientX - svgRect.left;
|
||||||
|
const y = clientY - svgRect.top;
|
||||||
|
|
||||||
|
// Punkt aktualisieren
|
||||||
|
points[draggedPointIndex].x = x;
|
||||||
|
points[draggedPointIndex].y = y;
|
||||||
|
|
||||||
|
// Basisposition für die Animation aktualisieren
|
||||||
|
CONFIG.baseYPositions[draggedPointIndex] = y;
|
||||||
|
|
||||||
|
// SVG aktualisieren
|
||||||
|
updateVisualization();
|
||||||
|
}
|
||||||
|
|
||||||
|
function endDrag() {
|
||||||
|
if (draggedPointIndex === -1) return;
|
||||||
|
|
||||||
|
// Cursor zurücksetzen
|
||||||
|
pointElements[draggedPointIndex].setAttribute('cursor', 'grab');
|
||||||
|
|
||||||
|
// Variablen zurücksetzen
|
||||||
|
draggedPointIndex = -1;
|
||||||
|
|
||||||
|
// Event-Listener entfernen
|
||||||
|
document.removeEventListener('mousemove', moveDrag);
|
||||||
|
document.removeEventListener('touchmove', moveDrag);
|
||||||
|
document.removeEventListener('mouseup', endDrag);
|
||||||
|
document.removeEventListener('touchend', endDrag);
|
||||||
|
document.removeEventListener('mouseleave', endDrag);
|
||||||
|
|
||||||
|
// Animation fortsetzen, wenn gewünscht
|
||||||
|
if (CONFIG.animationEnabled) {
|
||||||
|
isAnimating = true;
|
||||||
|
startWaveAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pfade aktualisieren oder erstellen
|
||||||
|
function updateOrCreatePath(existingPath, points, normals, offsetDistance, className) {
|
||||||
|
const d = generateOffsetSplinePath(points, normals, offsetDistance);
|
||||||
|
|
||||||
|
if (existingPath) {
|
||||||
|
existingPath.setAttribute('d', d);
|
||||||
|
return existingPath;
|
||||||
|
} else {
|
||||||
|
const path = createSVGElement('path', {
|
||||||
|
'class': className,
|
||||||
|
'stroke': 'url(#lineGradient)',
|
||||||
|
'd': d
|
||||||
|
});
|
||||||
|
svg.appendChild(path);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Punkte aktualisieren
|
||||||
|
function updatePointsVisualization() {
|
||||||
|
points.forEach((p, i) => {
|
||||||
|
if (pointElements[i]) {
|
||||||
|
pointElements[i].setAttribute('cx', p.x);
|
||||||
|
pointElements[i].setAttribute('cy', p.y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hauptfunktion zur Visualisierung
|
||||||
|
function updateVisualization() {
|
||||||
|
// Zeit- und Animations-Faktoren berechnen
|
||||||
|
const time = Date.now() / 1000;
|
||||||
|
const waveOffset1 = Math.sin(time * 1.2) * 1.5;
|
||||||
|
const waveOffset2 = Math.sin(time * 0.8 + 1) * 2;
|
||||||
|
|
||||||
|
// Normalen nur einmal berechnen
|
||||||
|
const normals = calculateNormals(points);
|
||||||
|
const { offsetNear, offsetFar } = CONFIG;
|
||||||
|
|
||||||
|
// Alle Pfade aktualisieren
|
||||||
|
const pathUpdates = [
|
||||||
|
{ key: 'farBg2', offset: (-offsetFar * 1.5) + waveOffset2, className: 'offset-spline-far-2' },
|
||||||
|
{ key: 'farBg1', offset: (-offsetFar * 1.35) + waveOffset2, className: 'offset-spline-far' },
|
||||||
|
{ key: 'nearBg', offset: (-offsetNear * 1.2) + waveOffset1, className: 'offset-spline' },
|
||||||
|
{ key: 'main', offset: 0, className: 'line' },
|
||||||
|
{ key: 'nearFg', offset: (offsetNear * 1.2) + waveOffset1, className: 'offset-spline' },
|
||||||
|
{ key: 'farFg1', offset: (offsetFar * 1.35) + waveOffset2, className: 'offset-spline-far' },
|
||||||
|
{ key: 'farFg2', offset: (offsetFar * 1.5) + waveOffset2, className: 'offset-spline-far-2' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Pfade aktualisieren
|
||||||
|
pathUpdates.forEach(({ key, offset, className }) => {
|
||||||
|
pathElements[key] = updateOrCreatePath(
|
||||||
|
pathElements[key], points, normals, offset, className
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Punkte aktualisieren
|
||||||
|
updatePointsVisualization();
|
||||||
|
|
||||||
|
// Alle Punktelemente nach vorne bringen
|
||||||
|
pointElements.forEach(p => svg.appendChild(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
function startWaveAnimation() {
|
||||||
|
// Animation beenden, falls bereits vorhanden
|
||||||
|
if (currentAnimation) {
|
||||||
|
currentAnimation.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initiale Animation erstellen
|
||||||
|
currentAnimation = anime({
|
||||||
|
targets: points,
|
||||||
|
y: function(p, i) {
|
||||||
|
return CONFIG.baseYPositions[i];
|
||||||
|
},
|
||||||
|
duration: CONFIG.initialAnimDuration,
|
||||||
|
direction: 'alternate',
|
||||||
|
loop: true,
|
||||||
|
easing: 'easeInOutSine',
|
||||||
|
delay: anime.stagger(100),
|
||||||
|
update: updateVisualization,
|
||||||
|
complete: startContinuousAnimation
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startContinuousAnimation() {
|
||||||
|
currentAnimation = anime({
|
||||||
|
targets: points,
|
||||||
|
y: function(p, i) {
|
||||||
|
const baseY = CONFIG.baseYPositions[i];
|
||||||
|
const amplitude = CONFIG.waveAmplitude;
|
||||||
|
return function() {
|
||||||
|
return baseY + amplitude * Math.sin((Date.now()/1000) * Math.PI + i * 0.8);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
duration: CONFIG.contAnimDuration,
|
||||||
|
autoplay: true,
|
||||||
|
easing: 'linear',
|
||||||
|
loop: true,
|
||||||
|
update: function() {
|
||||||
|
requestAnimationFrame(updateVisualization);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Steuerelemente hinzufügen
|
||||||
|
function addControls() {
|
||||||
|
const controlPanel = document.createElement('div');
|
||||||
|
controlPanel.style.marginTop = '20px';
|
||||||
|
|
||||||
|
const toggleButton = document.createElement('button');
|
||||||
|
toggleButton.textContent = 'Animation pausieren';
|
||||||
|
toggleButton.addEventListener('click', function() {
|
||||||
|
if (currentAnimation && currentAnimation.paused) {
|
||||||
|
currentAnimation.play();
|
||||||
|
this.textContent = 'Animation pausieren';
|
||||||
|
} else if (currentAnimation) {
|
||||||
|
currentAnimation.pause();
|
||||||
|
this.textContent = 'Animation fortsetzen';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const resetButton = document.createElement('button');
|
||||||
|
resetButton.textContent = 'Zurücksetzen';
|
||||||
|
resetButton.style.marginLeft = '10px';
|
||||||
|
resetButton.addEventListener('click', resetAnimation);
|
||||||
|
|
||||||
|
controlPanel.appendChild(toggleButton);
|
||||||
|
controlPanel.appendChild(resetButton);
|
||||||
|
document.body.appendChild(controlPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetAnimation() {
|
||||||
|
// Originale Basispositionen wiederherstellen
|
||||||
|
CONFIG.baseYPositions = [100, 70, 110, 80, 130, 90, 100];
|
||||||
|
|
||||||
|
// X-Positionen zurücksetzen
|
||||||
|
const xStep = 580 / (CONFIG.baseYPositions.length - 1);
|
||||||
|
points.forEach((p, i) => {
|
||||||
|
p.x = 20 + i * xStep;
|
||||||
|
p.y = CONFIG.baseYPositions[i];
|
||||||
|
});
|
||||||
|
|
||||||
|
// Visualisierung aktualisieren
|
||||||
|
updateVisualization();
|
||||||
|
|
||||||
|
// Animation neu starten
|
||||||
|
if (CONFIG.animationEnabled) {
|
||||||
|
isAnimating = true;
|
||||||
|
startWaveAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
635
test/anime-points-fine.html
Normal file
635
test/anime-points-fine.html
Normal file
|
|
@ -0,0 +1,635 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Welle Hintergrund (SVG)</title>
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
margin: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
border: 1px solid #ccc; /* Optional: Rahmen zur Visualisierung */
|
||||||
|
overflow: visible; /* Damit Punkte am Rand nicht abgeschnitten werden */
|
||||||
|
}
|
||||||
|
|
||||||
|
.point {
|
||||||
|
fill: #000000; /* Farbe der Punkte */
|
||||||
|
opacity: 0.8; /* Punkte sichtbar machen */
|
||||||
|
r: 3.5; /* Einheitliche Punktgröße */
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
fill: none; /* Keine Füllung für die Linie */
|
||||||
|
stroke-width: 2; /* Dickere Hauptlinie */
|
||||||
|
}
|
||||||
|
|
||||||
|
.offset-spline {
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 1; /* Dickere mittlere Linien */
|
||||||
|
stroke-opacity: 0.6; /* Angepasste Transparenz */
|
||||||
|
transition: stroke-opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offset-spline-far {
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 1; /* Etwas dickere äußere Linien */
|
||||||
|
stroke-opacity: 0.4; /* Höhere Transparenz */
|
||||||
|
transition: stroke-opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
.offset-spline-far-2 {
|
||||||
|
fill: none;
|
||||||
|
stroke-width: 1; /* Etwas dickere äußere Linien */
|
||||||
|
stroke-opacity: 0.2; /* Höhere Transparenz */
|
||||||
|
transition: stroke-opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Steuerelemente für die Slider */
|
||||||
|
.controls {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
width: 600px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-label {
|
||||||
|
width: 140px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-slider {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-value {
|
||||||
|
width: 50px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-header {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 8px 15px;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #45a049;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Welleneffekt (SVG)</h1>
|
||||||
|
|
||||||
|
<svg width="600" height="200" id="point-canvas">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="lineGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
|
<stop offset="0%" style="stop-color:rgb(226, 245, 172);stop-opacity:1" />
|
||||||
|
<stop offset="35%" style="stop-color:rgb(135, 241, 36);stop-opacity:1" />
|
||||||
|
<stop offset="70%" style="stop-color:rgb(62, 200, 232);stop-opacity:1" />
|
||||||
|
<stop offset="100%" style="stop-color:rgb(4, 162, 254);stop-opacity:1" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<!-- Punkte und Linie werden hier per JavaScript hinzugefügt -->
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
<!-- Steuerelemente hinzufügen -->
|
||||||
|
<div class="controls">
|
||||||
|
<div class="control-header">Wellenanimation einstellen</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<div class="control-row">
|
||||||
|
<div class="control-label">Zeitfaktor:</div>
|
||||||
|
<input type="range" id="time-factor" class="control-slider" min="0.2" max="3.0" step="0.1" value="1.0">
|
||||||
|
<div class="control-value"><span id="time-factor-value">1.0</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<div class="control-label">Amplitude:</div>
|
||||||
|
<input type="range" id="amplitude-factor" class="control-slider" min="0.2" max="3.0" step="0.1" value="1.0">
|
||||||
|
<div class="control-value"><span id="amplitude-factor-value">1.0</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<div class="control-label">Turbulenz:</div>
|
||||||
|
<input type="range" id="turbulence" class="control-slider" min="0" max="3" step="0.1" value="0.5">
|
||||||
|
<div class="control-value"><span id="turbulence-value">0.5</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<div class="control-label">Geschwindigkeit:</div>
|
||||||
|
<input type="range" id="animation-speed" class="control-slider" min="0.2" max="10.0" step="0.1" value="1.0">
|
||||||
|
<div class="control-value"><span id="animation-speed-value">1.0</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-header">Linieneinstellungen</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<div class="control-row">
|
||||||
|
<div class="control-label">Linienanzahl:</div>
|
||||||
|
<input type="range" id="total-lines" class="control-slider" min="4" max="24" step="1" value="12">
|
||||||
|
<div class="control-value"><span id="total-lines-value">12</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<div class="control-label">Linienabstand:</div>
|
||||||
|
<input type="range" id="line-offset" class="control-slider" min="5" max="30" step="1" value="12">
|
||||||
|
<div class="control-value"><span id="line-offset-value">12</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<div class="control-label">Linienspannung:</div>
|
||||||
|
<input type="range" id="tension" class="control-slider" min="0.5" max="5.0" step="0.1" value="1.5">
|
||||||
|
<div class="control-value"><span id="tension-value">1.5</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="reset-button">Zurücksetzen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Anime.js wird vorerst nicht benötigt, kann aber später wieder eingebunden werden -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Konstanten und Konfigurationen
|
||||||
|
const svg = document.getElementById('point-canvas');
|
||||||
|
const svgNS = "http://www.w3.org/2000/svg";
|
||||||
|
let TENSION = 1.5;
|
||||||
|
const MIN_OFFSET_FACTOR = 0.2;
|
||||||
|
const OFFSET_NEAR = 5;
|
||||||
|
let OFFSET_FAR = 12;
|
||||||
|
let TOTAL_LINES = 12; // Mehr Linien für ein dichteres Aussehen
|
||||||
|
|
||||||
|
// Neue Variable für Animationsgeschwindigkeit
|
||||||
|
let animationSpeed = 1.0;
|
||||||
|
|
||||||
|
// Variablen für die Animation
|
||||||
|
let currentAnimation = null;
|
||||||
|
let continuousAnimation = null;
|
||||||
|
|
||||||
|
// DOM-Elemente für Steuerelemente
|
||||||
|
const timeFactorSlider = document.getElementById('time-factor');
|
||||||
|
const timeFactorValue = document.getElementById('time-factor-value');
|
||||||
|
const amplitudeFactorSlider = document.getElementById('amplitude-factor');
|
||||||
|
const amplitudeFactorValue = document.getElementById('amplitude-factor-value');
|
||||||
|
const turbulenceSlider = document.getElementById('turbulence');
|
||||||
|
const turbulenceValue = document.getElementById('turbulence-value');
|
||||||
|
const totalLinesSlider = document.getElementById('total-lines');
|
||||||
|
const totalLinesValue = document.getElementById('total-lines-value');
|
||||||
|
const lineOffsetSlider = document.getElementById('line-offset');
|
||||||
|
const lineOffsetValue = document.getElementById('line-offset-value');
|
||||||
|
const tensionSlider = document.getElementById('tension');
|
||||||
|
const tensionValue = document.getElementById('tension-value');
|
||||||
|
const animationSpeedSlider = document.getElementById('animation-speed');
|
||||||
|
const animationSpeedValue = document.getElementById('animation-speed-value');
|
||||||
|
const resetButton = document.getElementById('reset-button');
|
||||||
|
|
||||||
|
// Mouse-tracking Variablen
|
||||||
|
let isMouseOver = false;
|
||||||
|
let mouseX = 0;
|
||||||
|
let mouseY = 0;
|
||||||
|
|
||||||
|
// Dynamische Faktoren
|
||||||
|
let timeFactor = 1.0;
|
||||||
|
let amplitudeFactor = 1.0;
|
||||||
|
let turbulence = 0.5;
|
||||||
|
|
||||||
|
// Basispunkte definieren
|
||||||
|
const points = [
|
||||||
|
{ x: 20, y: 100 },
|
||||||
|
{ x: 150, y: 60 },
|
||||||
|
{ x: 300, y: 140 },
|
||||||
|
{ x: 450, y: 60 },
|
||||||
|
{ x: 580, y: 100 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Speichern der DOM-Elemente für Punkte
|
||||||
|
let pointElements = [];
|
||||||
|
|
||||||
|
// Container für Pfad-Referenzen
|
||||||
|
const pathElements = {};
|
||||||
|
|
||||||
|
// Berechnet Normalen für alle Punkte
|
||||||
|
function calculateNormals(points) {
|
||||||
|
const normals = [];
|
||||||
|
for (let i = 0; i < points.length; i++) {
|
||||||
|
const p0 = i > 0 ? points[i - 1] : points[i];
|
||||||
|
const p1 = points[i];
|
||||||
|
const p2 = i < points.length - 1 ? points[i + 1] : points[i];
|
||||||
|
|
||||||
|
let dx = p2.x - p0.x;
|
||||||
|
let dy = p2.y - p0.y;
|
||||||
|
|
||||||
|
// Tangente an Endpunkten approximieren
|
||||||
|
if (i === 0) {
|
||||||
|
dx = points[1].x - points[0].x;
|
||||||
|
dy = points[1].y - points[0].y;
|
||||||
|
} else if (i === points.length - 1) {
|
||||||
|
dx = points[i].x - points[i-1].x;
|
||||||
|
dy = points[i].y - points[i-1].y;
|
||||||
|
}
|
||||||
|
|
||||||
|
const length = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
let normalX = 0, normalY = 0;
|
||||||
|
|
||||||
|
if (length > 0) {
|
||||||
|
normalX = -dy / length;
|
||||||
|
normalY = dx / length;
|
||||||
|
}
|
||||||
|
|
||||||
|
normals.push({ x: normalX, y: normalY });
|
||||||
|
}
|
||||||
|
return normals;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erzeugt Pfad mit variablem Offset
|
||||||
|
function generateOffsetSplinePath(originalPoints, normals, offsetDistance) {
|
||||||
|
if (originalPoints.length < 2) return "";
|
||||||
|
|
||||||
|
// Startpunkt des Offset-Pfades
|
||||||
|
const startNormal = normals[0];
|
||||||
|
const startOffsetX = originalPoints[0].x + startNormal.x * offsetDistance * MIN_OFFSET_FACTOR;
|
||||||
|
const startOffsetY = originalPoints[0].y + startNormal.y * offsetDistance * MIN_OFFSET_FACTOR;
|
||||||
|
let d = `M ${startOffsetX} ${startOffsetY}`;
|
||||||
|
|
||||||
|
for (let i = 0; i < originalPoints.length - 1; i++) {
|
||||||
|
const p0 = i > 0 ? originalPoints[i - 1] : originalPoints[0];
|
||||||
|
const p1 = originalPoints[i];
|
||||||
|
const p2 = originalPoints[i + 1];
|
||||||
|
const p3 = i < originalPoints.length - 2 ? originalPoints[i + 2] : originalPoints[originalPoints.length - 1];
|
||||||
|
|
||||||
|
// Originale Kontrollpunkte berechnen
|
||||||
|
const cp1x = p1.x + (p2.x - p0.x) / 6 * TENSION;
|
||||||
|
const cp1y = p1.y + (p2.y - p0.y) / 6 * TENSION;
|
||||||
|
const cp2x = p2.x - (p3.x - p1.x) / 6 * TENSION;
|
||||||
|
const cp2y = p2.y - (p3.y - p1.y) / 6 * TENSION;
|
||||||
|
|
||||||
|
// Normalen an den relevanten Punkten
|
||||||
|
const n1 = normals[i];
|
||||||
|
const n2 = normals[i+1];
|
||||||
|
|
||||||
|
// Offset-Kontrollpunkte und Endpunkt berechnen
|
||||||
|
const offsetCp1x = cp1x + n1.x * offsetDistance;
|
||||||
|
const offsetCp1y = cp1y + n1.y * offsetDistance;
|
||||||
|
const offsetCp2x = cp2x + n2.x * offsetDistance;
|
||||||
|
const offsetCp2y = cp2y + n2.y * offsetDistance;
|
||||||
|
const offsetP2x = p2.x + n2.x * offsetDistance * MIN_OFFSET_FACTOR;
|
||||||
|
const offsetP2y = p2.y + n2.y * offsetDistance * MIN_OFFSET_FACTOR;
|
||||||
|
|
||||||
|
// Füge kubisches Bézier-Segment hinzu
|
||||||
|
d += ` C ${offsetCp1x} ${offsetCp1y}, ${offsetCp2x} ${offsetCp2y}, ${offsetP2x} ${offsetP2y}`;
|
||||||
|
}
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erstellt oder aktualisiert einen Pfad
|
||||||
|
function updateOrCreatePath(existingPath, points, normals, offsetDistance, className) {
|
||||||
|
const d = generateOffsetSplinePath(points, normals, offsetDistance);
|
||||||
|
|
||||||
|
if (existingPath) {
|
||||||
|
existingPath.setAttribute('d', d);
|
||||||
|
return existingPath;
|
||||||
|
} else {
|
||||||
|
const path = document.createElementNS(svgNS, 'path');
|
||||||
|
path.setAttribute('class', className);
|
||||||
|
path.setAttribute('stroke', 'url(#lineGradient)');
|
||||||
|
path.setAttribute('d', d);
|
||||||
|
svg.appendChild(path);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erstellt SVG-Punktelemente
|
||||||
|
function createPoints(points) {
|
||||||
|
return points.map(p => {
|
||||||
|
const circle = document.createElementNS(svgNS, 'circle');
|
||||||
|
circle.setAttribute('class', 'point');
|
||||||
|
circle.setAttribute('cx', p.x);
|
||||||
|
circle.setAttribute('cy', p.y);
|
||||||
|
circle.setAttribute('r', 3.5);
|
||||||
|
svg.appendChild(circle);
|
||||||
|
return circle;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aktualisiert Punktpositionen
|
||||||
|
function updatePointsVisualization(points, pointElements) {
|
||||||
|
points.forEach((p, i) => {
|
||||||
|
if (pointElements[i]) {
|
||||||
|
pointElements[i].setAttribute('cx', p.x);
|
||||||
|
pointElements[i].setAttribute('cy', p.y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hauptaktualisierungsfunktion für die Animation
|
||||||
|
function updateVisualization() {
|
||||||
|
const normals = calculateNormals(points);
|
||||||
|
|
||||||
|
// Zeit-basierter Animationsfaktor für die Offset-Linien
|
||||||
|
const time = Date.now() / 1000;
|
||||||
|
|
||||||
|
// Füge turbulente Bewegung hinzu wenn Maus über SVG
|
||||||
|
const turbulenceFactor = true ?
|
||||||
|
turbulence * Math.sin(time * 3) :
|
||||||
|
turbulence * 0.3 * Math.sin(time * 1.5);
|
||||||
|
|
||||||
|
// Zeichne die Hauptlinie
|
||||||
|
pathElements.main = updateOrCreatePath(pathElements.main, points, normals, 0, 'line');
|
||||||
|
|
||||||
|
// Zeichne mehrere Offset-Linien in unterschiedlichen Abständen
|
||||||
|
for (let i = 1; i <= TOTAL_LINES; i++) {
|
||||||
|
// Berechne einen variablen Offset für jede Linie
|
||||||
|
const offsetFactor = i / TOTAL_LINES;
|
||||||
|
const maxOffset = OFFSET_FAR * 2.5;
|
||||||
|
|
||||||
|
|
||||||
|
// Verschiedene dynamische Wellenkomponenten
|
||||||
|
const primaryWave = Math.sin(time * timeFactor * 0.8 + i * 0.2) * 2 * amplitudeFactor;
|
||||||
|
const secondaryWave = Math.cos(time * timeFactor * 0.5 + i * 0.3) * 2 * amplitudeFactor;
|
||||||
|
const tertiaryWave = Math.sin(time * timeFactor * 1.2 + i * 0.15) * turbulenceFactor;
|
||||||
|
|
||||||
|
// Kombiniere alle Wellenkomponenten
|
||||||
|
const wavePhase1 = primaryWave + tertiaryWave;
|
||||||
|
const wavePhase2 = secondaryWave + tertiaryWave * 0.7;
|
||||||
|
|
||||||
|
// Positive Offsets (oberhalb der Hauptlinie)
|
||||||
|
const offsetPositive = maxOffset * offsetFactor + wavePhase1;
|
||||||
|
const className = offsetFactor < 0.3 ? 'offset-spline' :
|
||||||
|
offsetFactor < 0.6 ? 'offset-spline-far' : 'offset-spline-far-2';
|
||||||
|
|
||||||
|
// Speichere jede Linie mit einem eindeutigen Schlüssel
|
||||||
|
const keyPos = `line_pos_${i}`;
|
||||||
|
pathElements[keyPos] = updateOrCreatePath(
|
||||||
|
pathElements[keyPos],
|
||||||
|
points,
|
||||||
|
normals,
|
||||||
|
offsetPositive,
|
||||||
|
className
|
||||||
|
);
|
||||||
|
|
||||||
|
// Negative Offsets (unterhalb der Hauptlinie)
|
||||||
|
const offsetNegative = -maxOffset * offsetFactor + wavePhase2;
|
||||||
|
const keyNeg = `line_neg_${i}`;
|
||||||
|
pathElements[keyNeg] = updateOrCreatePath(
|
||||||
|
pathElements[keyNeg],
|
||||||
|
points,
|
||||||
|
normals,
|
||||||
|
offsetNegative,
|
||||||
|
className
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aktualisiere Punkte
|
||||||
|
updatePointsVisualization(points, pointElements);
|
||||||
|
|
||||||
|
// Alle Punkteelemente nach vorne bringen
|
||||||
|
pointElements.forEach(p => svg.appendChild(p));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animation starten
|
||||||
|
function startAnimation() {
|
||||||
|
// Alle bestehenden Animationen stoppen
|
||||||
|
if (currentAnimation) anime.remove(currentAnimation);
|
||||||
|
if (continuousAnimation) anime.remove(continuousAnimation);
|
||||||
|
|
||||||
|
// Basis-Animation
|
||||||
|
currentAnimation = anime({
|
||||||
|
targets: points,
|
||||||
|
y: function(p, i) {
|
||||||
|
const baseY = [100, 60, 140, 60, 100][i];
|
||||||
|
return baseY;
|
||||||
|
},
|
||||||
|
duration: 100 / animationSpeed,
|
||||||
|
direction: 'alternate',
|
||||||
|
loop: true,
|
||||||
|
easing: 'easeInOutSine',
|
||||||
|
delay: anime.stagger(100 / animationSpeed),
|
||||||
|
update: updateVisualization,
|
||||||
|
complete: startContinuousAnimation
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kontinuierliche wellenförmige Animation
|
||||||
|
function startContinuousAnimation() {
|
||||||
|
// Stoppen einer möglicherweise noch laufenden Animation
|
||||||
|
if (continuousAnimation) anime.remove(continuousAnimation);
|
||||||
|
|
||||||
|
continuousAnimation = anime({
|
||||||
|
targets: points,
|
||||||
|
y: function(p, i) {
|
||||||
|
const baseY = i % 2 === 0 ? 100 : i % 3 === 0 ? 60 : 140;
|
||||||
|
const amplitude = 20;
|
||||||
|
return function() {
|
||||||
|
// Komplexere Wellenbewegung mit mehreren Frequenzen und dynamischen Faktoren
|
||||||
|
const t = Date.now() / 1000 * animationSpeed; // Direkte Multiplikation der Zeit mit der Geschwindigkeit
|
||||||
|
|
||||||
|
// Berechne dynamische Amplitude basierend auf Position und Zeit
|
||||||
|
const dynamicAmplitude = amplitude * amplitudeFactor;
|
||||||
|
|
||||||
|
// Mehrere überlagerte Wellen mit dynamischen Faktoren
|
||||||
|
const wave1 = dynamicAmplitude * Math.sin(t * timeFactor * 0.8 + i * 0.7);
|
||||||
|
const wave2 = dynamicAmplitude/2 * Math.sin(t * timeFactor * 1.2 + i * 0.5);
|
||||||
|
const wave3 = dynamicAmplitude/4 * Math.sin(t * timeFactor * 2.1 + i * 0.3);
|
||||||
|
|
||||||
|
// Füge Mauseinfluss hinzu wenn Maus über dem SVG
|
||||||
|
let mouseEffect = 0;
|
||||||
|
if (isMouseOver) {
|
||||||
|
const dx = p.x - mouseX;
|
||||||
|
const distanceFactor = Math.max(0, 1 - Math.abs(dx) / 200);
|
||||||
|
mouseEffect = (mouseY - baseY) * distanceFactor * 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseY + wave1 + wave2 + wave3 + mouseEffect;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
duration: (1000 / animationSpeed),
|
||||||
|
autoplay: true,
|
||||||
|
easing: 'linear',
|
||||||
|
loop: true,
|
||||||
|
update: function() {
|
||||||
|
requestAnimationFrame(updateVisualization);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event-Listener für Steuerelemente
|
||||||
|
function setupControlListeners() {
|
||||||
|
// Zeitfaktor
|
||||||
|
timeFactorSlider.addEventListener('input', function() {
|
||||||
|
timeFactor = parseFloat(this.value);
|
||||||
|
timeFactorValue.textContent = timeFactor.toFixed(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Amplitudenfaktor
|
||||||
|
amplitudeFactorSlider.addEventListener('input', function() {
|
||||||
|
amplitudeFactor = parseFloat(this.value);
|
||||||
|
amplitudeFactorValue.textContent = amplitudeFactor.toFixed(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Turbulenz
|
||||||
|
turbulenceSlider.addEventListener('input', function() {
|
||||||
|
turbulence = parseFloat(this.value);
|
||||||
|
turbulenceValue.textContent = turbulence.toFixed(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Animationsgeschwindigkeit
|
||||||
|
animationSpeedSlider.addEventListener('input', function() {
|
||||||
|
const newSpeed = parseFloat(this.value);
|
||||||
|
|
||||||
|
// Nur aktualisieren, wenn sich der Wert tatsächlich ändert
|
||||||
|
if (newSpeed !== animationSpeed) {
|
||||||
|
animationSpeed = newSpeed;
|
||||||
|
animationSpeedValue.textContent = animationSpeed.toFixed(1);
|
||||||
|
// Animation komplett neu starten, um Geschwindigkeitsänderungen zu übernehmen
|
||||||
|
anime.remove(points);
|
||||||
|
if (currentAnimation) anime.remove(currentAnimation);
|
||||||
|
if (continuousAnimation) anime.remove(continuousAnimation);
|
||||||
|
startAnimation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gesamtanzahl der Linien
|
||||||
|
totalLinesSlider.addEventListener('input', function() {
|
||||||
|
TOTAL_LINES = parseInt(this.value);
|
||||||
|
totalLinesValue.textContent = TOTAL_LINES;
|
||||||
|
|
||||||
|
// Bestehende Linien entfernen, die nicht mehr benötigt werden
|
||||||
|
for (let key in pathElements) {
|
||||||
|
if (key.startsWith('line_pos_') || key.startsWith('line_neg_')) {
|
||||||
|
const lineNum = parseInt(key.split('_').pop());
|
||||||
|
if (lineNum > TOTAL_LINES) {
|
||||||
|
if (pathElements[key].parentNode) {
|
||||||
|
pathElements[key].parentNode.removeChild(pathElements[key]);
|
||||||
|
}
|
||||||
|
delete pathElements[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Linienabstand
|
||||||
|
lineOffsetSlider.addEventListener('input', function() {
|
||||||
|
OFFSET_FAR = parseInt(this.value);
|
||||||
|
lineOffsetValue.textContent = OFFSET_FAR;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Linienspannung
|
||||||
|
tensionSlider.addEventListener('input', function() {
|
||||||
|
TENSION = parseFloat(this.value);
|
||||||
|
tensionValue.textContent = TENSION.toFixed(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Zurücksetzen-Button
|
||||||
|
resetButton.addEventListener('click', function() {
|
||||||
|
// Standardwerte wiederherstellen
|
||||||
|
timeFactorSlider.value = 1.0;
|
||||||
|
amplitudeFactorSlider.value = 1.0;
|
||||||
|
turbulenceSlider.value = 0.5;
|
||||||
|
totalLinesSlider.value = 12;
|
||||||
|
lineOffsetSlider.value = 12;
|
||||||
|
tensionSlider.value = 1.5;
|
||||||
|
animationSpeedSlider.value = 1.0;
|
||||||
|
|
||||||
|
// UI-Werte aktualisieren
|
||||||
|
timeFactorValue.textContent = "1.0";
|
||||||
|
amplitudeFactorValue.textContent = "1.0";
|
||||||
|
turbulenceValue.textContent = "0.5";
|
||||||
|
totalLinesValue.textContent = "12";
|
||||||
|
lineOffsetValue.textContent = "12";
|
||||||
|
tensionValue.textContent = "1.5";
|
||||||
|
animationSpeedValue.textContent = "1.0";
|
||||||
|
|
||||||
|
// Variablen zurücksetzen
|
||||||
|
timeFactor = 1.0;
|
||||||
|
amplitudeFactor = 1.0;
|
||||||
|
turbulence = 0.5;
|
||||||
|
TOTAL_LINES = 12;
|
||||||
|
OFFSET_FAR = 12;
|
||||||
|
TENSION = 1.5;
|
||||||
|
animationSpeed = 1.0;
|
||||||
|
|
||||||
|
// Animation neu starten
|
||||||
|
anime.remove(points);
|
||||||
|
if (currentAnimation) anime.remove(currentAnimation);
|
||||||
|
if (continuousAnimation) anime.remove(continuousAnimation);
|
||||||
|
startAnimation();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mouse-Tracking für SVG
|
||||||
|
svg.addEventListener('mouseover', function() {
|
||||||
|
isMouseOver = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
svg.addEventListener('mouseout', function() {
|
||||||
|
isMouseOver = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
svg.addEventListener('mousemove', function(e) {
|
||||||
|
const rect = svg.getBoundingClientRect();
|
||||||
|
mouseX = e.clientX - rect.left;
|
||||||
|
mouseY = e.clientY - rect.top;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialisierung
|
||||||
|
function init() {
|
||||||
|
pointElements = createPoints(points);
|
||||||
|
setupControlListeners();
|
||||||
|
updateVisualization();
|
||||||
|
startAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starte die Anwendung
|
||||||
|
init();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1060
test/anime-points.html
Normal file
1060
test/anime-points.html
Normal file
File diff suppressed because it is too large
Load diff
151
test/anime-wave.html
Normal file
151
test/anime-wave.html
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Animierte Welle</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
background-color: #ffffff; /* Weißer Hintergrund */
|
||||||
|
overflow: hidden; /* Verhindert Scrollbalken, falls Welle überläuft */
|
||||||
|
position: relative; /* Für absolute Positionierung des Textes */
|
||||||
|
min-height: 100vh; /* Mindesthöhe für Demo */
|
||||||
|
display: flex;
|
||||||
|
align-items: center; /* Zentriert den Inhalt vertikal */
|
||||||
|
justify-content: center; /* Zentriert den Inhalt horizontal */
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-container {
|
||||||
|
width: 100%;
|
||||||
|
position: absolute; /* Positioniert die Welle */
|
||||||
|
bottom: 0; /* Am unteren Rand */
|
||||||
|
left: 0;
|
||||||
|
line-height: 0; /* Entfernt zusätzlichen Leerraum unter dem SVG */
|
||||||
|
}
|
||||||
|
|
||||||
|
#waveSvg {
|
||||||
|
display: block; /* Entfernt zusätzlichen Leerraum */
|
||||||
|
width: 100%;
|
||||||
|
height: 250px; /* Höhe der Welle anpassen */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optionaler Text */
|
||||||
|
.wave-text {
|
||||||
|
position: absolute;
|
||||||
|
top: 40%; /* Position anpassen */
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
color: #555; /* Dunkelgrau */
|
||||||
|
font-size: 1.5em;
|
||||||
|
letter-spacing: 5px;
|
||||||
|
z-index: 10; /* Stellt sicher, dass der Text über der Welle liegt (falls sie höher ist) */
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="wave-container">
|
||||||
|
<!-- SVG Container für die Welle -->
|
||||||
|
<svg id="waveSvg" viewBox="0 0 1440 300" preserveAspectRatio="none">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="waveGradient1" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
|
<stop offset="0%" style="stop-color:rgba(120, 255, 150, 0.7);" /> <!-- Helles Grün -->
|
||||||
|
<stop offset="40%" style="stop-color:rgba(0, 200, 230, 0.7);" /> <!-- Türkis/Cyan -->
|
||||||
|
<stop offset="100%" style="stop-color:rgba(0, 120, 220, 0.7);" /> <!-- Helles Blau -->
|
||||||
|
</linearGradient>
|
||||||
|
<!-- Zweiter Verlauf für eine weitere Ebene (leicht variiert) -->
|
||||||
|
<linearGradient id="waveGradient2" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
|
<stop offset="0%" style="stop-color:rgba(80, 230, 120, 0.5);" /> <!-- Etwas dunkleres Grün -->
|
||||||
|
<stop offset="50%" style="stop-color:rgba(0, 170, 210, 0.5);" />
|
||||||
|
<stop offset="100%" style="stop-color:rgba(0, 90, 190, 0.5);" /> <!-- Etwas dunkleres Blau -->
|
||||||
|
</linearGradient>
|
||||||
|
<!-- Dritter Verlauf für die feinen Linien (noch transparenter) -->
|
||||||
|
<linearGradient id="waveGradient3" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
|
<stop offset="0%" style="stop-color:rgba(180, 255, 200, 0.3);" />
|
||||||
|
<stop offset="60%" style="stop-color:rgba(50, 200, 250, 0.3);" />
|
||||||
|
<stop offset="100%" style="stop-color:rgba(30, 130, 240, 0.3);" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- Pfade für die Wellenformen -->
|
||||||
|
<!-- Diese 'd' Attributes definieren die Form. Wir werden sie animieren. -->
|
||||||
|
<!-- M = MoveTo, C = Cubic Bezier Curve, S = Smooth Cubic Bezier Curve, L = LineTo, Z = ClosePath -->
|
||||||
|
<!-- Die Koordinaten basieren auf der viewBox (0 0 1440 300) -->
|
||||||
|
<path id="wavePath1" d="M0,160 C360,80 720,240 1080,160 S1440,80 1440,160 L1440,300 L0,300 Z" fill="url(#waveGradient1)" />
|
||||||
|
<path id="wavePath2" d="M0,180 C360,100 720,260 1080,180 S1440,100 1440,180 L1440,300 L0,300 Z" fill="url(#waveGradient2)" />
|
||||||
|
<path id="wavePath3" d="M0,200 C360,120 720,280 1080,200 S1440,120 1440,200 L1440,300 L0,300 Z" fill="url(#waveGradient3)" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Text (optional, wie im Beispielbild) -->
|
||||||
|
<div class="wave-text">
|
||||||
|
WAVE BACKGROUND
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Anime.js Bibliothek einbinden -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
|
||||||
|
<!-- Unser eigenes JavaScript -->
|
||||||
|
<script>
|
||||||
|
// Warten bis das DOM vollständig geladen ist
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
|
// Definition der Start- und Endformen für die Animation (d-Attribute)
|
||||||
|
// Pfad 1
|
||||||
|
const path1_start = "M0,80 C150,150 350,0 500,80 S850,150 1000,50 L1000,200 L0,200 Z";
|
||||||
|
const path1_end = "M0,50 C150,0 350,150 500,80 S850,0 1000,80 L1000,200 L0,200 Z";
|
||||||
|
|
||||||
|
// Pfad 2 (leicht versetzt)
|
||||||
|
const path2_start = "M0,100 C180,180 380,30 500,100 S820,180 1000,70 L1000,200 L0,200 Z";
|
||||||
|
const path2_end = "M0,70 C180,30 380,180 500,100 S820,30 1000,100 L1000,200 L0,200 Z";
|
||||||
|
|
||||||
|
// Pfad 3 (nochmals leicht versetzt)
|
||||||
|
const path3_start = "M0,120 C200,200 400,50 500,120 S800,200 1000,90 L1000,200 L0,200 Z";
|
||||||
|
const path3_end = "M0,90 C200,50 400,200 500,120 S800,50 1000,120 L1000,200 L0,200 Z";
|
||||||
|
|
||||||
|
|
||||||
|
// Anime.js Animation für jeden Pfad
|
||||||
|
anime({
|
||||||
|
targets: '#wavePath1', // Ziel ist der Pfad mit ID wavePath1
|
||||||
|
d: [ // Animiere das 'd' Attribut
|
||||||
|
{ value: path1_start }, // Startform (optional, falls schon im HTML gesetzt)
|
||||||
|
{ value: path1_end } // Endform
|
||||||
|
],
|
||||||
|
duration: 4000, // Dauer der Animation in ms
|
||||||
|
easing: 'easeInOutSine', // Easing-Funktion für sanfte Bewegung
|
||||||
|
direction: 'alternate', // Animation läuft hin und zurück
|
||||||
|
loop: true // Animation wiederholt sich unendlich
|
||||||
|
});
|
||||||
|
|
||||||
|
anime({
|
||||||
|
targets: '#wavePath2',
|
||||||
|
d: [
|
||||||
|
{ value: path2_start },
|
||||||
|
{ value: path2_end }
|
||||||
|
],
|
||||||
|
duration: 3500, // Leicht andere Dauer für Asynchronität
|
||||||
|
delay: 150, // Kleiner Startversatz
|
||||||
|
easing: 'easeInOutSine',
|
||||||
|
direction: 'alternate',
|
||||||
|
loop: true
|
||||||
|
});
|
||||||
|
|
||||||
|
anime({
|
||||||
|
targets: '#wavePath3',
|
||||||
|
d: [
|
||||||
|
{ value: path3_start },
|
||||||
|
{ value: path3_end }
|
||||||
|
],
|
||||||
|
duration: 4500, // Nochmals andere Dauer
|
||||||
|
delay: 50, // Kleiner Startversatz
|
||||||
|
easing: 'easeInOutSine',
|
||||||
|
direction: 'alternate',
|
||||||
|
loop: true
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
91
test/anime.html
Normal file
91
test/anime.html
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Anime.js Welle</title>
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column; /* Damit H1 über der Welle steht */
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
margin: 0;
|
||||||
|
font-family: sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-container {
|
||||||
|
display: flex; /* Elemente nebeneinander anordnen */
|
||||||
|
align-items: center; /* Vertikal zentrieren (Startposition) */
|
||||||
|
height: 100px; /* Höhe für die Wellenbewegung */
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave-dot {
|
||||||
|
width: 10px; /* Breite des Punktes */
|
||||||
|
height: 10px; /* Höhe des Punktes */
|
||||||
|
margin: 0 2px; /* Kleiner Abstand zwischen Punkten */
|
||||||
|
background-color: #0077cc; /* Farbe der Punkte */
|
||||||
|
border-radius: 50%; /* Macht die Quadrate zu Kreisen (Punkten) */
|
||||||
|
/* Wichtig: transform-origin anpassen, falls nötig (hier nicht unbedingt) */
|
||||||
|
/* transform-origin: center bottom; */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Dynamische Welle mit Anime.js</h1>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="wave-container"><!-- Die Wellenpunkte werden per JavaScript hinzugefügt -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.1/anime.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
const container = document.querySelector('.wave-container');
|
||||||
|
const numberOfDots = 50;
|
||||||
|
// Anzahl der Punkte in der Welle
|
||||||
|
|
||||||
|
// --- Schritt 1: Wellenpunkte dynamisch erstellen ---
|
||||||
|
for (let i = 0; i < numberOfDots; i++) {
|
||||||
|
const dot = document.createElement('div');
|
||||||
|
dot.classList.add('wave-dot');
|
||||||
|
container.appendChild(dot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Schritt 2: Anime.js Animation definieren ---
|
||||||
|
anime({
|
||||||
|
targets: '.wave-dot', // Wähle alle Elemente mit der Klasse 'wave-dot'
|
||||||
|
translateY: [
|
||||||
|
{ // Animiere die vertikale Position
|
||||||
|
value: -30,
|
||||||
|
duration: 600
|
||||||
|
}, { // Bewege 30px nach oben (Dauer: 600ms)
|
||||||
|
value: 0,
|
||||||
|
duration: 600
|
||||||
|
} // Bewege zurück zur Ausgangsposition (Dauer: 600ms)
|
||||||
|
],
|
||||||
|
easing: 'easeInOutSine', // Sorgt für sanfte Beschleunigung/Verzögerung
|
||||||
|
loop: true, // Die Animation soll sich unendlich wiederholen
|
||||||
|
delay: anime.stagger(30)
|
||||||
|
// DER SCHLÜSSEL: Verzögert den Start der Animation
|
||||||
|
// für jedes Element um 30ms gegenüber dem vorherigen.
|
||||||
|
// Dies erzeugt den Welleneffekt!
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
// Alternative Animation (kontinuierlicher auf/ab):
|
||||||
|
anime({
|
||||||
|
targets: '.wave-dot',
|
||||||
|
translateY: ['-25px', '25px'], // Oszilliert zwischen -25px und +25px
|
||||||
|
direction: 'alternate', // Kehrt die Richtung am Ende jeder Iteration um
|
||||||
|
loop: true,
|
||||||
|
duration: 800, // Dauer für eine halbe Oszillation (hoch ODER runter)
|
||||||
|
easing: 'easeInOutSine',
|
||||||
|
delay: anime.stagger(30) // Wellenverzögerung
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
124
test/index.html
Normal file
124
test/index.html
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Test-Übersicht</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background-color: #2c3e50;
|
||||||
|
color: white;
|
||||||
|
padding: 1.5rem;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-card {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: transform 0.3s, box-shadow 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-card-header {
|
||||||
|
background-color: #3498db;
|
||||||
|
color: white;
|
||||||
|
padding: 1rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-card-body {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-card-body p {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-link {
|
||||||
|
display: inline-block;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #3498db;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-link:hover {
|
||||||
|
border-color: #3498db;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<h1>Test-Übersicht</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<p>Testseite. Hier werden verfügbaren Tests angezeigt.</p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="test-grid">
|
||||||
|
|
||||||
|
<div class="test-card">
|
||||||
|
<div class="test-card-header">Animation!</div>
|
||||||
|
<div class="test-card-body">
|
||||||
|
<p>Ein Test für Animationen mit Controller und Anime.js Bibliothek.</p>
|
||||||
|
<a href="anime-points-animation.html" class="test-link">Animierte Welle anzeigen</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="test-card">
|
||||||
|
<div class="test-card-header">Animation</div>
|
||||||
|
<div class="test-card-body">
|
||||||
|
<p>Ein Test einer Background Welle mit Anime.js Bibliothek.</p>
|
||||||
|
<a href="anime-wave.html" class="test-link">Welle anzeigen</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Platzhalter für weitere Testdateien -->
|
||||||
|
<div class="test-card">
|
||||||
|
<div class="test-card-header">Anime.js Punkte</div>
|
||||||
|
<div class="test-card-body">
|
||||||
|
<p>Ein Test für bewegliche Punkte mit Anime.js Bibliothek.</p>
|
||||||
|
<a href="anime-points-drag.html" class="test-link">bewegliche Punkte anzeigen</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue