@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.
@@ -1,35 +1,53 @@
1
- var S = Object.defineProperty;
2
- var y = (r) => {
3
- throw TypeError(r);
1
+ var $ = Object.defineProperty;
2
+ var q = (c) => {
3
+ throw TypeError(c);
4
4
  };
5
- var $ = (r, t, i) => t in r ? S(r, t, { enumerable: !0, configurable: !0, writable: !0, value: i }) : r[t] = i;
6
- var M = (r, t, i) => $(r, typeof t != "symbol" ? t + "" : t, i), j = (r, t, i) => t.has(r) || y("Cannot " + i);
7
- var F = (r, t, i) => t.has(r) ? y("Cannot add the same private member more than once") : t instanceof WeakSet ? t.add(r) : t.set(r, i);
8
- var f = (r, t, i) => (j(r, t, "access private method"), i);
9
- var g, X, R, c, Y, B, d;
10
- const u = class u {
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: a = null,
15
- speed: n = 0.01,
16
- seed: o = 0,
14
+ y: h = null,
15
+ speed: r = 0.01,
16
+ seed: l = 0,
17
17
  lerpSpeed: s = 0.1
18
18
  }) {
19
- F(this, c);
20
- M(this, "animate", () => {
21
- if (this.lerpFactor !== this.targetLerpFactor && (this.lerpFactor = f(this, c, B).call(this, this.lerpFactor, this.targetLerpFactor, this.lerpSpeed), Math.abs(this.lerpFactor - this.targetLerpFactor) < 1e-3 && (this.lerpFactor = this.targetLerpFactor)), this.lerpFactor > 0) {
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 t = this.noise.perlin2(this.time, 0), i = this.noise.perlin2(this.time, 100), a = this.xRange ? f(this, c, d).call(this, t, -1, 1, this.xRange.min, this.xRange.max) : 0, n = this.yRange ? f(this, c, d).call(this, i, -1, 1, this.yRange.min, this.yRange.max) : 0;
24
- this.lastX = a, this.lastY = n, this.el && (this.el.style.left = `${this.initialX + a}px`, this.el.style.top = `${this.initialY + n}px`);
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
- this.el = t, this.xRange = i, this.yRange = a, this.speed = n, this.time = o, this.isPlaying = !0, this.lerpFactor = 1, this.lerpSpeed = s, this.targetLerpFactor = 1, this.lastX = 0, this.lastY = 0, t ? (this.initialX = t.offsetLeft, this.initialY = t.offsetTop) : (this.initialX = 0, this.initialY = 0), this.animate = this.animate.bind(this), this.init(o);
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 = f(this, c, Y).call(this, f(i = u, g, R).call(i, t)), this.isPlaying = !0, this.lerpFactor = 1, this.targetLerpFactor = 1, this.raf = requestAnimationFrame(this.animate), console.log("PerlinDOM initialized");
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
- g = new WeakSet(), X = function(t) {
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
- }, R = function(t = 0) {
50
- var n;
51
- const i = Array.from({ length: 256 }, (o, s) => s), a = f(n = u, g, X).call(n, t);
52
- for (let o = 255; o > 0; o--) {
53
- const s = Math.floor(a() * (o + 1));
54
- [i[o], i[s]] = [i[s], i[o]];
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
- }, c = new WeakSet(), Y = function(t) {
58
- const i = (s) => s * s * s * (s * (s * 6 - 15) + 10), a = (s, e, h) => e + s * (h - e), n = (s, e, h) => {
59
- const l = s & 15, p = l < 8 ? e : h, m = l < 4 ? h : l === 12 || l === 14 ? e : 0;
60
- return (l & 1 ? -p : p) + (l & 2 ? -m : m);
61
- }, o = (s, e) => s & 1 ? -e : e;
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 h = i(s);
68
- return a(h, o(t[e], s), o(t[e + 1], s - 1));
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 h = Math.floor(s) & 255, l = Math.floor(e) & 255;
95
+ const g = Math.floor(s) & 255, m = Math.floor(e) & 255;
73
96
  s -= Math.floor(s), e -= Math.floor(e);
74
- const p = i(s), m = i(e), A = t[h] + l, L = t[h + 1] + l, b = t[A], v = t[A + 1], q = t[L], D = t[L + 1];
75
- return a(
76
- m,
77
- a(
78
- p,
79
- n(t[b], s, e),
80
- n(t[q], s - 1, e)
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
- a(
83
- p,
84
- n(t[v], s, e - 1),
85
- n(t[D], s - 1, e - 1)
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
- }, B = function(t, i, a) {
91
- return t + a * (i - t);
92
- }, d = function(t, i, a, n, o) {
93
- return (t - i) * (o - n) / (a - i) + n;
94
- }, F(u, g);
95
- let P = u;
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
- P as default
120
+ E as default
98
121
  };
@@ -1 +1 @@
1
- (function(r,e){typeof exports=="object"&&typeof module<"u"?module.exports=e():typeof define=="function"&&define.amd?define(e):(r=typeof globalThis<"u"?globalThis:r||self,r.PerlinDOM=e())})(this,function(){"use strict";var T=Object.defineProperty;var M=r=>{throw TypeError(r)};var j=(r,e,l)=>e in r?T(r,e,{enumerable:!0,configurable:!0,writable:!0,value:l}):r[e]=l;var P=(r,e,l)=>j(r,typeof e!="symbol"?e+"":e,l),O=(r,e,l)=>e.has(r)||M("Cannot "+l);var d=(r,e,l)=>e.has(r)?M("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(r):e.set(r,l);var u=(r,e,l)=>(O(r,e,"access private method"),l);var e,X,R,c,Y,b,y;const g=class g{constructor({element:s,x:t=null,y:a=null,speed:o=.01,seed:h=0,lerpSpeed:i=.1}){d(this,c);P(this,"animate",()=>{if(this.lerpFactor!==this.targetLerpFactor&&(this.lerpFactor=u(this,c,b).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 s=this.noise.perlin2(this.time,0),t=this.noise.perlin2(this.time,100),a=this.xRange?u(this,c,y).call(this,s,-1,1,this.xRange.min,this.xRange.max):0,o=this.yRange?u(this,c,y).call(this,t,-1,1,this.yRange.min,this.yRange.max):0;this.lastX=a,this.lastY=o,this.el&&(this.el.style.left=`${this.initialX+a}px`,this.el.style.top=`${this.initialY+o}px`)}(this.lerpFactor>0||this.targetLerpFactor>0)&&(this.raf=requestAnimationFrame(this.animate))});this.el=s,this.xRange=t,this.yRange=a,this.speed=o,this.time=h,this.isPlaying=!0,this.lerpFactor=1,this.lerpSpeed=i,this.targetLerpFactor=1,this.lastX=0,this.lastY=0,s?(this.initialX=s.offsetLeft,this.initialY=s.offsetTop):(this.initialX=0,this.initialY=0),this.animate=this.animate.bind(this),this.init(h)}init(s){var t;this.noise=u(this,c,Y).call(this,u(t=g,e,R).call(t,s)),this.isPlaying=!0,this.lerpFactor=1,this.targetLerpFactor=1,this.raf=requestAnimationFrame(this.animate),console.log("PerlinDOM initialized")}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)}};e=new WeakSet,X=function(s){return function(){let t=s+=1831565813;return t=Math.imul(t^t>>>15,t|1),t^=t+Math.imul(t^t>>>7,t|61),((t^t>>>14)>>>0)/4294967296}},R=function(s=0){var o;const t=Array.from({length:256},(h,i)=>i),a=u(o=g,e,X).call(o,s);for(let h=255;h>0;h--){const i=Math.floor(a()*(h+1));[t[h],t[i]]=[t[i],t[h]]}return t.concat(t)},c=new WeakSet,Y=function(s){const t=i=>i*i*i*(i*(i*6-15)+10),a=(i,n,f)=>n+i*(f-n),o=(i,n,f)=>{const p=i&15,m=p<8?n:f,F=p<4?f:p===12||p===14?n:0;return(p&1?-m:m)+(p&2?-F:F)},h=(i,n)=>i&1?-n:n;return{perlin1(i){const n=Math.floor(i)&255;i-=Math.floor(i);const f=t(i);return a(f,h(s[n],i),h(s[n+1],i-1))},perlin2(i,n){const f=Math.floor(i)&255,p=Math.floor(n)&255;i-=Math.floor(i),n-=Math.floor(n);const m=t(i),F=t(n),A=s[f]+p,L=s[f+1]+p,B=s[A],v=s[A+1],D=s[L],q=s[L+1];return a(F,a(m,o(s[B],i,n),o(s[D],i-1,n)),a(m,o(s[v],i,n-1),o(s[q],i-1,n-1)))}}},b=function(s,t,a){return s+a*(t-s)},y=function(s,t,a,o,h){return(s-t)*(h-o)/(a-t)+o},d(g,e);let r=g;return r});
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});
@@ -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.1",
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 transform
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
+ &nbsp;&nbsp;element: document.querySelector('.pixel-element'),<br>
653
+ &nbsp;&nbsp;x: { min: -50, max: 50 },<br>
654
+ &nbsp;&nbsp;y: { min: -50, max: 50 },<br>
655
+ &nbsp;&nbsp;speed: 0.01,<br>
656
+ &nbsp;&nbsp;seed: 123<br>
657
+ });<br><br>
658
+
659
+ // Percentage-based animation (responsive)<br>
660
+ const percentageAnimation = new PerlinDOM({<br>
661
+ &nbsp;&nbsp;element: document.querySelector('.percentage-element'),<br>
662
+ &nbsp;&nbsp;x: { min: '-20%', max: '20%' },<br>
663
+ &nbsp;&nbsp;y: { min: '-20%', max: '20%' },<br>
664
+ &nbsp;&nbsp;speed: 0.01,<br>
665
+ &nbsp;&nbsp;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
+ &nbsp;&nbsp;element: document.querySelector('.responsive-element'),<br>
688
+ &nbsp;&nbsp;x: { min: '-25%', max: '25%' },<br>
689
+ &nbsp;&nbsp;y: { min: '-25%', max: '25%' },<br>
690
+ &nbsp;&nbsp;speed: 0.008,<br>
691
+ &nbsp;&nbsp;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
+ &nbsp;&nbsp;element: document.querySelector('.horizontal-percent-element'),<br>
707
+ &nbsp;&nbsp;x: { min: '-30%', max: '30%' },<br>
708
+ &nbsp;&nbsp;y: { min: -40, max: 40 },<br>
709
+ &nbsp;&nbsp;speed: 0.01,<br>
710
+ &nbsp;&nbsp;seed: 789<br>
711
+ });<br><br>
712
+
713
+ // Horizontal pixel, vertical percentage<br>
714
+ const verticalPercentAnimation = new PerlinDOM({<br>
715
+ &nbsp;&nbsp;element: document.querySelector('.vertical-percent-element'),<br>
716
+ &nbsp;&nbsp;x: { min: -80, max: 80 },<br>
717
+ &nbsp;&nbsp;y: { min: '-20%', max: '20%' },<br>
718
+ &nbsp;&nbsp;speed: 0.01,<br>
719
+ &nbsp;&nbsp;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>
@@ -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
- const valX = this.xRange
182
- ? this.#map(noiseX, -1, 1, this.xRange.min, this.xRange.max)
183
- : 0;
184
-
185
- const valY = this.yRange
186
- ? this.#map(noiseY, -1, 1, this.yRange.min, this.yRange.max)
187
- : 0;
188
-
189
- // Store current values for interpolation
190
- this.lastX = valX;
191
- this.lastY = valY;
192
-
193
- // Update position using left/top instead of transform
194
- // Solo actualizar el elemento si existe
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
- &nbsp;&nbsp;element: document.querySelector('.basic-element'),<br>
232
- &nbsp;&nbsp;x: { min: -50, max: 50 },<br>
233
- &nbsp;&nbsp;y: { min: -50, max: 50 },<br>
234
- &nbsp;&nbsp;speed: 0.01,<br>
235
- &nbsp;&nbsp;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
- &nbsp;&nbsp;element: document.querySelector('.horizontal-element'),<br>
247
- &nbsp;&nbsp;x: { min: -100, max: 100 },<br>
248
- &nbsp;&nbsp;y: null, // No vertical movement<br>
249
- &nbsp;&nbsp;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
- &nbsp;&nbsp;new PerlinDOM({<br>
261
- &nbsp;&nbsp;&nbsp;&nbsp;element: el,<br>
262
- &nbsp;&nbsp;&nbsp;&nbsp;x: { min: -100, max: 100 },<br>
263
- &nbsp;&nbsp;&nbsp;&nbsp;y: { min: -100, max: 100 },<br>
264
- &nbsp;&nbsp;&nbsp;&nbsp;speed: 0.01,<br>
265
- &nbsp;&nbsp;&nbsp;&nbsp;seed: index * 100 // Different seed for each element<br>
266
- &nbsp;&nbsp;});<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
- &nbsp;&nbsp;new PerlinDOM({<br>
282
- &nbsp;&nbsp;&nbsp;&nbsp;element: el,<br>
283
- &nbsp;&nbsp;&nbsp;&nbsp;x: { min: -10, max: 10 },<br>
284
- &nbsp;&nbsp;&nbsp;&nbsp;y: { min: -10, max: 10 },<br>
285
- &nbsp;&nbsp;&nbsp;&nbsp;speed: 0.002, // Very slow movement<br>
286
- &nbsp;&nbsp;&nbsp;&nbsp;seed: Math.random() * 2000<br>
287
- &nbsp;&nbsp;});<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
- &nbsp;&nbsp;element: document.querySelector('.pause-play-element'),<br>
305
- &nbsp;&nbsp;x: { min: -80, max: 80 },<br>
306
- &nbsp;&nbsp;y: { min: -80, max: 80 },<br>
307
- &nbsp;&nbsp;speed: 0.01,<br>
308
- &nbsp;&nbsp;seed: 42,<br>
309
- &nbsp;&nbsp;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
- &nbsp;&nbsp;pausePlayAnimation.pause();<br>
315
- &nbsp;&nbsp;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
- &nbsp;&nbsp;pausePlayAnimation.play();<br>
321
- &nbsp;&nbsp;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
- &nbsp;&nbsp;const newSpeed = parseFloat(e.target.value);<br>
327
- &nbsp;&nbsp;pausePlayAnimation.lerpSpeed = newSpeed;<br>
328
- &nbsp;&nbsp;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
- &nbsp;&nbsp;element: document.querySelector('.interactive-element'),<br>
352
- &nbsp;&nbsp;x: { min: -50, max: 50 },<br>
353
- &nbsp;&nbsp;y: { min: -50, max: 50 },<br>
354
- &nbsp;&nbsp;speed: 0.01,<br>
355
- &nbsp;&nbsp;seed: 0<br>
356
- });<br><br>
357
-
358
- // Update settings based on user input<br>
359
- speedControl.addEventListener('input', () => {<br>
360
- &nbsp;&nbsp;// Stop current animation<br>
361
- &nbsp;&nbsp;interactiveAnimation.destroy();<br>
362
- &nbsp;&nbsp;<br>
363
- &nbsp;&nbsp;// Reinitialize with new settings<br>
364
- &nbsp;&nbsp;interactiveAnimation.speed = parseFloat(speedControl.value);<br>
365
- &nbsp;&nbsp;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>
@@ -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;