@andresclua/perlindom 0.0.5 → 0.1.0

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,307 +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
- }) {
42
- this.el = element;
43
- this.xRange = x;
44
- this.yRange = y;
45
- this.speed = speed;
46
- this.time = seed;
47
- this.isPlaying = true;
48
- this.lerpFactor = 1; // 1 = full speed, 0 = paused
49
- this.lerpSpeed = lerpSpeed; // How fast to transition between play/pause states
50
- this.targetLerpFactor = 1; // Target state (1 = play, 0 = pause)
51
- this.lastX = 0;
52
- this.lastY = 0;
53
-
54
- // Check if we have percentage values
55
- this.hasPercentages = false;
56
- if (x && (PerlinDOM.#isPercentage(x.min) || PerlinDOM.#isPercentage(x.max)) ||
57
- y && (PerlinDOM.#isPercentage(y.min) || PerlinDOM.#isPercentage(y.max))) {
58
- this.hasPercentages = true;
59
- }
60
-
61
- // Solo inicializar estas propiedades si el elemento no es null
62
- if (element) {
63
- // Ensure element has positioning for transform to work properly
64
- const computedStyle = window.getComputedStyle(element);
65
- if (computedStyle.position === 'static') {
66
- element.style.position = 'relative';
67
- }
68
-
69
- // Initialize transform if it doesn't exist
70
- const currentTransform = computedStyle.transform;
71
- if (!currentTransform || currentTransform === 'none') {
72
- element.style.transform = 'translate3d(0px, 0px, 0px)';
73
- }
74
-
75
- // If using percentages, add resize listener
76
- if (this.hasPercentages) {
77
- this.handleResize = this.handleResize.bind(this);
78
- window.addEventListener('resize', this.handleResize);
79
- }
80
-
81
- }
82
-
83
- this.animate = this.animate.bind(this);
84
-
85
- this.init(seed);
86
- }
87
-
88
- init(seed) {
89
- this.noise = this.#createPerlin(PerlinDOM.#generatePermutation(seed));
90
- this.isPlaying = true;
91
- this.lerpFactor = 1;
92
- this.targetLerpFactor = 1;
93
- this.raf = requestAnimationFrame(this.animate);
94
- }
95
-
96
- // Handle window resize for percentage-based values
97
- handleResize() {
98
- // Only recalculate if we're using percentages
99
- if (this.hasPercentages && this.el) {
100
- // Force a recalculation on next animation frame
101
- this.needsRecalculation = true;
102
- }
103
- }
104
-
105
- pause() {
106
- if (this.isPlaying) {
107
- this.targetLerpFactor = 0; // Target paused state
108
- this.isPlaying = false;
109
- }
110
- }
111
-
112
- play() {
113
- if (!this.isPlaying) {
114
- this.targetLerpFactor = 1; // Target playing state
115
- this.isPlaying = true;
116
-
117
- // Only start a new animation frame if we're completely paused
118
- if (this.lerpFactor === 0) {
119
- this.raf = requestAnimationFrame(this.animate);
120
- }
121
-
122
- }
123
- }
124
-
125
- #createPerlin(p) {
126
- const fade = t => t * t * t * (t * (t * 6 - 15) + 10);
127
- const lerp = (t, a, b) => a + t * (b - a);
128
-
129
- // Enhanced gradient function for 2D
130
- const grad2D = (hash, x, y) => {
131
- const h = hash & 15;
132
- const u = h < 8 ? x : y;
133
- const v = h < 4 ? y : (h === 12 || h === 14 ? x : 0);
134
- return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
135
- };
136
-
137
- // 1D gradient (kept for backward compatibility)
138
- const grad1D = (hash, x) => (hash & 1 ? -x : x);
139
-
140
- return {
141
- // Original 1D Perlin noise function
142
- perlin1(x) {
143
- const X = Math.floor(x) & 255;
144
- x -= Math.floor(x);
145
- const u = fade(x);
146
- return lerp(u, grad1D(p[X], x), grad1D(p[X + 1], x - 1));
147
- },
148
-
149
- // New 2D Perlin noise function
150
- perlin2(x, y) {
151
- // Integer parts of coordinates
152
- const X = Math.floor(x) & 255;
153
- const Y = Math.floor(y) & 255;
154
-
155
- // Fractional parts of coordinates
156
- x -= Math.floor(x);
157
- y -= Math.floor(y);
158
-
159
- // Fade curves for x and y
160
- const u = fade(x);
161
- const v = fade(y);
162
-
163
- // Hash coordinates of the 4 square corners
164
- const A = p[X] + Y;
165
- const B = p[X + 1] + Y;
166
- const AA = p[A];
167
- const AB = p[A + 1];
168
- const BA = p[B];
169
- const BB = p[B + 1];
170
-
171
- // Blend the four corner gradients
172
- return lerp(
173
- v,
174
- lerp(
175
- u,
176
- grad2D(p[AA], x, y),
177
- grad2D(p[BA], x - 1, y)
178
- ),
179
- lerp(
180
- u,
181
- grad2D(p[AB], x, y - 1),
182
- grad2D(p[BB], x - 1, y - 1)
183
- )
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(
197
- this.lerpFactor,
198
- this.targetLerpFactor,
199
- this.lerpSpeed
200
- );
201
-
202
- // Snap to target if very close
203
- if (Math.abs(this.lerpFactor - this.targetLerpFactor) < 0.001) {
204
- this.lerpFactor = this.targetLerpFactor;
205
- }
206
- }
207
-
208
- // Only update time if not completely paused
209
- if (this.lerpFactor > 0) {
210
- // Scale time increment by lerp factor for smooth speed transition
211
- this.time += this.speed * this.lerpFactor;
212
-
213
- // Use 2D Perlin noise for more natural movement
214
- // We use time for one dimension and a fixed offset for the other
215
- // This creates a path through the 2D noise field
216
- const noiseX = this.noise.perlin2(this.time, 0);
217
- const noiseY = this.noise.perlin2(this.time, 100); // Different slice of the noise field
218
-
219
- // Only process if we have an element
220
- if (this.el) {
221
- let minX, maxX, minY, maxY;
222
-
223
- // Get parent dimensions for percentage calculations if needed
224
- if (this.hasPercentages) {
225
- const parent = this.el.parentElement || document.body;
226
- const parentWidth = parent.clientWidth;
227
- const parentHeight = parent.clientHeight;
228
-
229
- // Convert percentage values to pixels if needed
230
- if (this.xRange) {
231
- minX = PerlinDOM.#isPercentage(this.xRange.min)
232
- ? PerlinDOM.#percentToPixels(this.xRange.min, parentWidth)
233
- : this.xRange.min;
234
-
235
- maxX = PerlinDOM.#isPercentage(this.xRange.max)
236
- ? PerlinDOM.#percentToPixels(this.xRange.max, parentWidth)
237
- : this.xRange.max;
238
- }
239
-
240
- if (this.yRange) {
241
- minY = PerlinDOM.#isPercentage(this.yRange.min)
242
- ? PerlinDOM.#percentToPixels(this.yRange.min, parentHeight)
243
- : this.yRange.min;
244
-
245
- maxY = PerlinDOM.#isPercentage(this.yRange.max)
246
- ? PerlinDOM.#percentToPixels(this.yRange.max, parentHeight)
247
- : this.yRange.max;
248
- }
249
- } else {
250
- // Use the original values if not using percentages
251
- minX = this.xRange?.min;
252
- maxX = this.xRange?.max;
253
- minY = this.yRange?.min;
254
- maxY = this.yRange?.max;
255
- }
256
-
257
- // Calculate the actual pixel values
258
- const valX = this.xRange
259
- ? this.#map(noiseX, -1, 1, minX, maxX)
260
- : 0;
261
-
262
- const valY = this.yRange
263
- ? this.#map(noiseY, -1, 1, minY, maxY)
264
- : 0;
265
-
266
- // Store current values for interpolation
267
- this.lastX = valX;
268
- this.lastY = valY;
269
-
270
- // Update position using transform for better performance
271
- this.el.style.transform = `translate3d(${valX}px, ${valY}px, 0)`;
272
- } else {
273
- // If no element, just calculate and store the values
274
- const valX = this.xRange
275
- ? this.#map(noiseX, -1, 1, this.xRange.min, this.xRange.max)
276
- : 0;
277
-
278
- const valY = this.yRange
279
- ? this.#map(noiseY, -1, 1, this.yRange.min, this.yRange.max)
280
- : 0;
281
-
282
- // Store current values for interpolation
283
- this.lastX = valX;
284
- this.lastY = valY;
285
- }
286
- }
287
-
288
- // Continue animation if not completely paused or still transitioning
289
- if (this.lerpFactor > 0 || this.targetLerpFactor > 0) {
290
- this.raf = requestAnimationFrame(this.animate);
291
- }
292
- };
293
-
294
- #map(n, inMin, inMax, outMin, outMax) {
295
- return ((n - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
296
- }
297
-
298
- destroy() {
299
- cancelAnimationFrame(this.raf);
300
-
301
- // Remove resize listener if it was added
302
- if (this.hasPercentages) {
303
- window.removeEventListener('resize', this.handleResize);
304
- }
305
- }
306
- }
307
- 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
- });