@andresclua/perlindom 0.0.6 → 0.1.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.
@@ -1,318 +0,0 @@
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
- // Helper to check if a value is a percentage string
23
- static #isPercentage(value) {
24
- return typeof value === 'string' && value.endsWith('%');
25
- }
26
-
27
- // Helper to convert percentage to pixels
28
- static #percentToPixels(percentage, dimension) {
29
- if (!PerlinDOM.#isPercentage(percentage)) return percentage;
30
- const value = parseFloat(percentage);
31
- return (value / 100) * dimension;
32
- }
33
-
34
- constructor({
35
- element,
36
- x = null,
37
- y = null,
38
- speed = 0.01,
39
- seed = 0,
40
- lerpSpeed = 0.1,
41
- preserveBaseTranslation = false, // <-- si querés mantener la traslación base
42
- }) {
43
- this.el = element;
44
- this.xRange = x;
45
- this.yRange = y;
46
- this.speed = speed;
47
- this.time = seed;
48
- this.isPlaying = true;
49
- this.lerpFactor = 1; // 1 = full speed, 0 = paused
50
- this.lerpSpeed = lerpSpeed; // How fast to transition between play/pause states
51
- this.targetLerpFactor = 1; // Target state (1 = play, 0 = pause)
52
- this.lastX = 0;
53
- this.lastY = 0;
54
- this.preserveBaseTranslation = preserveBaseTranslation;
55
-
56
- // Check if we have percentage values (paréntesis para evitar precedencia ambigua)
57
- this.hasPercentages = !!(
58
- (x && (PerlinDOM.#isPercentage(x.min) || PerlinDOM.#isPercentage(x.max))) ||
59
- (y && (PerlinDOM.#isPercentage(y.min) || PerlinDOM.#isPercentage(y.max)))
60
- );
61
-
62
- // Solo inicializar estas propiedades si el elemento no es null
63
- if (element) {
64
- // Ensure element has positioning for transform to work properly
65
- const computedStyle = window.getComputedStyle(element);
66
- if (computedStyle.position === 'static') {
67
- element.style.position = 'relative';
68
- }
69
-
70
- // Guardamos el transform inline original para restaurar en destroy()
71
- this.originalInlineTransform = element.style.transform || '';
72
-
73
- // Capturamos el transform base (quitando translate en 2D por defecto)
74
- this.baseTransform = this.#extractBaseTransform(
75
- computedStyle.transform,
76
- this.preserveBaseTranslation
77
- );
78
-
79
- // Si usamos porcentajes, escuchamos resize
80
- if (this.hasPercentages) {
81
- this.handleResize = this.handleResize.bind(this);
82
- window.addEventListener('resize', this.handleResize);
83
- }
84
- } else {
85
- this.originalInlineTransform = '';
86
- this.baseTransform = '';
87
- }
88
-
89
- this.animate = this.animate.bind(this);
90
- this.init(seed);
91
- }
92
-
93
- init(seed) {
94
- this.noise = this.#createPerlin(PerlinDOM.#generatePermutation(seed));
95
- this.isPlaying = true;
96
- this.lerpFactor = 1;
97
- this.targetLerpFactor = 1;
98
- this.raf = requestAnimationFrame(this.animate);
99
- }
100
-
101
- // Permite refrescar el transform base si cambian clases/estilos en runtime
102
- refreshBaseTransform(preserveTranslation = this.preserveBaseTranslation) {
103
- if (!this.el) return;
104
- const cs = window.getComputedStyle(this.el);
105
- this.baseTransform = this.#extractBaseTransform(cs.transform, preserveTranslation);
106
- }
107
-
108
- // Handle window resize for percentage-based values
109
- handleResize() {
110
- // No necesitamos recalcular nada fuera de animate; se leen dimensiones en cada frame
111
- // Este método queda por si querés extender la lógica (debounce, etc.).
112
- }
113
-
114
- pause() {
115
- if (this.isPlaying) {
116
- this.targetLerpFactor = 0; // Target paused state
117
- this.isPlaying = false;
118
- }
119
- }
120
-
121
- play() {
122
- if (!this.isPlaying) {
123
- this.targetLerpFactor = 1; // Target playing state
124
- this.isPlaying = true;
125
-
126
- // Solo arrancar un nuevo frame si estaba completamente pausado
127
- if (this.lerpFactor === 0) {
128
- this.raf = requestAnimationFrame(this.animate);
129
- }
130
- }
131
- }
132
-
133
- #createPerlin(p) {
134
- const fade = t => t * t * t * (t * (t * 6 - 15) + 10);
135
- const lerp = (t, a, b) => a + t * (b - a);
136
-
137
- // Enhanced gradient function for 2D
138
- const grad2D = (hash, x, y) => {
139
- const h = hash & 15;
140
- const u = h < 8 ? x : y;
141
- const v = h < 4 ? y : (h === 12 || h === 14 ? x : 0);
142
- return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
143
- };
144
-
145
- // 1D gradient (kept for backward compatibility)
146
- const grad1D = (hash, x) => (hash & 1 ? -x : x);
147
-
148
- return {
149
- // Original 1D Perlin noise function
150
- perlin1(x) {
151
- const X = Math.floor(x) & 255;
152
- x -= Math.floor(x);
153
- const u = fade(x);
154
- return lerp(u, grad1D(p[X], x), grad1D(p[X + 1], x - 1));
155
- },
156
-
157
- // New 2D Perlin noise function
158
- perlin2(x, y) {
159
- // Integer parts
160
- const X = Math.floor(x) & 255;
161
- const Y = Math.floor(y) & 255;
162
-
163
- // Fractional parts
164
- x -= Math.floor(x);
165
- y -= Math.floor(y);
166
-
167
- // Fade curves
168
- const u = fade(x);
169
- const v = fade(y);
170
-
171
- // Hash coordinates of the 4 corners
172
- const A = p[X] + Y;
173
- const B = p[X + 1] + Y;
174
- const AA = p[A];
175
- const AB = p[A + 1];
176
- const BA = p[B];
177
- const BB = p[B + 1];
178
-
179
- // Blend the four corner gradients
180
- return lerp(
181
- v,
182
- lerp(u, grad2D(p[AA], x, y), grad2D(p[BA], x - 1, y)),
183
- lerp(u, grad2D(p[AB], x, y - 1), grad2D(p[BB], x - 1, y - 1))
184
- );
185
- }
186
- };
187
- }
188
-
189
- #lerp(a, b, t) {
190
- return a + t * (b - a);
191
- }
192
-
193
- animate = () => {
194
- // Update lerp factor towards target
195
- if (this.lerpFactor !== this.targetLerpFactor) {
196
- this.lerpFactor = this.#lerp(this.lerpFactor, this.targetLerpFactor, this.lerpSpeed);
197
-
198
- // Snap to target if very close
199
- if (Math.abs(this.lerpFactor - this.targetLerpFactor) < 0.001) {
200
- this.lerpFactor = this.targetLerpFactor;
201
- }
202
- }
203
-
204
- // Only update time if not completely paused
205
- if (this.lerpFactor > 0) {
206
- // Scale time increment by lerp factor for smooth speed transition
207
- this.time += this.speed * this.lerpFactor;
208
-
209
- // 2D Perlin noise for more natural movement
210
- const noiseX = this.noise.perlin2(this.time, 0);
211
- const noiseY = this.noise.perlin2(this.time, 100); // Different slice
212
-
213
- // Only process if we have an element
214
- if (this.el) {
215
- let minX, maxX, minY, maxY;
216
-
217
- // Get parent dimensions for percentage calculations if needed
218
- if (this.hasPercentages) {
219
- const parent = this.el.parentElement || document.body;
220
- const parentWidth = parent.clientWidth;
221
- const parentHeight = parent.clientHeight;
222
-
223
- // Convert percentage values to pixels if needed
224
- if (this.xRange) {
225
- minX = PerlinDOM.#isPercentage(this.xRange.min)
226
- ? PerlinDOM.#percentToPixels(this.xRange.min, parentWidth)
227
- : this.xRange.min;
228
-
229
- maxX = PerlinDOM.#isPercentage(this.xRange.max)
230
- ? PerlinDOM.#percentToPixels(this.xRange.max, parentWidth)
231
- : this.xRange.max;
232
- }
233
-
234
- if (this.yRange) {
235
- minY = PerlinDOM.#isPercentage(this.yRange.min)
236
- ? PerlinDOM.#percentToPixels(this.yRange.min, parentHeight)
237
- : this.yRange.min;
238
-
239
- maxY = PerlinDOM.#isPercentage(this.yRange.max)
240
- ? PerlinDOM.#percentToPixels(this.yRange.max, parentHeight)
241
- : this.yRange.max;
242
- }
243
- } else {
244
- // Use original values if not using percentages
245
- minX = this.xRange?.min;
246
- maxX = this.xRange?.max;
247
- minY = this.yRange?.min;
248
- maxY = this.yRange?.max;
249
- }
250
-
251
- // Calculate pixel values
252
- const valX = this.xRange ? this.#map(noiseX, -1, 1, minX, maxX) : 0;
253
- const valY = this.yRange ? this.#map(noiseY, -1, 1, minY, maxY) : 0;
254
-
255
- // Store current values for potential external use
256
- this.lastX = valX;
257
- this.lastY = valY;
258
-
259
- // Compose translate3d with the base transform (scale/rotate/skew)
260
- // Orden: primero translate (nuestro movimiento), luego el transform base del diseño
261
- this.el.style.transform = `translate3d(${valX}px, ${valY}px, 0) ${this.baseTransform}`.trim();
262
- } else {
263
- // If no element, just calculate and store the values
264
- const valX = this.xRange ? this.#map(noiseX, -1, 1, this.xRange.min, this.xRange.max) : 0;
265
- const valY = this.yRange ? this.#map(noiseY, -1, 1, this.yRange.min, this.yRange.max) : 0;
266
-
267
- this.lastX = valX;
268
- this.lastY = valY;
269
- }
270
- }
271
-
272
- // Continue animation if not completely paused or still transitioning
273
- if (this.lerpFactor > 0 || this.targetLerpFactor > 0) {
274
- this.raf = requestAnimationFrame(this.animate);
275
- }
276
- };
277
-
278
- #map(n, inMin, inMax, outMin, outMax) {
279
- return ((n - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
280
- }
281
-
282
- // Quita o preserva la traslación del transform base en 2D según flag
283
- #extractBaseTransform(transformValue, preserveTranslation = false) {
284
- if (!transformValue || transformValue === 'none') return '';
285
-
286
- try {
287
- const m = new DOMMatrixReadOnly(transformValue);
288
- if (m.is2D) {
289
- // matrix(a, b, c, d, e, f) -> rearmamos con e,f = 0 si NO preservamos traslación
290
- const e = preserveTranslation ? m.e : 0;
291
- const f = preserveTranslation ? m.f : 0;
292
- return `matrix(${m.a}, ${m.b}, ${m.c}, ${m.d}, ${e}, ${f})`;
293
- } else {
294
- // 3D: lo dejamos tal cual (remover traslación en 3D requiere descomposición)
295
- return transformValue;
296
- }
297
- } catch {
298
- // Fallback si DOMMatrix no está disponible o hay un valor raro
299
- return transformValue;
300
- }
301
- }
302
-
303
- destroy() {
304
- cancelAnimationFrame(this.raf);
305
-
306
- // Restaurar transform inline original
307
- if (this.el) {
308
- this.el.style.transform = this.originalInlineTransform || '';
309
- }
310
-
311
- // Remove resize listener if it was added
312
- if (this.hasPercentages) {
313
- window.removeEventListener('resize', this.handleResize);
314
- }
315
- }
316
- }
317
-
318
- export default PerlinDOM;
@@ -1,40 +0,0 @@
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
- });
@@ -1,43 +0,0 @@
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 DELETED
@@ -1,13 +0,0 @@
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
- );
@@ -1,43 +0,0 @@
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
- });
@@ -1,33 +0,0 @@
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
- });
@@ -1,48 +0,0 @@
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
- });
@@ -1,20 +0,0 @@
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 DELETED
@@ -1,36 +0,0 @@
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
- });