@andresclua/perlindom 0.0.1 → 0.0.2
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 +76 -53
- package/dist/PerlinDOM.umd.js +1 -1
- package/docs/examples.html +6 -0
- package/package.json +1 -1
- package/readme.md +51 -3
- package/src/index.html +136 -0
- package/src/js/PerlinDom.js +103 -14
- package/src/examples.html +0 -515
- package/src/js/PerlinSVG.js +0 -190
package/dist/PerlinDOM.es.js
CHANGED
|
@@ -1,35 +1,53 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
throw TypeError(
|
|
1
|
+
var $ = Object.defineProperty;
|
|
2
|
+
var q = (c) => {
|
|
3
|
+
throw TypeError(c);
|
|
4
4
|
};
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
const
|
|
5
|
+
var j = (c, t, i) => t in c ? $(c, t, { enumerable: !0, configurable: !0, writable: !0, value: i }) : c[t] = i;
|
|
6
|
+
var D = (c, t, i) => j(c, typeof t != "symbol" ? t + "" : t, i), O = (c, t, i) => t.has(c) || q("Cannot " + i);
|
|
7
|
+
var A = (c, t, i) => t.has(c) ? q("Cannot add the same private member more than once") : t instanceof WeakSet ? t.add(c) : t.set(c, i);
|
|
8
|
+
var a = (c, t, i) => (O(c, t, "access private method"), i);
|
|
9
|
+
var n, W, H, u, X, p, S, T, L;
|
|
10
|
+
const o = class o {
|
|
11
11
|
constructor({
|
|
12
12
|
element: t,
|
|
13
13
|
x: i = null,
|
|
14
|
-
y:
|
|
15
|
-
speed:
|
|
16
|
-
seed:
|
|
14
|
+
y: h = null,
|
|
15
|
+
speed: r = 0.01,
|
|
16
|
+
seed: l = 0,
|
|
17
17
|
lerpSpeed: s = 0.1
|
|
18
18
|
}) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
A(this, p);
|
|
20
|
+
D(this, "animate", () => {
|
|
21
|
+
var t, i, h, r, l, s, e, g, m, f, R, y;
|
|
22
|
+
if (this.lerpFactor !== this.targetLerpFactor && (this.lerpFactor = a(this, p, T).call(this, this.lerpFactor, this.targetLerpFactor, this.lerpSpeed), Math.abs(this.lerpFactor - this.targetLerpFactor) < 1e-3 && (this.lerpFactor = this.targetLerpFactor)), this.lerpFactor > 0) {
|
|
22
23
|
this.time += this.speed * this.lerpFactor;
|
|
23
|
-
const
|
|
24
|
-
|
|
24
|
+
const P = this.noise.perlin2(this.time, 0), Y = this.noise.perlin2(this.time, 100);
|
|
25
|
+
if (this.el) {
|
|
26
|
+
let d, F, x, v;
|
|
27
|
+
if (this.hasPercentages) {
|
|
28
|
+
const b = this.el.parentElement || document.body, B = b.clientWidth, w = b.clientHeight;
|
|
29
|
+
this.xRange && (d = a(t = o, n, u).call(t, this.xRange.min) ? a(i = o, n, X).call(i, this.xRange.min, B) : this.xRange.min, F = a(h = o, n, u).call(h, this.xRange.max) ? a(r = o, n, X).call(r, this.xRange.max, B) : this.xRange.max), this.yRange && (x = a(l = o, n, u).call(l, this.yRange.min) ? a(s = o, n, X).call(s, this.yRange.min, w) : this.yRange.min, v = a(e = o, n, u).call(e, this.yRange.max) ? a(g = o, n, X).call(g, this.yRange.max, w) : this.yRange.max);
|
|
30
|
+
} else
|
|
31
|
+
d = (m = this.xRange) == null ? void 0 : m.min, F = (f = this.xRange) == null ? void 0 : f.max, x = (R = this.yRange) == null ? void 0 : R.min, v = (y = this.yRange) == null ? void 0 : y.max;
|
|
32
|
+
const M = this.xRange ? a(this, p, L).call(this, P, -1, 1, d, F) : 0, z = this.yRange ? a(this, p, L).call(this, Y, -1, 1, x, v) : 0;
|
|
33
|
+
this.lastX = M, this.lastY = z, this.el.style.left = `${this.initialX + M}px`, this.el.style.top = `${this.initialY + z}px`;
|
|
34
|
+
} else {
|
|
35
|
+
const d = this.xRange ? a(this, p, L).call(this, P, -1, 1, this.xRange.min, this.xRange.max) : 0, F = this.yRange ? a(this, p, L).call(this, Y, -1, 1, this.yRange.min, this.yRange.max) : 0;
|
|
36
|
+
this.lastX = d, this.lastY = F;
|
|
37
|
+
}
|
|
25
38
|
}
|
|
26
39
|
(this.lerpFactor > 0 || this.targetLerpFactor > 0) && (this.raf = requestAnimationFrame(this.animate));
|
|
27
40
|
});
|
|
28
|
-
|
|
41
|
+
var e, g, m, f;
|
|
42
|
+
this.el = t, this.xRange = i, this.yRange = h, this.speed = r, this.time = l, this.isPlaying = !0, this.lerpFactor = 1, this.lerpSpeed = s, this.targetLerpFactor = 1, this.lastX = 0, this.lastY = 0, this.hasPercentages = !1, (i && (a(e = o, n, u).call(e, i.min) || a(g = o, n, u).call(g, i.max)) || h && (a(m = o, n, u).call(m, h.min) || a(f = o, n, u).call(f, h.max))) && (this.hasPercentages = !0), t ? (this.initialX = t.offsetLeft, this.initialY = t.offsetTop, this.hasPercentages && (this.handleResize = this.handleResize.bind(this), window.addEventListener("resize", this.handleResize))) : (this.initialX = 0, this.initialY = 0), this.animate = this.animate.bind(this), this.init(l);
|
|
29
43
|
}
|
|
30
44
|
init(t) {
|
|
31
45
|
var i;
|
|
32
|
-
this.noise =
|
|
46
|
+
this.noise = a(this, p, S).call(this, a(i = o, n, H).call(i, t)), this.isPlaying = !0, this.lerpFactor = 1, this.targetLerpFactor = 1, this.raf = requestAnimationFrame(this.animate), console.log("PerlinDOM initialized");
|
|
47
|
+
}
|
|
48
|
+
// Handle window resize for percentage-based values
|
|
49
|
+
handleResize() {
|
|
50
|
+
this.hasPercentages && this.el && (this.needsRecalculation = !0);
|
|
33
51
|
}
|
|
34
52
|
pause() {
|
|
35
53
|
this.isPlaying && (this.targetLerpFactor = 0, this.isPlaying = !1, console.log("PerlinDOM pausing..."));
|
|
@@ -38,61 +56,66 @@ const u = class u {
|
|
|
38
56
|
this.isPlaying || (this.targetLerpFactor = 1, this.isPlaying = !0, this.lerpFactor === 0 && (this.raf = requestAnimationFrame(this.animate)), console.log("PerlinDOM playing..."));
|
|
39
57
|
}
|
|
40
58
|
destroy() {
|
|
41
|
-
cancelAnimationFrame(this.raf);
|
|
59
|
+
cancelAnimationFrame(this.raf), this.hasPercentages && window.removeEventListener("resize", this.handleResize);
|
|
42
60
|
}
|
|
43
61
|
};
|
|
44
|
-
|
|
62
|
+
n = new WeakSet(), W = function(t) {
|
|
45
63
|
return function() {
|
|
46
64
|
let i = t += 1831565813;
|
|
47
65
|
return i = Math.imul(i ^ i >>> 15, i | 1), i ^= i + Math.imul(i ^ i >>> 7, i | 61), ((i ^ i >>> 14) >>> 0) / 4294967296;
|
|
48
66
|
};
|
|
49
|
-
},
|
|
50
|
-
var
|
|
51
|
-
const i = Array.from({ length: 256 }, (
|
|
52
|
-
for (let
|
|
53
|
-
const s = Math.floor(
|
|
54
|
-
[i[
|
|
67
|
+
}, H = function(t = 0) {
|
|
68
|
+
var r;
|
|
69
|
+
const i = Array.from({ length: 256 }, (l, s) => s), h = a(r = o, n, W).call(r, t);
|
|
70
|
+
for (let l = 255; l > 0; l--) {
|
|
71
|
+
const s = Math.floor(h() * (l + 1));
|
|
72
|
+
[i[l], i[s]] = [i[s], i[l]];
|
|
55
73
|
}
|
|
56
74
|
return i.concat(i);
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
}, u = function(t) {
|
|
76
|
+
return typeof t == "string" && t.endsWith("%");
|
|
77
|
+
}, X = function(t, i) {
|
|
78
|
+
var r;
|
|
79
|
+
return a(r = o, n, u).call(r, t) ? parseFloat(t) / 100 * i : t;
|
|
80
|
+
}, p = new WeakSet(), S = function(t) {
|
|
81
|
+
const i = (s) => s * s * s * (s * (s * 6 - 15) + 10), h = (s, e, g) => e + s * (g - e), r = (s, e, g) => {
|
|
82
|
+
const m = s & 15, f = m < 8 ? e : g, R = m < 4 ? g : m === 12 || m === 14 ? e : 0;
|
|
83
|
+
return (m & 1 ? -f : f) + (m & 2 ? -R : R);
|
|
84
|
+
}, l = (s, e) => s & 1 ? -e : e;
|
|
62
85
|
return {
|
|
63
86
|
// Original 1D Perlin noise function
|
|
64
87
|
perlin1(s) {
|
|
65
88
|
const e = Math.floor(s) & 255;
|
|
66
89
|
s -= Math.floor(s);
|
|
67
|
-
const
|
|
68
|
-
return
|
|
90
|
+
const g = i(s);
|
|
91
|
+
return h(g, l(t[e], s), l(t[e + 1], s - 1));
|
|
69
92
|
},
|
|
70
93
|
// New 2D Perlin noise function
|
|
71
94
|
perlin2(s, e) {
|
|
72
|
-
const
|
|
95
|
+
const g = Math.floor(s) & 255, m = Math.floor(e) & 255;
|
|
73
96
|
s -= Math.floor(s), e -= Math.floor(e);
|
|
74
|
-
const
|
|
75
|
-
return
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
97
|
+
const f = i(s), R = i(e), y = t[g] + m, P = t[g + 1] + m, Y = t[y], d = t[y + 1], F = t[P], x = t[P + 1];
|
|
98
|
+
return h(
|
|
99
|
+
R,
|
|
100
|
+
h(
|
|
101
|
+
f,
|
|
102
|
+
r(t[Y], s, e),
|
|
103
|
+
r(t[F], s - 1, e)
|
|
81
104
|
),
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
105
|
+
h(
|
|
106
|
+
f,
|
|
107
|
+
r(t[d], s, e - 1),
|
|
108
|
+
r(t[x], s - 1, e - 1)
|
|
86
109
|
)
|
|
87
110
|
);
|
|
88
111
|
}
|
|
89
112
|
};
|
|
90
|
-
},
|
|
91
|
-
return t +
|
|
92
|
-
},
|
|
93
|
-
return (t - i) * (
|
|
94
|
-
},
|
|
95
|
-
let
|
|
113
|
+
}, T = function(t, i, h) {
|
|
114
|
+
return t + h * (i - t);
|
|
115
|
+
}, L = function(t, i, h, r, l) {
|
|
116
|
+
return (t - i) * (l - r) / (h - i) + r;
|
|
117
|
+
}, A(o, n);
|
|
118
|
+
let E = o;
|
|
96
119
|
export {
|
|
97
|
-
|
|
120
|
+
E as default
|
|
98
121
|
};
|
package/dist/PerlinDOM.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(a,t){typeof exports=="object"&&typeof module<"u"?module.exports=t():typeof define=="function"&&define.amd?define(t):(a=typeof globalThis<"u"?globalThis:a||self,a.PerlinDOM=t())})(this,function(){"use strict";var O=Object.defineProperty;var T=a=>{throw TypeError(a)};var S=(a,t,p)=>t in a?O(a,t,{enumerable:!0,configurable:!0,writable:!0,value:p}):a[t]=p;var q=(a,t,p)=>S(a,typeof t!="symbol"?t+"":t,p),$=(a,t,p)=>t.has(a)||T("Cannot "+p);var M=(a,t,p)=>t.has(a)?T("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(a):t.set(a,p);var h=(a,t,p)=>($(a,t,"access private method"),p);var t,E,W,d,L,f,j,H,Y;const o=class o{constructor({element:e,x:i=null,y:r=null,speed:l=.01,seed:c=0,lerpSpeed:s=.1}){M(this,f);q(this,"animate",()=>{var e,i,r,l,c,s,n,g,m,u,R,P;if(this.lerpFactor!==this.targetLerpFactor&&(this.lerpFactor=h(this,f,H).call(this,this.lerpFactor,this.targetLerpFactor,this.lerpSpeed),Math.abs(this.lerpFactor-this.targetLerpFactor)<.001&&(this.lerpFactor=this.targetLerpFactor)),this.lerpFactor>0){this.time+=this.speed*this.lerpFactor;const x=this.noise.perlin2(this.time,0),v=this.noise.perlin2(this.time,100);if(this.el){let F,y,X,A;if(this.hasPercentages){const B=this.el.parentElement||document.body,w=B.clientWidth,D=B.clientHeight;this.xRange&&(F=h(e=o,t,d).call(e,this.xRange.min)?h(i=o,t,L).call(i,this.xRange.min,w):this.xRange.min,y=h(r=o,t,d).call(r,this.xRange.max)?h(l=o,t,L).call(l,this.xRange.max,w):this.xRange.max),this.yRange&&(X=h(c=o,t,d).call(c,this.yRange.min)?h(s=o,t,L).call(s,this.yRange.min,D):this.yRange.min,A=h(n=o,t,d).call(n,this.yRange.max)?h(g=o,t,L).call(g,this.yRange.max,D):this.yRange.max)}else F=(m=this.xRange)==null?void 0:m.min,y=(u=this.xRange)==null?void 0:u.max,X=(R=this.yRange)==null?void 0:R.min,A=(P=this.yRange)==null?void 0:P.max;const z=this.xRange?h(this,f,Y).call(this,x,-1,1,F,y):0,b=this.yRange?h(this,f,Y).call(this,v,-1,1,X,A):0;this.lastX=z,this.lastY=b,this.el.style.left=`${this.initialX+z}px`,this.el.style.top=`${this.initialY+b}px`}else{const F=this.xRange?h(this,f,Y).call(this,x,-1,1,this.xRange.min,this.xRange.max):0,y=this.yRange?h(this,f,Y).call(this,v,-1,1,this.yRange.min,this.yRange.max):0;this.lastX=F,this.lastY=y}}(this.lerpFactor>0||this.targetLerpFactor>0)&&(this.raf=requestAnimationFrame(this.animate))});var n,g,m,u;this.el=e,this.xRange=i,this.yRange=r,this.speed=l,this.time=c,this.isPlaying=!0,this.lerpFactor=1,this.lerpSpeed=s,this.targetLerpFactor=1,this.lastX=0,this.lastY=0,this.hasPercentages=!1,(i&&(h(n=o,t,d).call(n,i.min)||h(g=o,t,d).call(g,i.max))||r&&(h(m=o,t,d).call(m,r.min)||h(u=o,t,d).call(u,r.max)))&&(this.hasPercentages=!0),e?(this.initialX=e.offsetLeft,this.initialY=e.offsetTop,this.hasPercentages&&(this.handleResize=this.handleResize.bind(this),window.addEventListener("resize",this.handleResize))):(this.initialX=0,this.initialY=0),this.animate=this.animate.bind(this),this.init(c)}init(e){var i;this.noise=h(this,f,j).call(this,h(i=o,t,W).call(i,e)),this.isPlaying=!0,this.lerpFactor=1,this.targetLerpFactor=1,this.raf=requestAnimationFrame(this.animate),console.log("PerlinDOM initialized")}handleResize(){this.hasPercentages&&this.el&&(this.needsRecalculation=!0)}pause(){this.isPlaying&&(this.targetLerpFactor=0,this.isPlaying=!1,console.log("PerlinDOM pausing..."))}play(){this.isPlaying||(this.targetLerpFactor=1,this.isPlaying=!0,this.lerpFactor===0&&(this.raf=requestAnimationFrame(this.animate)),console.log("PerlinDOM playing..."))}destroy(){cancelAnimationFrame(this.raf),this.hasPercentages&&window.removeEventListener("resize",this.handleResize)}};t=new WeakSet,E=function(e){return function(){let i=e+=1831565813;return i=Math.imul(i^i>>>15,i|1),i^=i+Math.imul(i^i>>>7,i|61),((i^i>>>14)>>>0)/4294967296}},W=function(e=0){var l;const i=Array.from({length:256},(c,s)=>s),r=h(l=o,t,E).call(l,e);for(let c=255;c>0;c--){const s=Math.floor(r()*(c+1));[i[c],i[s]]=[i[s],i[c]]}return i.concat(i)},d=function(e){return typeof e=="string"&&e.endsWith("%")},L=function(e,i){var l;return h(l=o,t,d).call(l,e)?parseFloat(e)/100*i:e},f=new WeakSet,j=function(e){const i=s=>s*s*s*(s*(s*6-15)+10),r=(s,n,g)=>n+s*(g-n),l=(s,n,g)=>{const m=s&15,u=m<8?n:g,R=m<4?g:m===12||m===14?n:0;return(m&1?-u:u)+(m&2?-R:R)},c=(s,n)=>s&1?-n:n;return{perlin1(s){const n=Math.floor(s)&255;s-=Math.floor(s);const g=i(s);return r(g,c(e[n],s),c(e[n+1],s-1))},perlin2(s,n){const g=Math.floor(s)&255,m=Math.floor(n)&255;s-=Math.floor(s),n-=Math.floor(n);const u=i(s),R=i(n),P=e[g]+m,x=e[g+1]+m,v=e[P],F=e[P+1],y=e[x],X=e[x+1];return r(R,r(u,l(e[v],s,n),l(e[y],s-1,n)),r(u,l(e[F],s,n-1),l(e[X],s-1,n-1)))}}},H=function(e,i,r){return e+r*(i-e)},Y=function(e,i,r,l,c){return(e-i)*(c-l)/(r-i)+l},M(o,t);let a=o;return a});
|
package/docs/examples.html
CHANGED
|
@@ -169,6 +169,12 @@
|
|
|
169
169
|
<h1>PerlinDOM Examples</h1>
|
|
170
170
|
<p>This page demonstrates various ways to use the PerlinDOM library for creating smooth, organic animations.</p>
|
|
171
171
|
|
|
172
|
+
<div style="background-color: #e3f2fd; padding: 15px; border-radius: 5px; margin-bottom: 20px; border-left: 5px solid #2196f3;">
|
|
173
|
+
<h3 style="margin-top: 0; color: #0d47a1;">New Feature: Percentage-Based Values</h3>
|
|
174
|
+
<p>PerlinDOM now supports percentage-based values for responsive animations that adapt to container size.</p>
|
|
175
|
+
<p><a href="../src/percentage-example.html" style="color: #1976d2; font-weight: bold;">View Percentage-Based Examples →</a></p>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
172
178
|
<!-- Example 1: Basic Animation -->
|
|
173
179
|
<h2>1. Basic Animation</h2>
|
|
174
180
|
<div class="code-block">
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andresclua/perlindom",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.2",
|
|
5
5
|
"description": "A lightweight JavaScript library for creating smooth, organic animations for DOM elements using Perlin noise",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/perlindom.umd.js",
|
package/readme.md
CHANGED
|
@@ -19,6 +19,7 @@ PerlinDOM allows you to animate HTML elements with natural and organic movements
|
|
|
19
19
|
|
|
20
20
|
- **Organic animations**: Natural movements that avoid the rigidity of traditional animations
|
|
21
21
|
- **Customizable**: Control over movement ranges, speed, and noise seed
|
|
22
|
+
- **Responsive**: Support for percentage-based values that adapt to container size
|
|
22
23
|
- **Lightweight**: Minimal implementation with no external dependencies
|
|
23
24
|
- **Easy to use**: Simple API to implement complex effects
|
|
24
25
|
- **Optimized performance**: Uses requestAnimationFrame for smooth animations
|
|
@@ -50,12 +51,42 @@ const animation = new PerlinDOM({
|
|
|
50
51
|
| Option | Type | Description |
|
|
51
52
|
|--------|------|-------------|
|
|
52
53
|
| `element` | HTMLElement | The DOM element to animate |
|
|
53
|
-
| `x` | Object or null | Horizontal movement configuration: `{min: value, max: value}`. Use `null` to disable horizontal movement |
|
|
54
|
-
| `y` | Object or null | Vertical movement configuration: `{min: value, max: value}`. Use `null` to disable vertical movement |
|
|
54
|
+
| `x` | Object or null | Horizontal movement configuration: `{min: value, max: value}`. Values can be numbers (pixels) or strings with '%' (percentage of parent width). Use `null` to disable horizontal movement |
|
|
55
|
+
| `y` | Object or null | Vertical movement configuration: `{min: value, max: value}`. Values can be numbers (pixels) or strings with '%' (percentage of parent height). Use `null` to disable vertical movement |
|
|
55
56
|
| `speed` | Number | Animation speed (smaller values = slower movement) |
|
|
56
57
|
| `seed` | Number | Seed for the Perlin noise generator |
|
|
57
58
|
| `lerpSpeed` | Number | Interpolation speed for smooth transitions (default: 0.1) |
|
|
58
59
|
|
|
60
|
+
> **⚠️ Important**: For PerlinDOM to work correctly, the animated element **must** have a CSS `position` property set to `absolute`, `relative`, or `fixed`. Without this property, the animation will not function as expected.
|
|
61
|
+
|
|
62
|
+
## Percentage-Based Values
|
|
63
|
+
|
|
64
|
+
PerlinDOM now supports percentage-based values for movement ranges, allowing for responsive animations that adapt to the size of the parent container:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
const responsiveAnimation = new PerlinDOM({
|
|
68
|
+
element: document.querySelector('.responsive-element'),
|
|
69
|
+
x: { min: '-10%', max: '10%' }, // 10% of parent width in each direction
|
|
70
|
+
y: { min: '-5%', max: '5%' }, // 5% of parent height in each direction
|
|
71
|
+
speed: 0.01,
|
|
72
|
+
seed: 123
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
You can also mix pixel and percentage values:
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
const mixedAnimation = new PerlinDOM({
|
|
80
|
+
element: document.querySelector('.mixed-element'),
|
|
81
|
+
x: { min: '-10%', max: '10%' }, // Percentage-based horizontal movement
|
|
82
|
+
y: { min: -20, max: 20 }, // Pixel-based vertical movement
|
|
83
|
+
speed: 0.01,
|
|
84
|
+
seed: 456
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
When using percentage values, PerlinDOM automatically recalculates the movement range when the window is resized, ensuring that your animations remain proportional to their container.
|
|
89
|
+
|
|
59
90
|
## Examples
|
|
60
91
|
|
|
61
92
|
### 1. Basic Animation
|
|
@@ -251,6 +282,23 @@ function animateTriangle() {
|
|
|
251
282
|
animateTriangle();
|
|
252
283
|
```
|
|
253
284
|
|
|
285
|
+
### 9. Responsive Animation with Percentage Values
|
|
286
|
+
|
|
287
|
+
Create an animation that adapts to the size of its container.
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
const responsiveAnimation = new PerlinDOM({
|
|
291
|
+
element: document.querySelector('.responsive-element'),
|
|
292
|
+
x: { min: '-15%', max: '15%' }, // 15% of parent width in each direction
|
|
293
|
+
y: { min: '-10%', max: '10%' }, // 10% of parent height in each direction
|
|
294
|
+
speed: 0.008,
|
|
295
|
+
seed: 789
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// The animation will automatically adjust when the window is resized
|
|
299
|
+
// No additional code needed for responsiveness
|
|
300
|
+
```
|
|
301
|
+
|
|
254
302
|
## Use Cases
|
|
255
303
|
|
|
256
304
|
- **Background elements**: Create dynamic and organic backgrounds
|
|
@@ -264,7 +312,7 @@ animateTriangle();
|
|
|
264
312
|
PerlinDOM works in all modern browsers that support:
|
|
265
313
|
- ES6 (ECMAScript 2015)
|
|
266
314
|
- requestAnimationFrame
|
|
267
|
-
- CSS
|
|
315
|
+
- CSS positioning (absolute, relative, fixed)
|
|
268
316
|
|
|
269
317
|
## License
|
|
270
318
|
|
package/src/index.html
CHANGED
|
@@ -644,6 +644,96 @@
|
|
|
644
644
|
</div>
|
|
645
645
|
</div>
|
|
646
646
|
|
|
647
|
+
<!-- Example 9: Pixel vs. Percentage Values -->
|
|
648
|
+
<h2>9. Pixel vs. Percentage Values</h2>
|
|
649
|
+
<div class="code-block">
|
|
650
|
+
// Pixel-based animation (fixed size)<br>
|
|
651
|
+
const pixelAnimation = new PerlinDOM({<br>
|
|
652
|
+
element: document.querySelector('.pixel-element'),<br>
|
|
653
|
+
x: { min: -50, max: 50 },<br>
|
|
654
|
+
y: { min: -50, max: 50 },<br>
|
|
655
|
+
speed: 0.01,<br>
|
|
656
|
+
seed: 123<br>
|
|
657
|
+
});<br><br>
|
|
658
|
+
|
|
659
|
+
// Percentage-based animation (responsive)<br>
|
|
660
|
+
const percentageAnimation = new PerlinDOM({<br>
|
|
661
|
+
element: document.querySelector('.percentage-element'),<br>
|
|
662
|
+
x: { min: '-20%', max: '20%' },<br>
|
|
663
|
+
y: { min: '-20%', max: '20%' },<br>
|
|
664
|
+
speed: 0.01,<br>
|
|
665
|
+
seed: 123<br>
|
|
666
|
+
});
|
|
667
|
+
</div>
|
|
668
|
+
<div class="example-container">
|
|
669
|
+
<div style="display: flex; justify-content: space-between; height: 100%;">
|
|
670
|
+
<div style="width: 48%; height: 100%; position: relative; border: 1px dashed #ccc; border-radius: 5px; overflow: hidden;">
|
|
671
|
+
<div style="position: absolute; top: 10px; left: 0; width: 100%; text-align: center; font-weight: bold; color: #7f8c8d; z-index: 10;">Pixel Values (Fixed)</div>
|
|
672
|
+
<div class="pixel-element" style="width: 80px; height: 80px; background-color: #3498db; border-radius: 50%; position: absolute; left: calc(50% - 40px); top: calc(50% - 40px); filter: blur(5px);"></div>
|
|
673
|
+
<div style="position: absolute; bottom: 10px; left: 0; width: 100%; text-align: center; font-size: 12px; color: #7f8c8d;">Resize your browser window - animation range stays the same</div>
|
|
674
|
+
</div>
|
|
675
|
+
<div style="width: 48%; height: 100%; position: relative; border: 1px dashed #ccc; border-radius: 5px; overflow: hidden;">
|
|
676
|
+
<div style="position: absolute; top: 10px; left: 0; width: 100%; text-align: center; font-weight: bold; color: #7f8c8d; z-index: 10;">Percentage Values (Responsive)</div>
|
|
677
|
+
<div class="percentage-element" style="width: 80px; height: 80px; background-color: #e74c3c; border-radius: 50%; position: absolute; left: calc(50% - 40px); top: calc(50% - 40px); filter: blur(5px);"></div>
|
|
678
|
+
<div style="position: absolute; bottom: 10px; left: 0; width: 100%; text-align: center; font-size: 12px; color: #7f8c8d;">Resize your browser window - animation range adapts</div>
|
|
679
|
+
</div>
|
|
680
|
+
</div>
|
|
681
|
+
</div>
|
|
682
|
+
|
|
683
|
+
<!-- Example 10: Resizable Container with Percentage Values -->
|
|
684
|
+
<h2>10. Resizable Container with Percentage Values</h2>
|
|
685
|
+
<div class="code-block">
|
|
686
|
+
const responsiveAnimation = new PerlinDOM({<br>
|
|
687
|
+
element: document.querySelector('.responsive-element'),<br>
|
|
688
|
+
x: { min: '-25%', max: '25%' },<br>
|
|
689
|
+
y: { min: '-25%', max: '25%' },<br>
|
|
690
|
+
speed: 0.008,<br>
|
|
691
|
+
seed: 456<br>
|
|
692
|
+
});
|
|
693
|
+
</div>
|
|
694
|
+
<div class="example-container">
|
|
695
|
+
<div style="height: 100%; position: relative; border: 1px solid #ddd; border-radius: 5px; overflow: hidden; resize: both; min-width: 200px; min-height: 200px; background-color: #f0f0f0;">
|
|
696
|
+
<div class="responsive-element" style="width: 60px; height: 60px; background-color: #9b59b6; border-radius: 50%; position: absolute; left: calc(50% - 30px); top: calc(50% - 30px);"></div>
|
|
697
|
+
<div style="position: absolute; bottom: 10px; right: 10px; font-size: 12px; color: #7f8c8d;">↘️ Drag this corner to resize the container</div>
|
|
698
|
+
</div>
|
|
699
|
+
</div>
|
|
700
|
+
|
|
701
|
+
<!-- Example 11: Mixed Pixel and Percentage Values -->
|
|
702
|
+
<h2>11. Mixed Pixel and Percentage Values</h2>
|
|
703
|
+
<div class="code-block">
|
|
704
|
+
// Horizontal percentage, vertical pixel<br>
|
|
705
|
+
const horizontalPercentAnimation = new PerlinDOM({<br>
|
|
706
|
+
element: document.querySelector('.horizontal-percent-element'),<br>
|
|
707
|
+
x: { min: '-30%', max: '30%' },<br>
|
|
708
|
+
y: { min: -40, max: 40 },<br>
|
|
709
|
+
speed: 0.01,<br>
|
|
710
|
+
seed: 789<br>
|
|
711
|
+
});<br><br>
|
|
712
|
+
|
|
713
|
+
// Horizontal pixel, vertical percentage<br>
|
|
714
|
+
const verticalPercentAnimation = new PerlinDOM({<br>
|
|
715
|
+
element: document.querySelector('.vertical-percent-element'),<br>
|
|
716
|
+
x: { min: -80, max: 80 },<br>
|
|
717
|
+
y: { min: '-20%', max: '20%' },<br>
|
|
718
|
+
speed: 0.01,<br>
|
|
719
|
+
seed: 789<br>
|
|
720
|
+
});
|
|
721
|
+
</div>
|
|
722
|
+
<div class="example-container">
|
|
723
|
+
<div style="display: flex; justify-content: space-between; height: 100%;">
|
|
724
|
+
<div style="width: 48%; height: 100%; position: relative; border: 1px dashed #ccc; border-radius: 5px; overflow: hidden;">
|
|
725
|
+
<div style="position: absolute; top: 10px; left: 0; width: 100%; text-align: center; font-weight: bold; color: #7f8c8d; z-index: 10;">Horizontal % / Vertical px</div>
|
|
726
|
+
<div class="horizontal-percent-element" style="width: 60px; height: 60px; background-color: #f39c12; border-radius: 10px; position: absolute; left: calc(50% - 30px); top: calc(50% - 30px);"></div>
|
|
727
|
+
<div style="position: absolute; bottom: 10px; left: 0; width: 100%; text-align: center; font-size: 12px; color: #7f8c8d;">Horizontal movement adapts to width, vertical is fixed</div>
|
|
728
|
+
</div>
|
|
729
|
+
<div style="width: 48%; height: 100%; position: relative; border: 1px dashed #ccc; border-radius: 5px; overflow: hidden;">
|
|
730
|
+
<div style="position: absolute; top: 10px; left: 0; width: 100%; text-align: center; font-weight: bold; color: #7f8c8d; z-index: 10;">Horizontal px / Vertical %</div>
|
|
731
|
+
<div class="vertical-percent-element" style="width: 60px; height: 60px; background-color: #f39c12; border-radius: 10px; position: absolute; left: calc(50% - 30px); top: calc(50% - 30px);"></div>
|
|
732
|
+
<div style="position: absolute; bottom: 10px; left: 0; width: 100%; text-align: center; font-size: 12px; color: #7f8c8d;">Horizontal movement is fixed, vertical adapts to height</div>
|
|
733
|
+
</div>
|
|
734
|
+
</div>
|
|
735
|
+
</div>
|
|
736
|
+
|
|
647
737
|
<!-- Load all scripts -->
|
|
648
738
|
<script type="module" src="./js/index.js"></script>
|
|
649
739
|
<script type="module" src="./js/basic-examples.js"></script>
|
|
@@ -651,5 +741,51 @@
|
|
|
651
741
|
<script type="module" src="./js/interactive-controls.js"></script>
|
|
652
742
|
<script type="module" src="./js/animated-button.js"></script>
|
|
653
743
|
<script type="module" src="./js/triangle-animation.js"></script>
|
|
744
|
+
<script type="module">
|
|
745
|
+
import PerlinDOM from './js/PerlinDom.js';
|
|
746
|
+
|
|
747
|
+
// Example 9: Pixel vs. Percentage Values
|
|
748
|
+
const pixelAnimation = new PerlinDOM({
|
|
749
|
+
element: document.querySelector('.pixel-element'),
|
|
750
|
+
x: { min: -50, max: 50 },
|
|
751
|
+
y: { min: -50, max: 50 },
|
|
752
|
+
speed: 0.01,
|
|
753
|
+
seed: 123
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
const percentageAnimation = new PerlinDOM({
|
|
757
|
+
element: document.querySelector('.percentage-element'),
|
|
758
|
+
x: { min: '-20%', max: '20%' },
|
|
759
|
+
y: { min: '-20%', max: '20%' },
|
|
760
|
+
speed: 0.01,
|
|
761
|
+
seed: 123
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
// Example 10: Resizable Container
|
|
765
|
+
const responsiveAnimation = new PerlinDOM({
|
|
766
|
+
element: document.querySelector('.responsive-element'),
|
|
767
|
+
x: { min: '-25%', max: '25%' },
|
|
768
|
+
y: { min: '-25%', max: '25%' },
|
|
769
|
+
speed: 0.008,
|
|
770
|
+
seed: 456
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
// Example 11: Mixed Values
|
|
774
|
+
const horizontalPercentAnimation = new PerlinDOM({
|
|
775
|
+
element: document.querySelector('.horizontal-percent-element'),
|
|
776
|
+
x: { min: '-30%', max: '30%' },
|
|
777
|
+
y: { min: -40, max: 40 },
|
|
778
|
+
speed: 0.01,
|
|
779
|
+
seed: 789
|
|
780
|
+
});
|
|
781
|
+
|
|
782
|
+
const verticalPercentAnimation = new PerlinDOM({
|
|
783
|
+
element: document.querySelector('.vertical-percent-element'),
|
|
784
|
+
x: { min: -80, max: 80 },
|
|
785
|
+
y: { min: '-20%', max: '20%' },
|
|
786
|
+
speed: 0.01,
|
|
787
|
+
seed: 789
|
|
788
|
+
});
|
|
789
|
+
</script>
|
|
654
790
|
</body>
|
|
655
791
|
</html>
|
package/src/js/PerlinDom.js
CHANGED
|
@@ -18,6 +18,18 @@ class PerlinDOM {
|
|
|
18
18
|
}
|
|
19
19
|
return permutation.concat(permutation);
|
|
20
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
|
+
}
|
|
21
33
|
|
|
22
34
|
constructor({
|
|
23
35
|
element,
|
|
@@ -38,11 +50,24 @@ class PerlinDOM {
|
|
|
38
50
|
this.targetLerpFactor = 1; // Target state (1 = play, 0 = pause)
|
|
39
51
|
this.lastX = 0;
|
|
40
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
|
+
}
|
|
41
60
|
|
|
42
61
|
// Solo inicializar estas propiedades si el elemento no es null
|
|
43
62
|
if (element) {
|
|
44
63
|
this.initialX = element.offsetLeft;
|
|
45
64
|
this.initialY = element.offsetTop;
|
|
65
|
+
|
|
66
|
+
// If using percentages, add resize listener
|
|
67
|
+
if (this.hasPercentages) {
|
|
68
|
+
this.handleResize = this.handleResize.bind(this);
|
|
69
|
+
window.addEventListener('resize', this.handleResize);
|
|
70
|
+
}
|
|
46
71
|
} else {
|
|
47
72
|
this.initialX = 0;
|
|
48
73
|
this.initialY = 0;
|
|
@@ -62,6 +87,15 @@ class PerlinDOM {
|
|
|
62
87
|
console.log('PerlinDOM initialized');
|
|
63
88
|
}
|
|
64
89
|
|
|
90
|
+
// Handle window resize for percentage-based values
|
|
91
|
+
handleResize() {
|
|
92
|
+
// Only recalculate if we're using percentages
|
|
93
|
+
if (this.hasPercentages && this.el) {
|
|
94
|
+
// Force a recalculation on next animation frame
|
|
95
|
+
this.needsRecalculation = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
65
99
|
pause() {
|
|
66
100
|
if (this.isPlaying) {
|
|
67
101
|
this.targetLerpFactor = 0; // Target paused state
|
|
@@ -178,23 +212,73 @@ class PerlinDOM {
|
|
|
178
212
|
const noiseX = this.noise.perlin2(this.time, 0);
|
|
179
213
|
const noiseY = this.noise.perlin2(this.time, 100); // Different slice of the noise field
|
|
180
214
|
|
|
181
|
-
|
|
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
|
|
215
|
+
// Only process if we have an element
|
|
195
216
|
if (this.el) {
|
|
217
|
+
let minX, maxX, minY, maxY;
|
|
218
|
+
|
|
219
|
+
// Get parent dimensions for percentage calculations if needed
|
|
220
|
+
if (this.hasPercentages) {
|
|
221
|
+
const parent = this.el.parentElement || document.body;
|
|
222
|
+
const parentWidth = parent.clientWidth;
|
|
223
|
+
const parentHeight = parent.clientHeight;
|
|
224
|
+
|
|
225
|
+
// Convert percentage values to pixels if needed
|
|
226
|
+
if (this.xRange) {
|
|
227
|
+
minX = PerlinDOM.#isPercentage(this.xRange.min)
|
|
228
|
+
? PerlinDOM.#percentToPixels(this.xRange.min, parentWidth)
|
|
229
|
+
: this.xRange.min;
|
|
230
|
+
|
|
231
|
+
maxX = PerlinDOM.#isPercentage(this.xRange.max)
|
|
232
|
+
? PerlinDOM.#percentToPixels(this.xRange.max, parentWidth)
|
|
233
|
+
: this.xRange.max;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (this.yRange) {
|
|
237
|
+
minY = PerlinDOM.#isPercentage(this.yRange.min)
|
|
238
|
+
? PerlinDOM.#percentToPixels(this.yRange.min, parentHeight)
|
|
239
|
+
: this.yRange.min;
|
|
240
|
+
|
|
241
|
+
maxY = PerlinDOM.#isPercentage(this.yRange.max)
|
|
242
|
+
? PerlinDOM.#percentToPixels(this.yRange.max, parentHeight)
|
|
243
|
+
: this.yRange.max;
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
// Use the original values if not using percentages
|
|
247
|
+
minX = this.xRange?.min;
|
|
248
|
+
maxX = this.xRange?.max;
|
|
249
|
+
minY = this.yRange?.min;
|
|
250
|
+
maxY = this.yRange?.max;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Calculate the actual pixel values
|
|
254
|
+
const valX = this.xRange
|
|
255
|
+
? this.#map(noiseX, -1, 1, minX, maxX)
|
|
256
|
+
: 0;
|
|
257
|
+
|
|
258
|
+
const valY = this.yRange
|
|
259
|
+
? this.#map(noiseY, -1, 1, minY, maxY)
|
|
260
|
+
: 0;
|
|
261
|
+
|
|
262
|
+
// Store current values for interpolation
|
|
263
|
+
this.lastX = valX;
|
|
264
|
+
this.lastY = valY;
|
|
265
|
+
|
|
266
|
+
// Update position using left/top
|
|
196
267
|
this.el.style.left = `${this.initialX + valX}px`;
|
|
197
268
|
this.el.style.top = `${this.initialY + valY}px`;
|
|
269
|
+
} else {
|
|
270
|
+
// If no element, just calculate and store the values
|
|
271
|
+
const valX = this.xRange
|
|
272
|
+
? this.#map(noiseX, -1, 1, this.xRange.min, this.xRange.max)
|
|
273
|
+
: 0;
|
|
274
|
+
|
|
275
|
+
const valY = this.yRange
|
|
276
|
+
? this.#map(noiseY, -1, 1, this.yRange.min, this.yRange.max)
|
|
277
|
+
: 0;
|
|
278
|
+
|
|
279
|
+
// Store current values for interpolation
|
|
280
|
+
this.lastX = valX;
|
|
281
|
+
this.lastY = valY;
|
|
198
282
|
}
|
|
199
283
|
}
|
|
200
284
|
|
|
@@ -210,6 +294,11 @@ class PerlinDOM {
|
|
|
210
294
|
|
|
211
295
|
destroy() {
|
|
212
296
|
cancelAnimationFrame(this.raf);
|
|
297
|
+
|
|
298
|
+
// Remove resize listener if it was added
|
|
299
|
+
if (this.hasPercentages) {
|
|
300
|
+
window.removeEventListener('resize', this.handleResize);
|
|
301
|
+
}
|
|
213
302
|
}
|
|
214
303
|
}
|
|
215
304
|
export default PerlinDOM;
|
package/src/examples.html
DELETED
|
@@ -1,515 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>PerlinDOM Examples</title>
|
|
7
|
-
<style>
|
|
8
|
-
body {
|
|
9
|
-
font-family: 'Arial', sans-serif;
|
|
10
|
-
line-height: 1.6;
|
|
11
|
-
color: #333;
|
|
12
|
-
max-width: 1200px;
|
|
13
|
-
margin: 0 auto;
|
|
14
|
-
padding: 20px;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
h1, h2 {
|
|
18
|
-
color: #2c3e50;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.example-container {
|
|
22
|
-
margin-bottom: 40px;
|
|
23
|
-
border: 1px solid #ddd;
|
|
24
|
-
border-radius: 8px;
|
|
25
|
-
padding: 20px;
|
|
26
|
-
position: relative;
|
|
27
|
-
overflow: hidden;
|
|
28
|
-
height: 300px;
|
|
29
|
-
background-color: #f9f9f9;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.code-block {
|
|
33
|
-
background-color: #f5f5f5;
|
|
34
|
-
padding: 15px;
|
|
35
|
-
border-radius: 5px;
|
|
36
|
-
overflow-x: auto;
|
|
37
|
-
margin-bottom: 20px;
|
|
38
|
-
font-family: monospace;
|
|
39
|
-
font-size: 14px;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/* Example 1: Basic Animation */
|
|
43
|
-
.basic-element {
|
|
44
|
-
width: 80px;
|
|
45
|
-
height: 80px;
|
|
46
|
-
background-color: #3498db;
|
|
47
|
-
border-radius: 50%;
|
|
48
|
-
position: absolute;
|
|
49
|
-
left: calc(50% - 40px); /* 50% - half width */
|
|
50
|
-
top: calc(50% - 40px); /* 50% - half height */
|
|
51
|
-
filter: blur(5px);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/* Example 2: Horizontal Only */
|
|
55
|
-
.horizontal-element {
|
|
56
|
-
width: 60px;
|
|
57
|
-
height: 60px;
|
|
58
|
-
background-color: #e74c3c;
|
|
59
|
-
border-radius: 10px;
|
|
60
|
-
position: absolute;
|
|
61
|
-
left: calc(50% - 30px); /* 50% - half width */
|
|
62
|
-
top: calc(50% - 30px); /* 50% - half height */
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/* Example 3: Multiple Elements */
|
|
66
|
-
.particle {
|
|
67
|
-
width: 230px;
|
|
68
|
-
height: 230px;
|
|
69
|
-
background:linear-gradient(to bottom right, #8e44ad, #3498db);
|
|
70
|
-
border-radius: 50%;
|
|
71
|
-
position: absolute;
|
|
72
|
-
/* opacity: 0.2; */
|
|
73
|
-
filter: blur(10px);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/* Example 4: Background Effect */
|
|
77
|
-
.bg-container {
|
|
78
|
-
background-color: #34495e;
|
|
79
|
-
position: relative;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
.bg-element {
|
|
83
|
-
position: absolute;
|
|
84
|
-
border-radius: 50%;
|
|
85
|
-
background-color: rgba(255, 255, 255, 0.1);
|
|
86
|
-
filter: blur(5px);
|
|
87
|
-
transform: scale(3.2);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.bg-element-1 {
|
|
91
|
-
width: 100px;
|
|
92
|
-
height: 100px;
|
|
93
|
-
left: 20%;
|
|
94
|
-
top: 30%;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.bg-element-2 {
|
|
98
|
-
width: 150px;
|
|
99
|
-
height: 150px;
|
|
100
|
-
left: 60%;
|
|
101
|
-
top: 50%;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.bg-element-3 {
|
|
105
|
-
width: 80px;
|
|
106
|
-
height: 80px;
|
|
107
|
-
left: 40%;
|
|
108
|
-
top: 70%;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
.content {
|
|
112
|
-
position: relative;
|
|
113
|
-
z-index: 10;
|
|
114
|
-
color: white;
|
|
115
|
-
text-align: center;
|
|
116
|
-
padding-top: 120px;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/* Example 5: Pause Play Element */
|
|
120
|
-
.pause-play-container {
|
|
121
|
-
position: relative;
|
|
122
|
-
cursor: pointer;
|
|
123
|
-
height: 200px;
|
|
124
|
-
border: 1px dashed #ccc;
|
|
125
|
-
border-radius: 8px;
|
|
126
|
-
margin-top: 20px;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.pause-play-element {
|
|
130
|
-
width: 70px;
|
|
131
|
-
height: 70px;
|
|
132
|
-
background-color: #f39c12;
|
|
133
|
-
border-radius: 50%;
|
|
134
|
-
position: absolute;
|
|
135
|
-
left: calc(50% - 35px);
|
|
136
|
-
top: calc(50% - 35px);
|
|
137
|
-
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
.status-display {
|
|
141
|
-
position: absolute;
|
|
142
|
-
bottom: 20px;
|
|
143
|
-
left: 0;
|
|
144
|
-
width: 100%;
|
|
145
|
-
text-align: center;
|
|
146
|
-
font-weight: bold;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.status-indicator {
|
|
150
|
-
color: #2980b9;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
.instruction {
|
|
154
|
-
font-size: 14px;
|
|
155
|
-
color: #7f8c8d;
|
|
156
|
-
margin-top: 5px;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.pause-play-settings {
|
|
160
|
-
margin-bottom: 20px;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.setting-description {
|
|
164
|
-
font-size: 12px;
|
|
165
|
-
color: #7f8c8d;
|
|
166
|
-
margin-top: 5px;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
.lerp-value {
|
|
170
|
-
font-weight: bold;
|
|
171
|
-
color: #e67e22;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/* Example 6: Interactive Element */
|
|
175
|
-
.interactive-container {
|
|
176
|
-
display: flex;
|
|
177
|
-
justify-content: space-between;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
.controls {
|
|
181
|
-
width: 40%;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
.preview {
|
|
185
|
-
width: 55%;
|
|
186
|
-
height: 200px;
|
|
187
|
-
border: 1px solid #ddd;
|
|
188
|
-
border-radius: 5px;
|
|
189
|
-
position: relative;
|
|
190
|
-
overflow: hidden;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
.interactive-element {
|
|
194
|
-
width: 50px;
|
|
195
|
-
height: 50px;
|
|
196
|
-
background-color: #9b59b6;
|
|
197
|
-
border-radius: 50%;
|
|
198
|
-
position: absolute;
|
|
199
|
-
left: calc(50% - 25px); /* 50% - half width */
|
|
200
|
-
top: calc(50% - 25px); /* 50% - half height */
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
.control-group {
|
|
204
|
-
margin-bottom: 15px;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
label {
|
|
208
|
-
display: block;
|
|
209
|
-
margin-bottom: 5px;
|
|
210
|
-
font-weight: bold;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
input[type="range"] {
|
|
214
|
-
width: 100%;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
.value-display {
|
|
218
|
-
font-size: 12px;
|
|
219
|
-
color: #666;
|
|
220
|
-
}
|
|
221
|
-
</style>
|
|
222
|
-
</head>
|
|
223
|
-
<body>
|
|
224
|
-
<h1>PerlinDOM Examples</h1>
|
|
225
|
-
<p>This page demonstrates various ways to use the PerlinDOM library for creating smooth, organic animations.</p>
|
|
226
|
-
|
|
227
|
-
<!-- Example 1: Basic Animation -->
|
|
228
|
-
<h2>1. Basic Animation</h2>
|
|
229
|
-
<div class="code-block">
|
|
230
|
-
const basicAnimation = new PerlinDOM({<br>
|
|
231
|
-
element: document.querySelector('.basic-element'),<br>
|
|
232
|
-
x: { min: -50, max: 50 },<br>
|
|
233
|
-
y: { min: -50, max: 50 },<br>
|
|
234
|
-
speed: 0.01,<br>
|
|
235
|
-
seed: 123<br>
|
|
236
|
-
});
|
|
237
|
-
</div>
|
|
238
|
-
<div class="example-container">
|
|
239
|
-
<div class="basic-element"></div>
|
|
240
|
-
</div>
|
|
241
|
-
|
|
242
|
-
<!-- Example 2: Horizontal Only -->
|
|
243
|
-
<h2>2. Horizontal-Only Movement</h2>
|
|
244
|
-
<div class="code-block">
|
|
245
|
-
const horizontalAnimation = new PerlinDOM({<br>
|
|
246
|
-
element: document.querySelector('.horizontal-element'),<br>
|
|
247
|
-
x: { min: -100, max: 100 },<br>
|
|
248
|
-
y: null, // No vertical movement<br>
|
|
249
|
-
speed: 0.02<br>
|
|
250
|
-
});
|
|
251
|
-
</div>
|
|
252
|
-
<div class="example-container">
|
|
253
|
-
<div class="horizontal-element"></div>
|
|
254
|
-
</div>
|
|
255
|
-
|
|
256
|
-
<!-- Example 3: Multiple Elements -->
|
|
257
|
-
<h2>3. Multiple Elements with Different Seeds</h2>
|
|
258
|
-
<div class="code-block">
|
|
259
|
-
document.querySelectorAll('.particle').forEach((el, index) => {<br>
|
|
260
|
-
new PerlinDOM({<br>
|
|
261
|
-
element: el,<br>
|
|
262
|
-
x: { min: -100, max: 100 },<br>
|
|
263
|
-
y: { min: -100, max: 100 },<br>
|
|
264
|
-
speed: 0.01,<br>
|
|
265
|
-
seed: index * 100 // Different seed for each element<br>
|
|
266
|
-
});<br>
|
|
267
|
-
});
|
|
268
|
-
</div>
|
|
269
|
-
<div class="example-container">
|
|
270
|
-
<div class="particle" style="left: 30%; top: 30%"></div>
|
|
271
|
-
<div class="particle" style="left: 50%; top: 50%"></div>
|
|
272
|
-
<div class="particle" style="left: 70%; top: 70%"></div>
|
|
273
|
-
<div class="particle" style="left: 30%; top: 70%"></div>
|
|
274
|
-
<div class="particle" style="left: 70%; top: 30%"></div>
|
|
275
|
-
</div>
|
|
276
|
-
|
|
277
|
-
<!-- Example 4: Background Effect -->
|
|
278
|
-
<h2>4. Creating a Background Effect</h2>
|
|
279
|
-
<div class="code-block">
|
|
280
|
-
document.querySelectorAll('.bg-element').forEach(el => {<br>
|
|
281
|
-
new PerlinDOM({<br>
|
|
282
|
-
element: el,<br>
|
|
283
|
-
x: { min: -10, max: 10 },<br>
|
|
284
|
-
y: { min: -10, max: 10 },<br>
|
|
285
|
-
speed: 0.002, // Very slow movement<br>
|
|
286
|
-
seed: Math.random() * 2000<br>
|
|
287
|
-
});<br>
|
|
288
|
-
});
|
|
289
|
-
</div>
|
|
290
|
-
<div class="example-container bg-container">
|
|
291
|
-
<div class="bg-element bg-element-1"></div>
|
|
292
|
-
<div class="bg-element bg-element-2"></div>
|
|
293
|
-
<div class="bg-element bg-element-3"></div>
|
|
294
|
-
<div class="content">
|
|
295
|
-
<h3>Subtle Background Animation</h3>
|
|
296
|
-
<p>Perfect for creating depth in UI elements</p>
|
|
297
|
-
</div>
|
|
298
|
-
</div>
|
|
299
|
-
|
|
300
|
-
<!-- Example 5: Pause and Play with Mouse Events -->
|
|
301
|
-
<h2>5. Smooth Pause and Play with Mouse Events</h2>
|
|
302
|
-
<div class="code-block">
|
|
303
|
-
const pausePlayAnimation = new PerlinDOM({<br>
|
|
304
|
-
element: document.querySelector('.pause-play-element'),<br>
|
|
305
|
-
x: { min: -80, max: 80 },<br>
|
|
306
|
-
y: { min: -80, max: 80 },<br>
|
|
307
|
-
speed: 0.01,<br>
|
|
308
|
-
seed: 42,<br>
|
|
309
|
-
lerpSpeed: 0.05 // Controls how smooth the transition is<br>
|
|
310
|
-
});<br><br>
|
|
311
|
-
|
|
312
|
-
// Pause on mouse leave<br>
|
|
313
|
-
document.querySelector('.pause-play-container').addEventListener('mouseleave', () => {<br>
|
|
314
|
-
pausePlayAnimation.pause();<br>
|
|
315
|
-
document.querySelector('.status-indicator').textContent = 'Pausing...';<br>
|
|
316
|
-
});<br><br>
|
|
317
|
-
|
|
318
|
-
// Play on mouse enter<br>
|
|
319
|
-
document.querySelector('.pause-play-container').addEventListener('mouseenter', () => {<br>
|
|
320
|
-
pausePlayAnimation.play();<br>
|
|
321
|
-
document.querySelector('.status-indicator').textContent = 'Playing...';<br>
|
|
322
|
-
});<br><br>
|
|
323
|
-
|
|
324
|
-
// Update lerpSpeed when slider changes<br>
|
|
325
|
-
document.getElementById('lerp-speed').addEventListener('input', (e) => {<br>
|
|
326
|
-
const newSpeed = parseFloat(e.target.value);<br>
|
|
327
|
-
pausePlayAnimation.lerpSpeed = newSpeed;<br>
|
|
328
|
-
document.querySelector('.lerp-value').textContent = newSpeed.toFixed(2);<br>
|
|
329
|
-
});
|
|
330
|
-
</div>
|
|
331
|
-
<div class="example-container">
|
|
332
|
-
<div class="pause-play-settings">
|
|
333
|
-
<label for="lerp-speed">Transition Speed: <span class="lerp-value">0.05</span></label>
|
|
334
|
-
<input type="range" id="lerp-speed" min="0.01" max="0.2" step="0.01" value="0.05">
|
|
335
|
-
<p class="setting-description">Lower values = smoother, slower transitions</p>
|
|
336
|
-
</div>
|
|
337
|
-
<div class="pause-play-container">
|
|
338
|
-
<div class="pause-play-element"></div>
|
|
339
|
-
<div class="status-display">
|
|
340
|
-
Status: <span class="status-indicator">Playing</span>
|
|
341
|
-
<p class="instruction">Hover over this orange circle to play, move mouse out to pause</p>
|
|
342
|
-
</div>
|
|
343
|
-
</div>
|
|
344
|
-
</div>
|
|
345
|
-
|
|
346
|
-
<!-- Example 6: Interactive Controls -->
|
|
347
|
-
<h2>6. Interactive Controls</h2>
|
|
348
|
-
<div class="code-block">
|
|
349
|
-
// Create the animation with initial settings<br>
|
|
350
|
-
const interactiveAnimation = new PerlinDOM({<br>
|
|
351
|
-
element: document.querySelector('.interactive-element'),<br>
|
|
352
|
-
x: { min: -50, max: 50 },<br>
|
|
353
|
-
y: { min: -50, max: 50 },<br>
|
|
354
|
-
speed: 0.01,<br>
|
|
355
|
-
seed: 0<br>
|
|
356
|
-
});<br><br>
|
|
357
|
-
|
|
358
|
-
// Update settings based on user input<br>
|
|
359
|
-
speedControl.addEventListener('input', () => {<br>
|
|
360
|
-
// Stop current animation<br>
|
|
361
|
-
interactiveAnimation.destroy();<br>
|
|
362
|
-
<br>
|
|
363
|
-
// Reinitialize with new settings<br>
|
|
364
|
-
interactiveAnimation.speed = parseFloat(speedControl.value);<br>
|
|
365
|
-
interactiveAnimation.init(seedControl.value);<br>
|
|
366
|
-
});
|
|
367
|
-
</div>
|
|
368
|
-
<div class="example-container">
|
|
369
|
-
<div class="interactive-container">
|
|
370
|
-
<div class="controls">
|
|
371
|
-
<div class="control-group">
|
|
372
|
-
<label for="x-range">X Range:</label>
|
|
373
|
-
<input type="range" id="x-range" min="0" max="100" value="50">
|
|
374
|
-
<div class="value-display">-50 to 50</div>
|
|
375
|
-
</div>
|
|
376
|
-
|
|
377
|
-
<div class="control-group">
|
|
378
|
-
<label for="y-range">Y Range:</label>
|
|
379
|
-
<input type="range" id="y-range" min="0" max="100" value="50">
|
|
380
|
-
<div class="value-display">-50 to 50</div>
|
|
381
|
-
</div>
|
|
382
|
-
|
|
383
|
-
<div class="control-group">
|
|
384
|
-
<label for="speed">Speed:</label>
|
|
385
|
-
<input type="range" id="speed" min="0.001" max="0.05" step="0.001" value="0.01">
|
|
386
|
-
<div class="value-display">0.01</div>
|
|
387
|
-
</div>
|
|
388
|
-
|
|
389
|
-
<div class="control-group">
|
|
390
|
-
<label for="seed">Seed:</label>
|
|
391
|
-
<input type="range" id="seed" min="0" max="1000" step="1" value="0">
|
|
392
|
-
<div class="value-display">0</div>
|
|
393
|
-
</div>
|
|
394
|
-
</div>
|
|
395
|
-
|
|
396
|
-
<div class="preview">
|
|
397
|
-
<div class="interactive-element"></div>
|
|
398
|
-
</div>
|
|
399
|
-
</div>
|
|
400
|
-
</div>
|
|
401
|
-
|
|
402
|
-
<!-- Load PerlinDOM library -->
|
|
403
|
-
<script type="module">
|
|
404
|
-
import PerlinDOM from './js/PerlinDom.js';
|
|
405
|
-
|
|
406
|
-
// Example 1: Basic Animation
|
|
407
|
-
const basicAnimation = new PerlinDOM({
|
|
408
|
-
element: document.querySelector('.basic-element'),
|
|
409
|
-
x: { min: -50, max: 50 },
|
|
410
|
-
y: { min: -50, max: 50 },
|
|
411
|
-
speed: 0.01,
|
|
412
|
-
seed: 123
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
// Example 2: Horizontal Only
|
|
416
|
-
const horizontalAnimation = new PerlinDOM({
|
|
417
|
-
element: document.querySelector('.horizontal-element'),
|
|
418
|
-
x: { min: -100, max: 100 },
|
|
419
|
-
y: null, // No vertical movement
|
|
420
|
-
speed: 0.02
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
// Example 3: Multiple Elements
|
|
424
|
-
document.querySelectorAll('.particle').forEach((el, index) => {
|
|
425
|
-
new PerlinDOM({
|
|
426
|
-
element: el,
|
|
427
|
-
x: { min: -100, max: 100 + index * 20 }, // Slightly wider range for each element
|
|
428
|
-
y: { min: -100, max: 120}, // Random vertical range
|
|
429
|
-
speed: index * 0.001, // Slightly different speed for each element
|
|
430
|
-
seed: index * 16700 // Different seed for each element
|
|
431
|
-
});
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
// Example 4: Background Effect
|
|
435
|
-
document.querySelectorAll('.bg-element').forEach(el => {
|
|
436
|
-
new PerlinDOM({
|
|
437
|
-
element: el,
|
|
438
|
-
x: { min: -110, max: 110 },
|
|
439
|
-
y: { min: -110, max: 110 },
|
|
440
|
-
speed: 0.002, // Very slow movement
|
|
441
|
-
seed: Math.random() * 2000
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
// Example 5: Pause and Play with Mouse Events
|
|
446
|
-
const pausePlayAnimation = new PerlinDOM({
|
|
447
|
-
element: document.querySelector('.pause-play-element'),
|
|
448
|
-
x: { min: -80, max: 80 },
|
|
449
|
-
y: { min: -180, max: 80 },
|
|
450
|
-
speed: 0.01,
|
|
451
|
-
seed: 42,
|
|
452
|
-
lerpSpeed: 0.05 // Controls how smooth the transition is
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
// Pause on mouse leave
|
|
456
|
-
document.querySelector('.pause-play-container').addEventListener('mouseleave', () => {
|
|
457
|
-
pausePlayAnimation.pause();
|
|
458
|
-
document.querySelector('.status-indicator').textContent = 'Pausing...';
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
// Play on mouse enter
|
|
462
|
-
document.querySelector('.pause-play-container').addEventListener('mouseenter', () => {
|
|
463
|
-
pausePlayAnimation.play();
|
|
464
|
-
document.querySelector('.status-indicator').textContent = 'Playing...';
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
// Update lerpSpeed when slider changes
|
|
468
|
-
document.getElementById('lerp-speed').addEventListener('input', (e) => {
|
|
469
|
-
const newSpeed = parseFloat(e.target.value);
|
|
470
|
-
pausePlayAnimation.lerpSpeed = newSpeed;
|
|
471
|
-
document.querySelector('.lerp-value').textContent = newSpeed.toFixed(2);
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
// Example 6: Interactive Controls
|
|
475
|
-
let interactiveAnimation;
|
|
476
|
-
|
|
477
|
-
function initInteractiveAnimation() {
|
|
478
|
-
// Destroy previous instance if it exists
|
|
479
|
-
if (interactiveAnimation) {
|
|
480
|
-
interactiveAnimation.destroy();
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// Get current values
|
|
484
|
-
const xRange = parseInt(document.getElementById('x-range').value);
|
|
485
|
-
const yRange = parseInt(document.getElementById('y-range').value);
|
|
486
|
-
const speed = parseFloat(document.getElementById('speed').value);
|
|
487
|
-
const seed = parseInt(document.getElementById('seed').value);
|
|
488
|
-
|
|
489
|
-
// Create new instance
|
|
490
|
-
interactiveAnimation = new PerlinDOM({
|
|
491
|
-
element: document.querySelector('.interactive-element'),
|
|
492
|
-
x: { min: -xRange, max: xRange },
|
|
493
|
-
y: { min: -yRange, max: yRange },
|
|
494
|
-
speed: speed,
|
|
495
|
-
seed: seed
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
// Update displays
|
|
499
|
-
document.querySelector('#x-range + .value-display').textContent = `-${xRange} to ${xRange}`;
|
|
500
|
-
document.querySelector('#y-range + .value-display').textContent = `-${yRange} to ${yRange}`;
|
|
501
|
-
document.querySelector('#speed + .value-display').textContent = speed.toFixed(3);
|
|
502
|
-
document.querySelector('#seed + .value-display').textContent = seed;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// Initialize
|
|
506
|
-
initInteractiveAnimation();
|
|
507
|
-
|
|
508
|
-
// Add event listeners
|
|
509
|
-
document.getElementById('x-range').addEventListener('input', initInteractiveAnimation);
|
|
510
|
-
document.getElementById('y-range').addEventListener('input', initInteractiveAnimation);
|
|
511
|
-
document.getElementById('speed').addEventListener('input', initInteractiveAnimation);
|
|
512
|
-
document.getElementById('seed').addEventListener('input', initInteractiveAnimation);
|
|
513
|
-
</script>
|
|
514
|
-
</body>
|
|
515
|
-
</html>
|
package/src/js/PerlinSVG.js
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
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;
|