@andresclua/perlindom 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/PerlinDOM.es.js +98 -0
- package/dist/PerlinDOM.umd.js +1 -0
- package/docs/examples.html +385 -0
- package/package.json +33 -0
- package/public/vite.svg +1 -0
- package/readme.md +271 -0
- package/src/examples.html +515 -0
- package/src/index.html +655 -0
- package/src/js/PerlinDom.js +215 -0
- package/src/js/PerlinSVG.js +190 -0
- package/src/js/animated-button.js +40 -0
- package/src/js/basic-examples.js +43 -0
- package/src/js/index.js +13 -0
- package/src/js/interactive-controls.js +43 -0
- package/src/js/pause-play.js +33 -0
- package/src/js/triangle-animation.js +48 -0
- package/src/scss/style.scss +20 -0
- package/vite.config.js +36 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
class PerlinDOM {
|
|
2
|
+
/* ---------- helpers privados y estáticos ---------- */
|
|
3
|
+
static #mulberry32(a) {
|
|
4
|
+
return function () {
|
|
5
|
+
let t = (a += 0x6d2b79f5);
|
|
6
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
7
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
8
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static #generatePermutation(seed = 0) {
|
|
13
|
+
const permutation = Array.from({ length: 256 }, (_, i) => i);
|
|
14
|
+
const rand = PerlinDOM.#mulberry32(seed);
|
|
15
|
+
for (let i = 255; i > 0; i--) {
|
|
16
|
+
const j = Math.floor(rand() * (i + 1));
|
|
17
|
+
[permutation[i], permutation[j]] = [permutation[j], permutation[i]];
|
|
18
|
+
}
|
|
19
|
+
return permutation.concat(permutation);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
constructor({
|
|
23
|
+
element,
|
|
24
|
+
x = null,
|
|
25
|
+
y = null,
|
|
26
|
+
speed = 0.01,
|
|
27
|
+
seed = 0,
|
|
28
|
+
lerpSpeed = 0.1
|
|
29
|
+
}) {
|
|
30
|
+
this.el = element;
|
|
31
|
+
this.xRange = x;
|
|
32
|
+
this.yRange = y;
|
|
33
|
+
this.speed = speed;
|
|
34
|
+
this.time = seed;
|
|
35
|
+
this.isPlaying = true;
|
|
36
|
+
this.lerpFactor = 1; // 1 = full speed, 0 = paused
|
|
37
|
+
this.lerpSpeed = lerpSpeed; // How fast to transition between play/pause states
|
|
38
|
+
this.targetLerpFactor = 1; // Target state (1 = play, 0 = pause)
|
|
39
|
+
this.lastX = 0;
|
|
40
|
+
this.lastY = 0;
|
|
41
|
+
|
|
42
|
+
// Solo inicializar estas propiedades si el elemento no es null
|
|
43
|
+
if (element) {
|
|
44
|
+
this.initialX = element.offsetLeft;
|
|
45
|
+
this.initialY = element.offsetTop;
|
|
46
|
+
} else {
|
|
47
|
+
this.initialX = 0;
|
|
48
|
+
this.initialY = 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.animate = this.animate.bind(this);
|
|
52
|
+
|
|
53
|
+
this.init(seed);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
init(seed) {
|
|
57
|
+
this.noise = this.#createPerlin(PerlinDOM.#generatePermutation(seed));
|
|
58
|
+
this.isPlaying = true;
|
|
59
|
+
this.lerpFactor = 1;
|
|
60
|
+
this.targetLerpFactor = 1;
|
|
61
|
+
this.raf = requestAnimationFrame(this.animate);
|
|
62
|
+
console.log('PerlinDOM initialized');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
pause() {
|
|
66
|
+
if (this.isPlaying) {
|
|
67
|
+
this.targetLerpFactor = 0; // Target paused state
|
|
68
|
+
this.isPlaying = false;
|
|
69
|
+
console.log('PerlinDOM pausing...');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
play() {
|
|
74
|
+
if (!this.isPlaying) {
|
|
75
|
+
this.targetLerpFactor = 1; // Target playing state
|
|
76
|
+
this.isPlaying = true;
|
|
77
|
+
|
|
78
|
+
// Only start a new animation frame if we're completely paused
|
|
79
|
+
if (this.lerpFactor === 0) {
|
|
80
|
+
this.raf = requestAnimationFrame(this.animate);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log('PerlinDOM playing...');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#createPerlin(p) {
|
|
88
|
+
const fade = t => t * t * t * (t * (t * 6 - 15) + 10);
|
|
89
|
+
const lerp = (t, a, b) => a + t * (b - a);
|
|
90
|
+
|
|
91
|
+
// Enhanced gradient function for 2D
|
|
92
|
+
const grad2D = (hash, x, y) => {
|
|
93
|
+
const h = hash & 15;
|
|
94
|
+
const u = h < 8 ? x : y;
|
|
95
|
+
const v = h < 4 ? y : (h === 12 || h === 14 ? x : 0);
|
|
96
|
+
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// 1D gradient (kept for backward compatibility)
|
|
100
|
+
const grad1D = (hash, x) => (hash & 1 ? -x : x);
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
// Original 1D Perlin noise function
|
|
104
|
+
perlin1(x) {
|
|
105
|
+
const X = Math.floor(x) & 255;
|
|
106
|
+
x -= Math.floor(x);
|
|
107
|
+
const u = fade(x);
|
|
108
|
+
return lerp(u, grad1D(p[X], x), grad1D(p[X + 1], x - 1));
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
// New 2D Perlin noise function
|
|
112
|
+
perlin2(x, y) {
|
|
113
|
+
// Integer parts of coordinates
|
|
114
|
+
const X = Math.floor(x) & 255;
|
|
115
|
+
const Y = Math.floor(y) & 255;
|
|
116
|
+
|
|
117
|
+
// Fractional parts of coordinates
|
|
118
|
+
x -= Math.floor(x);
|
|
119
|
+
y -= Math.floor(y);
|
|
120
|
+
|
|
121
|
+
// Fade curves for x and y
|
|
122
|
+
const u = fade(x);
|
|
123
|
+
const v = fade(y);
|
|
124
|
+
|
|
125
|
+
// Hash coordinates of the 4 square corners
|
|
126
|
+
const A = p[X] + Y;
|
|
127
|
+
const B = p[X + 1] + Y;
|
|
128
|
+
const AA = p[A];
|
|
129
|
+
const AB = p[A + 1];
|
|
130
|
+
const BA = p[B];
|
|
131
|
+
const BB = p[B + 1];
|
|
132
|
+
|
|
133
|
+
// Blend the four corner gradients
|
|
134
|
+
return lerp(
|
|
135
|
+
v,
|
|
136
|
+
lerp(
|
|
137
|
+
u,
|
|
138
|
+
grad2D(p[AA], x, y),
|
|
139
|
+
grad2D(p[BA], x - 1, y)
|
|
140
|
+
),
|
|
141
|
+
lerp(
|
|
142
|
+
u,
|
|
143
|
+
grad2D(p[AB], x, y - 1),
|
|
144
|
+
grad2D(p[BB], x - 1, y - 1)
|
|
145
|
+
)
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#lerp(a, b, t) {
|
|
152
|
+
return a + t * (b - a);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
animate = () => {
|
|
156
|
+
// Update lerp factor towards target
|
|
157
|
+
if (this.lerpFactor !== this.targetLerpFactor) {
|
|
158
|
+
this.lerpFactor = this.#lerp(
|
|
159
|
+
this.lerpFactor,
|
|
160
|
+
this.targetLerpFactor,
|
|
161
|
+
this.lerpSpeed
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Snap to target if very close
|
|
165
|
+
if (Math.abs(this.lerpFactor - this.targetLerpFactor) < 0.001) {
|
|
166
|
+
this.lerpFactor = this.targetLerpFactor;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Only update time if not completely paused
|
|
171
|
+
if (this.lerpFactor > 0) {
|
|
172
|
+
// Scale time increment by lerp factor for smooth speed transition
|
|
173
|
+
this.time += this.speed * this.lerpFactor;
|
|
174
|
+
|
|
175
|
+
// Use 2D Perlin noise for more natural movement
|
|
176
|
+
// We use time for one dimension and a fixed offset for the other
|
|
177
|
+
// This creates a path through the 2D noise field
|
|
178
|
+
const noiseX = this.noise.perlin2(this.time, 0);
|
|
179
|
+
const noiseY = this.noise.perlin2(this.time, 100); // Different slice of the noise field
|
|
180
|
+
|
|
181
|
+
const valX = this.xRange
|
|
182
|
+
? this.#map(noiseX, -1, 1, this.xRange.min, this.xRange.max)
|
|
183
|
+
: 0;
|
|
184
|
+
|
|
185
|
+
const valY = this.yRange
|
|
186
|
+
? this.#map(noiseY, -1, 1, this.yRange.min, this.yRange.max)
|
|
187
|
+
: 0;
|
|
188
|
+
|
|
189
|
+
// Store current values for interpolation
|
|
190
|
+
this.lastX = valX;
|
|
191
|
+
this.lastY = valY;
|
|
192
|
+
|
|
193
|
+
// Update position using left/top instead of transform
|
|
194
|
+
// Solo actualizar el elemento si existe
|
|
195
|
+
if (this.el) {
|
|
196
|
+
this.el.style.left = `${this.initialX + valX}px`;
|
|
197
|
+
this.el.style.top = `${this.initialY + valY}px`;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Continue animation if not completely paused or still transitioning
|
|
202
|
+
if (this.lerpFactor > 0 || this.targetLerpFactor > 0) {
|
|
203
|
+
this.raf = requestAnimationFrame(this.animate);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
#map(n, inMin, inMax, outMin, outMax) {
|
|
208
|
+
return ((n - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
destroy() {
|
|
212
|
+
cancelAnimationFrame(this.raf);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
export default PerlinDOM;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import PerlinDOM from './PerlinDom.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PerlinSVG - Clase para animar paths SVG usando ruido Perlin
|
|
5
|
+
*
|
|
6
|
+
* Esta clase extiende PerlinDOM para proporcionar animaciones orgánicas
|
|
7
|
+
* a elementos SVG path, creando efectos de ondulación fluidos y naturales.
|
|
8
|
+
*/
|
|
9
|
+
class PerlinSVG extends PerlinDOM {
|
|
10
|
+
/**
|
|
11
|
+
* Constructor de PerlinSVG
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} options - Opciones de configuración
|
|
14
|
+
* @param {Element|Element[]} options.element - El/los elemento(s) SVG path a animar
|
|
15
|
+
* @param {number} options.numPoints - Número de puntos de control para cada path (más puntos = curvas más detalladas)
|
|
16
|
+
* @param {number} options.amplitude - Amplitud máxima de la animación (altura de las ondas en píxeles)
|
|
17
|
+
* @param {boolean} options.isOpen - Estado inicial (true = abierto/visible, false = cerrado/oculto)
|
|
18
|
+
* @param {number} options.speed - Velocidad de la animación (valores más altos = movimiento más rápido)
|
|
19
|
+
* @param {number} options.seed - Semilla para el generador de ruido Perlin (misma semilla = mismo patrón)
|
|
20
|
+
* @param {number} options.lerpSpeed - Velocidad de interpolación entre estados (valores más altos = transiciones más rápidas)
|
|
21
|
+
* @param {number} options.delayPointsMax - Retraso máximo aleatorio para cada punto (crea efectos de cascada)
|
|
22
|
+
* @param {number} options.delayPerPath - Retraso entre capas/paths consecutivos (crea efecto escalonado)
|
|
23
|
+
*/
|
|
24
|
+
constructor({
|
|
25
|
+
element, // El/los elemento(s) SVG path a animar
|
|
26
|
+
numPoints = 10, // Número de puntos de control (más puntos = curvas más detalladas)
|
|
27
|
+
amplitude = 100, // Amplitud máxima de la animación (altura de las ondas)
|
|
28
|
+
isOpen = false, // Estado inicial (true = abierto/visible, false = cerrado/oculto)
|
|
29
|
+
speed = 0.01, // Velocidad de la animación (valores más altos = movimiento más rápido)
|
|
30
|
+
seed = 0, // Semilla para el generador de ruido Perlin (misma semilla = mismo patrón)
|
|
31
|
+
lerpSpeed = 0.1, // Velocidad de interpolación entre estados
|
|
32
|
+
delayPointsMax = 0.3, // Retraso máximo aleatorio para cada punto (crea efectos de cascada)
|
|
33
|
+
delayPerPath = 0.25 // Retraso entre capas/paths consecutivos (crea efecto escalonado)
|
|
34
|
+
}) {
|
|
35
|
+
// Call parent constructor with null element
|
|
36
|
+
// since we'll handle the SVG differently
|
|
37
|
+
super({
|
|
38
|
+
element: null,
|
|
39
|
+
speed: speed,
|
|
40
|
+
seed: seed,
|
|
41
|
+
lerpSpeed: lerpSpeed
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// SVG specific properties
|
|
45
|
+
this.svgElement = element;
|
|
46
|
+
this.paths = Array.isArray(element) ? element : [element];
|
|
47
|
+
this.numPoints = numPoints;
|
|
48
|
+
this.amplitude = amplitude;
|
|
49
|
+
this.isOpen = isOpen;
|
|
50
|
+
this.delayPointsMax = delayPointsMax;
|
|
51
|
+
this.delayPerPath = delayPerPath;
|
|
52
|
+
this.isAnimating = false;
|
|
53
|
+
|
|
54
|
+
// Arrays to store points and delays
|
|
55
|
+
this.allPoints = [];
|
|
56
|
+
this.pointsDelay = [];
|
|
57
|
+
|
|
58
|
+
// Initialize points and delays
|
|
59
|
+
this.initPoints();
|
|
60
|
+
|
|
61
|
+
// Add click event
|
|
62
|
+
if (this.paths.length > 0 && this.paths[0].ownerSVGElement) {
|
|
63
|
+
this.paths[0].ownerSVGElement.addEventListener('click', () => this.toggle());
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Initialize points for all paths
|
|
68
|
+
initPoints() {
|
|
69
|
+
// Initialize delays for points
|
|
70
|
+
for (let i = 0; i < this.numPoints; i++) {
|
|
71
|
+
this.pointsDelay[i] = Math.random() * this.delayPointsMax;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Initialize points for each path
|
|
75
|
+
for (let i = 0; i < this.paths.length; i++) {
|
|
76
|
+
let points = [];
|
|
77
|
+
this.allPoints.push(points);
|
|
78
|
+
|
|
79
|
+
for (let j = 0; j < this.numPoints; j++) {
|
|
80
|
+
points.push(this.isOpen ? 0 : this.amplitude);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Render initial paths
|
|
85
|
+
this.render();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Toggle between open/closed states
|
|
89
|
+
toggle() {
|
|
90
|
+
if (this.isAnimating) return;
|
|
91
|
+
|
|
92
|
+
this.isOpen = !this.isOpen;
|
|
93
|
+
this.isAnimating = true;
|
|
94
|
+
|
|
95
|
+
// Schedule animations for each point with Perlin noise-based delays
|
|
96
|
+
const startTime = performance.now();
|
|
97
|
+
const animate = (timestamp) => {
|
|
98
|
+
const elapsed = timestamp - startTime;
|
|
99
|
+
let stillAnimating = false;
|
|
100
|
+
|
|
101
|
+
// Update all points
|
|
102
|
+
for (let i = 0; i < this.paths.length; i++) {
|
|
103
|
+
const pathDelay = this.delayPerPath * 1000 * (this.isOpen ? i : (this.paths.length - i - 1));
|
|
104
|
+
|
|
105
|
+
for (let j = 0; j < this.numPoints; j++) {
|
|
106
|
+
const pointDelay = this.pointsDelay[j] * 1000; // Convert to ms
|
|
107
|
+
const totalDelay = pointDelay + pathDelay;
|
|
108
|
+
|
|
109
|
+
if (elapsed > totalDelay) {
|
|
110
|
+
// Calculate animation progress
|
|
111
|
+
const animProgress = Math.min(1, (elapsed - totalDelay) / (1000 * this.lerpSpeed * 5));
|
|
112
|
+
|
|
113
|
+
// Target value
|
|
114
|
+
const targetValue = this.isOpen ? 0 : this.amplitude;
|
|
115
|
+
|
|
116
|
+
// Interpolate towards target
|
|
117
|
+
if (Math.abs(this.allPoints[i][j] - targetValue) > 0.1) {
|
|
118
|
+
// Use Perlin noise to add natural variation
|
|
119
|
+
const noiseValue = this.noise.perlin2(j * 0.1, i * 0.1 + this.time);
|
|
120
|
+
const variation = noiseValue * 2; // Small variation
|
|
121
|
+
|
|
122
|
+
// Use our own lerp function since we can't access the private one
|
|
123
|
+
this.allPoints[i][j] = this.lerp(
|
|
124
|
+
this.allPoints[i][j],
|
|
125
|
+
targetValue + (this.isOpen ? 0 : variation), // Only add variation in closed state
|
|
126
|
+
animProgress
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
stillAnimating = true;
|
|
130
|
+
} else {
|
|
131
|
+
this.allPoints[i][j] = targetValue;
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
stillAnimating = true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Update the display
|
|
140
|
+
this.render();
|
|
141
|
+
|
|
142
|
+
// Continue animation if needed
|
|
143
|
+
if (stillAnimating) {
|
|
144
|
+
this.raf = requestAnimationFrame(animate);
|
|
145
|
+
} else {
|
|
146
|
+
this.isAnimating = false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Update time for Perlin noise
|
|
150
|
+
this.time += this.speed;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Start animation
|
|
154
|
+
this.raf = requestAnimationFrame(animate);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Render SVG paths
|
|
158
|
+
render() {
|
|
159
|
+
for (let i = 0; i < this.paths.length; i++) {
|
|
160
|
+
const path = this.paths[i];
|
|
161
|
+
const points = this.allPoints[i];
|
|
162
|
+
|
|
163
|
+
let d = "";
|
|
164
|
+
d += this.isOpen ? `M 0 0 V ${points[0]} C` : `M 0 ${points[0]} C`;
|
|
165
|
+
|
|
166
|
+
for (let j = 0; j < this.numPoints - 1; j++) {
|
|
167
|
+
let p = (j + 1) / (this.numPoints - 1) * 100;
|
|
168
|
+
let cp = p - (1 / (this.numPoints - 1) * 100) / 2;
|
|
169
|
+
d += ` ${cp} ${points[j]} ${cp} ${points[j+1]} ${p} ${points[j+1]}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
d += this.isOpen ? ` V 100 H 0` : ` V 0 H 0`;
|
|
173
|
+
path.setAttribute("d", d);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Override parent's animate method
|
|
178
|
+
// so it doesn't interfere with our custom animation
|
|
179
|
+
animate = () => {
|
|
180
|
+
// We don't do anything here, as we handle animation in toggle()
|
|
181
|
+
this.raf = requestAnimationFrame(this.animate);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Our own lerp function since we can't access the private one from PerlinDOM
|
|
185
|
+
lerp(a, b, t) {
|
|
186
|
+
return a + t * (b - a);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export default PerlinSVG;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Example 7: Animated Button with Circles
|
|
2
|
+
import PerlinDOM from './PerlinDom.js';
|
|
3
|
+
|
|
4
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
5
|
+
// Get all the circle elements inside the button
|
|
6
|
+
const circleElements = document.querySelectorAll('.animated-button .circle');
|
|
7
|
+
|
|
8
|
+
// Create an array to store the animation instances
|
|
9
|
+
let circleAnimations = [];
|
|
10
|
+
|
|
11
|
+
// Function to initialize animations
|
|
12
|
+
function initCircleAnimations() {
|
|
13
|
+
// Clear any existing animations
|
|
14
|
+
circleAnimations.forEach(anim => anim.destroy());
|
|
15
|
+
circleAnimations = [];
|
|
16
|
+
|
|
17
|
+
// Create new animations for each circle
|
|
18
|
+
circleElements.forEach((el, index) => {
|
|
19
|
+
const animation = new PerlinDOM({
|
|
20
|
+
element: el,
|
|
21
|
+
x: { min: -15, max: 15 },
|
|
22
|
+
y: { min: -15, max: 15 },
|
|
23
|
+
speed: 0.007,
|
|
24
|
+
seed: index * 1000,
|
|
25
|
+
lerpSpeed: 0.2 * index
|
|
26
|
+
});
|
|
27
|
+
circleAnimations.push(animation);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Add event listeners to the button
|
|
32
|
+
document.querySelector('.animated-button')?.addEventListener('mouseenter', () => {
|
|
33
|
+
initCircleAnimations();
|
|
34
|
+
circleAnimations.forEach(anim => anim.play());
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
document.querySelector('.animated-button')?.addEventListener('mouseleave', () => {
|
|
38
|
+
circleAnimations.forEach(anim => anim.pause());
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Examples 1-4: Basic PerlinDOM Examples
|
|
2
|
+
import PerlinDOM from './PerlinDom.js';
|
|
3
|
+
|
|
4
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
5
|
+
// Example 1: Basic Animation
|
|
6
|
+
const basicAnimation = new PerlinDOM({
|
|
7
|
+
element: document.querySelector('.basic-element'),
|
|
8
|
+
x: { min: -50, max: 50 },
|
|
9
|
+
y: { min: -50, max: 50 },
|
|
10
|
+
speed: 0.01,
|
|
11
|
+
seed: 123
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Example 2: Horizontal Only
|
|
15
|
+
const horizontalAnimation = new PerlinDOM({
|
|
16
|
+
element: document.querySelector('.horizontal-element'),
|
|
17
|
+
x: { min: -100, max: 100 },
|
|
18
|
+
y: null, // No vertical movement
|
|
19
|
+
speed: 0.02
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Example 3: Multiple Elements
|
|
23
|
+
document.querySelectorAll('.particle').forEach((el, index) => {
|
|
24
|
+
new PerlinDOM({
|
|
25
|
+
element: el,
|
|
26
|
+
x: { min: -100, max: 100 + index * 20 }, // Slightly wider range for each element
|
|
27
|
+
y: { min: -100, max: 120}, // Random vertical range
|
|
28
|
+
speed: index * 0.001, // Slightly different speed for each element
|
|
29
|
+
seed: index * 16700 // Different seed for each element
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Example 4: Background Effect
|
|
34
|
+
document.querySelectorAll('.bg-element').forEach(el => {
|
|
35
|
+
new PerlinDOM({
|
|
36
|
+
element: el,
|
|
37
|
+
x: { min: -110, max: 110 },
|
|
38
|
+
y: { min: -110, max: 110 },
|
|
39
|
+
speed: 0.002, // Very slow movement
|
|
40
|
+
seed: Math.random() * 2000
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
});
|
package/src/js/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import PerlinDOM from "./PerlinDom";
|
|
2
|
+
console.log('PerlinDOM loaded');
|
|
3
|
+
|
|
4
|
+
const elements = document.querySelectorAll('.box');
|
|
5
|
+
elements.forEach(el =>
|
|
6
|
+
new PerlinDOM({
|
|
7
|
+
element: el,
|
|
8
|
+
x: { min: -120, max: 120 },
|
|
9
|
+
y: { min: 0, max: 120 },
|
|
10
|
+
speed: 0.001,
|
|
11
|
+
seed: 1
|
|
12
|
+
})
|
|
13
|
+
);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Example 6: Interactive Controls
|
|
2
|
+
import PerlinDOM from './PerlinDom.js';
|
|
3
|
+
|
|
4
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
5
|
+
let interactiveAnimation;
|
|
6
|
+
|
|
7
|
+
function initInteractiveAnimation() {
|
|
8
|
+
// Destroy previous instance if it exists
|
|
9
|
+
if (interactiveAnimation) {
|
|
10
|
+
interactiveAnimation.destroy();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Get current values
|
|
14
|
+
const xRange = parseInt(document.getElementById('x-range').value);
|
|
15
|
+
const yRange = parseInt(document.getElementById('y-range').value);
|
|
16
|
+
const speed = parseFloat(document.getElementById('speed').value);
|
|
17
|
+
const seed = parseInt(document.getElementById('seed').value);
|
|
18
|
+
|
|
19
|
+
// Create new instance
|
|
20
|
+
interactiveAnimation = new PerlinDOM({
|
|
21
|
+
element: document.querySelector('.interactive-element'),
|
|
22
|
+
x: { min: -xRange, max: xRange },
|
|
23
|
+
y: { min: -yRange, max: yRange },
|
|
24
|
+
speed: speed,
|
|
25
|
+
seed: seed
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Update displays
|
|
29
|
+
document.querySelector('#x-range + .value-display').textContent = `-${xRange} to ${xRange}`;
|
|
30
|
+
document.querySelector('#y-range + .value-display').textContent = `-${yRange} to ${yRange}`;
|
|
31
|
+
document.querySelector('#speed + .value-display').textContent = speed.toFixed(3);
|
|
32
|
+
document.querySelector('#seed + .value-display').textContent = seed;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Initialize
|
|
36
|
+
initInteractiveAnimation();
|
|
37
|
+
|
|
38
|
+
// Add event listeners
|
|
39
|
+
document.getElementById('x-range')?.addEventListener('input', initInteractiveAnimation);
|
|
40
|
+
document.getElementById('y-range')?.addEventListener('input', initInteractiveAnimation);
|
|
41
|
+
document.getElementById('speed')?.addEventListener('input', initInteractiveAnimation);
|
|
42
|
+
document.getElementById('seed')?.addEventListener('input', initInteractiveAnimation);
|
|
43
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Example 5: Pause and Play with Mouse Events
|
|
2
|
+
import PerlinDOM from './PerlinDom.js';
|
|
3
|
+
|
|
4
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
5
|
+
// Create the animation
|
|
6
|
+
const pausePlayAnimation = new PerlinDOM({
|
|
7
|
+
element: document.querySelector('.pause-play-element'),
|
|
8
|
+
x: { min: -80, max: 80 },
|
|
9
|
+
y: { min: -180, max: 80 },
|
|
10
|
+
speed: 0.01,
|
|
11
|
+
seed: 42,
|
|
12
|
+
lerpSpeed: 0.05 // Controls how smooth the transition is
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Pause on mouse leave
|
|
16
|
+
document.querySelector('.pause-play-container')?.addEventListener('mouseleave', () => {
|
|
17
|
+
pausePlayAnimation.pause();
|
|
18
|
+
document.querySelector('.status-indicator').textContent = 'Pausing...';
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Play on mouse enter
|
|
22
|
+
document.querySelector('.pause-play-container')?.addEventListener('mouseenter', () => {
|
|
23
|
+
pausePlayAnimation.play();
|
|
24
|
+
document.querySelector('.status-indicator').textContent = 'Playing...';
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Update lerpSpeed when slider changes
|
|
28
|
+
document.getElementById('lerp-speed')?.addEventListener('input', (e) => {
|
|
29
|
+
const newSpeed = parseFloat(e.target.value);
|
|
30
|
+
pausePlayAnimation.lerpSpeed = newSpeed;
|
|
31
|
+
document.querySelector('.lerp-value').textContent = newSpeed.toFixed(2);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Example 8: ClipPath Triangle with Animated Vertex
|
|
2
|
+
import PerlinDOM from './PerlinDom.js';
|
|
3
|
+
|
|
4
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
5
|
+
// Get the polygon element
|
|
6
|
+
const trianglePath = document.getElementById('trianglePath');
|
|
7
|
+
|
|
8
|
+
if (!trianglePath) return;
|
|
9
|
+
|
|
10
|
+
// Store the original points
|
|
11
|
+
const originalPoints = {
|
|
12
|
+
x1: 150, y1: 50, // Top vertex
|
|
13
|
+
x2: 250, y2: 250, // Bottom right
|
|
14
|
+
x3: 50, y3: 250 // Bottom left
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Create a custom animation function
|
|
18
|
+
function animateTriangle() {
|
|
19
|
+
// Create a PerlinDOM instance for the top vertex
|
|
20
|
+
const topVertexAnimation = new PerlinDOM({
|
|
21
|
+
element: null, // We're not moving a DOM element directly
|
|
22
|
+
x: { min: -30, max: 30 },
|
|
23
|
+
y: { min: -20, max: 20 },
|
|
24
|
+
speed: 0.005,
|
|
25
|
+
seed: 42
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Animation function
|
|
29
|
+
function updateTriangle() {
|
|
30
|
+
// Get the current noise values
|
|
31
|
+
const offsetX = topVertexAnimation.lastX;
|
|
32
|
+
const offsetY = topVertexAnimation.lastY;
|
|
33
|
+
|
|
34
|
+
// Update the polygon points
|
|
35
|
+
const newPoints = `${originalPoints.x1 + offsetX},${originalPoints.y1 + offsetY} ${originalPoints.x2},${originalPoints.y2} ${originalPoints.x3},${originalPoints.y3}`;
|
|
36
|
+
trianglePath.setAttribute('points', newPoints);
|
|
37
|
+
|
|
38
|
+
// Continue animation
|
|
39
|
+
requestAnimationFrame(updateTriangle);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Start the animation
|
|
43
|
+
updateTriangle();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Initialize the animation
|
|
47
|
+
animateTriangle();
|
|
48
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.spacer{
|
|
2
|
+
padding:120px;
|
|
3
|
+
border:1px solid red;
|
|
4
|
+
position:relative;
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
.box{
|
|
10
|
+
width:120px;
|
|
11
|
+
height:120px;
|
|
12
|
+
border-radius: 50%;
|
|
13
|
+
background-color: blue;
|
|
14
|
+
position:absolute;
|
|
15
|
+
left:0;
|
|
16
|
+
top:-20%;
|
|
17
|
+
filter: blur(40px) drop-shadow(0 0 20px rgba(0, 0, 255, 0.5));
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
}
|
package/vite.config.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
|
|
4
|
+
const root = resolve(__dirname, 'src');
|
|
5
|
+
const outDir = resolve(__dirname, 'dist');
|
|
6
|
+
const entryPath = resolve(__dirname, 'src/js/PerlinDOM.js');
|
|
7
|
+
|
|
8
|
+
export default defineConfig({
|
|
9
|
+
root,
|
|
10
|
+
publicDir: 'public',
|
|
11
|
+
plugins: [],
|
|
12
|
+
build: {
|
|
13
|
+
lib: {
|
|
14
|
+
entry: entryPath,
|
|
15
|
+
name: 'PerlinDOM', // Library name as : Counter | etc
|
|
16
|
+
formats: ['es', 'umd'], // Output formats as : es | umd, es is for modern browsers, umd is for older browsers
|
|
17
|
+
fileName: (format,name) => `PerlinDOM.${format}.js`, // File naming as : reveal-it | collapsify | etc
|
|
18
|
+
},
|
|
19
|
+
outDir,
|
|
20
|
+
emptyOutDir: true,
|
|
21
|
+
// rollupOptions: {
|
|
22
|
+
|
|
23
|
+
// output: {
|
|
24
|
+
// name: 'Validate',
|
|
25
|
+
// },
|
|
26
|
+
// },
|
|
27
|
+
},
|
|
28
|
+
resolve: {
|
|
29
|
+
alias: {
|
|
30
|
+
'@': resolve(__dirname, './src'),
|
|
31
|
+
'@js': resolve(__dirname, './src/js'),
|
|
32
|
+
'@scss': resolve(__dirname, './src/scss'),
|
|
33
|
+
'@assets': resolve(__dirname, './src/assets'),
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|