@andresclua/perlindom 0.0.6 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +426 -0
- package/dist/perlindom.es.js +212 -0
- package/dist/perlindom.umd.js +1 -0
- package/package.json +14 -6
- package/dist/PerlinDOM.es.js +0 -138
- package/dist/PerlinDOM.umd.js +0 -1
- package/docs/examples.html +0 -391
- package/public/vite.svg +0 -1
- package/readme.md +0 -353
- package/src/index.html +0 -819
- package/src/js/PerlinDom.js +0 -318
- package/src/js/animated-button.js +0 -40
- package/src/js/basic-examples.js +0 -43
- package/src/js/index.js +0 -13
- package/src/js/interactive-controls.js +0 -43
- package/src/js/pause-play.js +0 -33
- package/src/js/triangle-animation.js +0 -48
- package/src/scss/style.scss +0 -20
- package/vite.config.js +0 -36
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.1] - 2026-11-07
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- README: links to the website at the top; file renamed to `README.md`
|
|
14
|
+
|
|
15
|
+
## [0.1.0] - 2026-11-07
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **`amplitude` option and property**: scales movement strength, clamped to [0, 1] (1 = free drift, 0 = pinned to the target or the base position); non-number values are treated as 1, no string/null coercion
|
|
19
|
+
- **`reset()` method**: rewinds the noise path to the seed and returns the element to its own transform, keeping play/pause, `amplitude` and `target`; a no-op after `destroy()`
|
|
20
|
+
- **`legacyNoise` option** (default `false`): `true` restores the pre-0.0.7 noise rows
|
|
21
|
+
- **`pauseOffscreen` option** (default `true`): `false` keeps an instance stepping while offscreen
|
|
22
|
+
- **`target` option and `setTarget`**: `target` is a `{x, y}` offset in px from the element's base position that `amplitude` pulls toward (amplitude 0 pins the element at the target); `setTarget` copies the object and turns non-finite coordinates into 0
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- **Noise sampling (behavior change)**: the two axes are now sampled at rows 0.5 and 100.5 instead of 0 and 100, where Perlin noise is flat on the lattice. Movement now reaches roughly ±1 of the range instead of ±0.5, and no longer sits nearly still at the start for some seeds (seed 42 used to take ~3.7 s). Pass `legacyNoise: true` to keep the previous movement exactly
|
|
26
|
+
- **Shared loop**: all instances now share a single `requestAnimationFrame` loop
|
|
27
|
+
- **Offscreen pause**: instances pause while their element's parent is outside the viewport (the parent, so an element's own drift cannot freeze it); changing `amplitude` or `target` still renders while paused
|
|
28
|
+
- **Build output**: `dist` files are now `perlindom.es.js` and `perlindom.umd.js` (lowercase), matching `package.json` and `vite.config.js`; the entry path casing was fixed for case-sensitive filesystems
|
|
29
|
+
|
|
30
|
+
### Removed
|
|
31
|
+
- Internal instance fields `animate` and `raf` (undocumented; replaced by the shared loop)
|
|
32
|
+
|
|
8
33
|
## [0.0.6] - 2025-02-09
|
|
9
34
|
|
|
10
35
|
## Changed
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrés Clúa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
**[perlindom.js.org](https://perlindom.js.org)** · [API](https://perlindom.js.org/api.html) · [Examples](https://perlindom.js.org/examples.html) · [Showcase](https://perlindom.js.org/showcase.html)
|
|
2
|
+
|
|
3
|
+
# PerlinDOM
|
|
4
|
+
|
|
5
|
+
[](https://badge.fury.io/js/@andresclua%2Fperlindom)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
|
|
8
|
+
|
|
9
|
+
A lightweight JavaScript library for creating smooth, organic animations on DOM elements using Perlin noise. Create natural, fluid movements that bring your web interfaces to life.
|
|
10
|
+
|
|
11
|
+
## 📋 Table of Contents
|
|
12
|
+
|
|
13
|
+
- [Features](#-features)
|
|
14
|
+
- [Installation](#-installation)
|
|
15
|
+
- [Quick Start](#-quick-start)
|
|
16
|
+
- [Configuration](#-configuration)
|
|
17
|
+
- [Amplitude and target](#-amplitude-and-target)
|
|
18
|
+
- [Use with GSAP + ScrollTrigger](#-use-with-gsap--scrolltrigger)
|
|
19
|
+
- [Examples](#-examples)
|
|
20
|
+
- [Best Practices](#-best-practices)
|
|
21
|
+
- [API Reference](#-api-reference)
|
|
22
|
+
- [Troubleshooting](#-troubleshooting)
|
|
23
|
+
- [Browser Compatibility](#-browser-compatibility)
|
|
24
|
+
- [Live site](#-live-site)
|
|
25
|
+
- [Contributing](#-contributing)
|
|
26
|
+
- [Sponsor](#-sponsor)
|
|
27
|
+
- [Changelog](#-changelog)
|
|
28
|
+
- [License](#-license)
|
|
29
|
+
|
|
30
|
+
## ✨ Features
|
|
31
|
+
|
|
32
|
+
- **🌊 Organic Animations**: Natural movements using Perlin noise that avoid mechanical rigidity
|
|
33
|
+
- **📱 Responsive Design**: Support for percentage-based values that adapt to container size
|
|
34
|
+
- **🎯 Smart Positioning**: Automatically works from element's current position (CSS or inline styles)
|
|
35
|
+
- **⚡ High Performance**: Uses `transform` and one shared `requestAnimationFrame` loop for all instances; instances scrolled out of the viewport pause automatically
|
|
36
|
+
- **🧲 Amplitude & Target**: Scale the drift from 0 to 1 and pin elements to a target position, ideal for scroll-driven effects
|
|
37
|
+
- **🎛️ Full Control**: Play, pause, and destroy animations with smooth transitions
|
|
38
|
+
- **📦 Lightweight**: Minimal footprint with zero dependencies
|
|
39
|
+
- **🔧 Easy Integration**: Simple API that works with any DOM element
|
|
40
|
+
- **🎨 Flexible**: Mix pixel and percentage values, animate on single or both axes
|
|
41
|
+
|
|
42
|
+
## 📦 Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install @andresclua/perlindom
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Or include directly in your HTML:
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<script type="module">
|
|
52
|
+
import PerlinDOM from 'https://unpkg.com/@andresclua/perlindom/dist/perlindom.es.js';
|
|
53
|
+
</script>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 🚀 Quick Start
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
import PerlinDOM from '@andresclua/perlindom';
|
|
60
|
+
|
|
61
|
+
// Basic animation - element will animate from its current position
|
|
62
|
+
const animation = new PerlinDOM({
|
|
63
|
+
element: document.querySelector('.my-element'),
|
|
64
|
+
x: { min: -50, max: 50 }, // Move ±50px horizontally
|
|
65
|
+
y: { min: -30, max: 30 }, // Move ±30px vertically
|
|
66
|
+
speed: 0.01, // Animation speed
|
|
67
|
+
seed: 123 // Reproducible pattern
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Control the animation
|
|
71
|
+
animation.pause(); // Smooth pause
|
|
72
|
+
animation.play(); // Resume
|
|
73
|
+
animation.reset(); // Rewind to the start (same path again)
|
|
74
|
+
animation.destroy(); // Clean up
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Instances start playing as soon as they are created. PerlinDOM sets `position: relative` itself if the element is `static`, and animates from wherever the element is laid out.
|
|
78
|
+
|
|
79
|
+
## ⚙️ Configuration
|
|
80
|
+
|
|
81
|
+
| Option | Type | Default | Description |
|
|
82
|
+
|--------|------|---------|-------------|
|
|
83
|
+
| `element` | `HTMLElement \| null` | `undefined` | The DOM element to animate. Optional: with `null` only `lastX` / `lastY` are computed |
|
|
84
|
+
| `x` | `Object \| null` | `null` | Horizontal movement: `{min: number, max: number}` |
|
|
85
|
+
| `y` | `Object \| null` | `null` | Vertical movement: `{min: number, max: number}` |
|
|
86
|
+
| `speed` | `number` | `0.01` | Animation speed (lower = slower) |
|
|
87
|
+
| `seed` | `number` | `0` | Seed for reproducible patterns |
|
|
88
|
+
| `lerpSpeed` | `number` | `0.1` | Transition smoothness for pause/play |
|
|
89
|
+
| `preserveBaseTranslation` | `boolean` | `false` | Keep the element's CSS `translate` (e.g. `translate(-50%, -50%)`); by default it is dropped and only scale/rotate/skew are kept |
|
|
90
|
+
| `amplitude` | `number` | `1` | Strength of the drift, clamped to `[0, 1]`; non-numbers become `1`. `1` = free drift, `0` = pinned to `target` (or the base position) |
|
|
91
|
+
| `target` | `{x, y} \| null` | `null` | Offset in px from the element's base position that `amplitude` pulls toward. Coordinates that are not finite numbers become `0` |
|
|
92
|
+
| `legacyNoise` | `boolean` | `false` | `true` keeps the old noise rows (0 / 100) for movement identical to 0.0.6 and earlier |
|
|
93
|
+
| `pauseOffscreen` | `boolean` | `true` | Skip frames while the element's parent is outside the viewport. `false` keeps it running everywhere |
|
|
94
|
+
|
|
95
|
+
### Value Types
|
|
96
|
+
|
|
97
|
+
- **Pixels**: `{ min: -50, max: 50 }` - Fixed pixel movement
|
|
98
|
+
- **Percentages**: `{ min: '-10%', max: '10%' }` - Relative to the parent's `clientWidth` / `clientHeight` (padding included), re-read every frame. They need an `element` (with `element: null` they produce `NaN`)
|
|
99
|
+
- **Mixed**: `x: { min: '-5%', max: '5%' }, y: { min: -20, max: 20 }`
|
|
100
|
+
- **Single Axis**: Set unused axis to `null`
|
|
101
|
+
|
|
102
|
+
## 🎚️ Amplitude and Target
|
|
103
|
+
|
|
104
|
+
`amplitude` scales how far the noise moves the element. `target` is where it settles as `amplitude` goes to `0`: an `{ x, y }` offset in px from the element's base position (the object is copied). Without a `target` the element settles on its base position.
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
const dot = new PerlinDOM({
|
|
108
|
+
element: document.querySelector('.dot'),
|
|
109
|
+
x: { min: -120, max: 120 },
|
|
110
|
+
y: { min: -120, max: 120 },
|
|
111
|
+
target: { x: 40, y: -20 }
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
dot.amplitude = 0.5; // half the drift, pulled toward the target
|
|
115
|
+
dot.amplitude = 0; // pinned at { x: 40, y: -20 }
|
|
116
|
+
dot.setTarget({ x: 0, y: 0 }); // move the target; setTarget(null) removes it
|
|
117
|
+
dot.amplitude = 1; // free drift again
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 🎬 Use with GSAP + ScrollTrigger
|
|
121
|
+
|
|
122
|
+
PerlinDOM owns the element's `transform`, so GSAP must not write `transform` (`x`, `y`, `scale`, ...) on the same element. Tween `amplitude` instead; to move an element with GSAP and drift it too, put PerlinDOM on a wrapper or a child.
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
import gsap from 'gsap';
|
|
126
|
+
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
|
127
|
+
import PerlinDOM from '@andresclua/perlindom';
|
|
128
|
+
|
|
129
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
130
|
+
|
|
131
|
+
const shape = [{ x: 0, y: -80 }, { x: 70, y: 40 }, { x: -70, y: 40 }]; // one { x, y } per .dot
|
|
132
|
+
|
|
133
|
+
const instances = [...document.querySelectorAll('.dot')].map((el, i) =>
|
|
134
|
+
new PerlinDOM({
|
|
135
|
+
element: el,
|
|
136
|
+
x: { min: -120, max: 120 },
|
|
137
|
+
y: { min: -120, max: 120 },
|
|
138
|
+
seed: i * 7919,
|
|
139
|
+
target: shape[i], // { x, y } offsets that draw your shape
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const state = { amplitude: 1 };
|
|
144
|
+
gsap.to(state, {
|
|
145
|
+
amplitude: 0,
|
|
146
|
+
ease: 'none',
|
|
147
|
+
scrollTrigger: { trigger: '.scene', start: 'top top', end: '+=1000', pin: true, scrub: 0.8 },
|
|
148
|
+
onUpdate: () => instances.forEach(inst => { inst.amplitude = state.amplitude; }),
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 📚 Examples
|
|
153
|
+
|
|
154
|
+
### 1. Basic Element Animation
|
|
155
|
+
|
|
156
|
+
```html
|
|
157
|
+
<div class="floating-card">Content</div>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```css
|
|
161
|
+
.floating-card {
|
|
162
|
+
position: relative;
|
|
163
|
+
left: 50%;
|
|
164
|
+
top: 50%;
|
|
165
|
+
transform: translate(-50%, -50%); /* centring translate */
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
new PerlinDOM({
|
|
171
|
+
element: document.querySelector('.floating-card'),
|
|
172
|
+
x: { min: -20, max: 20 },
|
|
173
|
+
y: { min: -15, max: 15 },
|
|
174
|
+
speed: 0.008,
|
|
175
|
+
seed: 42,
|
|
176
|
+
preserveBaseTranslation: true // keep the centring translate
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 2. Responsive Background Elements
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
// Elements adapt to container size changes
|
|
184
|
+
document.querySelectorAll('.bg-particle').forEach((el, index) => {
|
|
185
|
+
new PerlinDOM({
|
|
186
|
+
element: el,
|
|
187
|
+
x: { min: '-15%', max: '15%' }, // 15% of parent width
|
|
188
|
+
y: { min: '-10%', max: '10%' }, // 10% of parent height
|
|
189
|
+
speed: 0.003,
|
|
190
|
+
seed: index * 1000
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 3. Interactive Hover Effects
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
const button = document.querySelector('.animated-button');
|
|
199
|
+
let animation;
|
|
200
|
+
|
|
201
|
+
button.addEventListener('mouseenter', () => {
|
|
202
|
+
if (!animation) {
|
|
203
|
+
animation = new PerlinDOM({
|
|
204
|
+
element: button.querySelector('.effect'),
|
|
205
|
+
x: { min: -5, max: 5 },
|
|
206
|
+
y: { min: -5, max: 5 },
|
|
207
|
+
speed: 0.02,
|
|
208
|
+
lerpSpeed: 0.15
|
|
209
|
+
});
|
|
210
|
+
} else {
|
|
211
|
+
animation.play();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
button.addEventListener('mouseleave', () => {
|
|
216
|
+
animation?.pause();
|
|
217
|
+
});
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 4. Multiple Elements with Synchronized Patterns
|
|
221
|
+
|
|
222
|
+
```javascript
|
|
223
|
+
// Same seed = synchronized movement
|
|
224
|
+
document.querySelectorAll('.sync-element').forEach(el => {
|
|
225
|
+
new PerlinDOM({
|
|
226
|
+
element: el,
|
|
227
|
+
x: { min: -30, max: 30 },
|
|
228
|
+
y: { min: -30, max: 30 },
|
|
229
|
+
speed: 0.01,
|
|
230
|
+
seed: 12345 // Same seed for all elements
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 5. Custom Animation Patterns
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
// Horizontal-only movement for sliding effects
|
|
239
|
+
new PerlinDOM({
|
|
240
|
+
element: document.querySelector('.slider'),
|
|
241
|
+
x: { min: -100, max: 100 },
|
|
242
|
+
y: null, // No vertical movement
|
|
243
|
+
speed: 0.005
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Vertical-only for floating effects
|
|
247
|
+
new PerlinDOM({
|
|
248
|
+
element: document.querySelector('.floater'),
|
|
249
|
+
x: null, // No horizontal movement
|
|
250
|
+
y: { min: -40, max: 40 },
|
|
251
|
+
speed: 0.008
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### 6. Advanced: SVG Path Animation
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
// Animate SVG elements or paths
|
|
259
|
+
const svgElement = document.querySelector('#animated-path');
|
|
260
|
+
const pathAnimation = new PerlinDOM({
|
|
261
|
+
element: null, // No direct element
|
|
262
|
+
x: { min: -20, max: 20 },
|
|
263
|
+
y: { min: -15, max: 15 },
|
|
264
|
+
speed: 0.01,
|
|
265
|
+
seed: 999
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
function updatePath() {
|
|
269
|
+
const offsetX = pathAnimation.lastX;
|
|
270
|
+
const offsetY = pathAnimation.lastY;
|
|
271
|
+
|
|
272
|
+
// Update SVG path or transform
|
|
273
|
+
svgElement.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
|
|
274
|
+
|
|
275
|
+
requestAnimationFrame(updatePath);
|
|
276
|
+
}
|
|
277
|
+
updatePath();
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## 💡 Best Practices
|
|
281
|
+
|
|
282
|
+
### ✅ Do
|
|
283
|
+
|
|
284
|
+
- **Position with CSS, not with `translate`**: Use `left`/`top`, margins, grid or flex. A CSS `translate` is dropped unless you pass `preserveBaseTranslation: true`
|
|
285
|
+
- **One instance per element**: Two instances on the same element overwrite each other's transform
|
|
286
|
+
- **Use percentage values for responsive designs**: Great for mobile-friendly animations
|
|
287
|
+
- **Choose appropriate speeds**: Start with `0.01` and adjust based on desired effect
|
|
288
|
+
- **Clean up animations**: Call `destroy()` when removing elements from DOM
|
|
289
|
+
|
|
290
|
+
### ❌ Don't
|
|
291
|
+
|
|
292
|
+
- **Don't tween `transform` with another library on the same element**: PerlinDOM writes it every frame (see the GSAP recipe above)
|
|
293
|
+
- **Don't forget to destroy**: A live instance keeps a reference to its element, so call `destroy()` before removing it
|
|
294
|
+
- **Don't use extreme speeds**: Above about `0.05` the motion turns jittery
|
|
295
|
+
- **Don't forget reduced motion**: PerlinDOM does not check `prefers-reduced-motion`, so skip creating instances when it is set
|
|
296
|
+
|
|
297
|
+
### 🎯 Performance Tips
|
|
298
|
+
|
|
299
|
+
```javascript
|
|
300
|
+
// Good: Many instances are cheap, they all share one requestAnimationFrame loop
|
|
301
|
+
// and pause on their own while scrolled out of view
|
|
302
|
+
|
|
303
|
+
// Good: Use percentage values for responsive design
|
|
304
|
+
x: { min: '-5%', max: '5%' }
|
|
305
|
+
|
|
306
|
+
// Good: Appropriate speeds for smooth animation
|
|
307
|
+
speed: 0.01 // Smooth and natural
|
|
308
|
+
|
|
309
|
+
// Good: Clean up when done
|
|
310
|
+
animation.destroy();
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## 📖 API Reference
|
|
314
|
+
|
|
315
|
+
### Constructor
|
|
316
|
+
|
|
317
|
+
```javascript
|
|
318
|
+
new PerlinDOM(options) // options: see Configuration
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Methods
|
|
322
|
+
|
|
323
|
+
| Method | Description | Returns |
|
|
324
|
+
|--------|-------------|---------|
|
|
325
|
+
| `play()` | Resume after `pause()` (instances start playing on construction) | `void` |
|
|
326
|
+
| `pause()` | Smoothly pause animation | `void` |
|
|
327
|
+
| `reset()` | Back to the starting state: the noise path rewinds to the seed and the element returns to its own transform. Keeps play/pause, `amplitude` and `target`; does nothing after `destroy()` | `void` |
|
|
328
|
+
| `destroy()` | Stop animation, restore the original inline `transform` and clean up | `void` |
|
|
329
|
+
| `init(seed?)` | Rebuild the noise from `seed` (without an argument: seed `0`, not the constructed seed; `time` is not reset) and restart playback, also after `destroy()` | `void` |
|
|
330
|
+
| `setTarget({x, y} \| null)` | Set the target offset in px; non-finite coordinates become `0`, the object is copied, `null` removes it | `void` |
|
|
331
|
+
| `refreshBaseTransform(preserveTranslation?)` | Re-read the element's CSS transform after classes or styles changed | `void` |
|
|
332
|
+
|
|
333
|
+
### Properties
|
|
334
|
+
|
|
335
|
+
| Property | Type | Description |
|
|
336
|
+
|----------|------|-------------|
|
|
337
|
+
| `lastX` | `number` | Current X offset value |
|
|
338
|
+
| `lastY` | `number` | Current Y offset value |
|
|
339
|
+
| `isPlaying` | `boolean` | Current animation state |
|
|
340
|
+
| `speed` | `number` | Animation speed (writable) |
|
|
341
|
+
| `lerpSpeed` | `number` | Transition speed (writable) |
|
|
342
|
+
| `amplitude` | `number` | Drift strength, clamped to `[0, 1]`, non-numbers reset to `1` (writable) |
|
|
343
|
+
| `target` | `{x, y} \| null` | Current target offset (set it with `setTarget()`) |
|
|
344
|
+
| `xRange` / `yRange` | `Object \| null` | Movement ranges (writable; whether percentages are used is fixed at construction) |
|
|
345
|
+
|
|
346
|
+
## 🔧 Troubleshooting
|
|
347
|
+
|
|
348
|
+
### Common Issues
|
|
349
|
+
|
|
350
|
+
**❓ Animation not visible**
|
|
351
|
+
- Set at least one of `x` / `y`
|
|
352
|
+
- The element must not be `display: inline` (e.g. a plain `<span>`): transforms do not apply to inline boxes. Use `inline-block` or `block`
|
|
353
|
+
- Static positioning is not the problem: PerlinDOM sets `position: relative` itself
|
|
354
|
+
|
|
355
|
+
**❓ Element jumps on initialization**
|
|
356
|
+
- Usually a CSS `translate`, such as `translate(-50%, -50%)` for centring, which PerlinDOM drops by default
|
|
357
|
+
- Pass `preserveBaseTranslation: true`, or centre with `left: calc(50% - half)` or margins
|
|
358
|
+
|
|
359
|
+
**❓ Animation too fast/slow**
|
|
360
|
+
```javascript
|
|
361
|
+
// Adjust speed value
|
|
362
|
+
speed: 0.001 // Very slow
|
|
363
|
+
speed: 0.01 // Normal
|
|
364
|
+
speed: 0.1 // Fast
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**❓ Percentage values not working**
|
|
368
|
+
- Ensure parent container has defined dimensions
|
|
369
|
+
- Percentages use the parent's `clientWidth` / `clientHeight`, and need an `element`
|
|
370
|
+
|
|
371
|
+
**❓ Element stops moving while off screen**
|
|
372
|
+
- Intended: instances pause while their element's parent is outside the viewport and resume when it returns (needs `IntersectionObserver`; without it everything keeps running). The parent is watched, not the element, so an element's own drift never pauses it. Changing `amplitude` or calling `setTarget()` still renders while paused. Pass `pauseOffscreen: false` to never pause
|
|
373
|
+
|
|
374
|
+
**❓ Memory leaks**
|
|
375
|
+
```javascript
|
|
376
|
+
// Always clean up when removing elements
|
|
377
|
+
animation.destroy();
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Browser DevTools
|
|
381
|
+
|
|
382
|
+
Monitor performance in DevTools:
|
|
383
|
+
- Check for smooth 60fps in Performance tab
|
|
384
|
+
- Verify transform properties are being animated (not layout properties)
|
|
385
|
+
|
|
386
|
+
## 🌐 Browser Compatibility
|
|
387
|
+
|
|
388
|
+
The npm build (`dist/`) targets browsers with native ES modules and ES2020: **Chrome 87+, Edge 88+, Firefox 78+, Safari 14+**. Importing the source file directly needs private class methods and `??=`: Chrome 85+, Edge 85+, Firefox 90+, Safari 15+.
|
|
389
|
+
|
|
390
|
+
- `requestAnimationFrame`, `getComputedStyle` and CSS transforms are required
|
|
391
|
+
- `DOMMatrixReadOnly` separates the CSS translate from scale/rotate/skew; without it the computed transform is used as it is
|
|
392
|
+
- `IntersectionObserver` powers the offscreen pause; without it everything keeps running
|
|
393
|
+
|
|
394
|
+
## 🔗 Live site
|
|
395
|
+
|
|
396
|
+
Live site: **https://perlindom.js.org** · [API reference](https://perlindom.js.org/api.html) · [Examples](https://perlindom.js.org/examples.html) · [Showcase](https://perlindom.js.org/showcase.html) (add yours with a PR to `src/showcase.json`)
|
|
397
|
+
|
|
398
|
+
## 🤝 Contributing
|
|
399
|
+
|
|
400
|
+
Bug reports, fixes and showcase entries are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) for setup, tests and how to add your project to the [showcase](https://perlindom.js.org/showcase.html).
|
|
401
|
+
|
|
402
|
+
## 💜 Sponsor
|
|
403
|
+
|
|
404
|
+
PerlinDOM is free and MIT licensed. If it saves you time, you can support its development through [GitHub Sponsors](https://github.com/sponsors/andresclua).
|
|
405
|
+
|
|
406
|
+
## 📝 Changelog
|
|
407
|
+
|
|
408
|
+
See [CHANGELOG.md](./CHANGELOG.md) for detailed version history.
|
|
409
|
+
|
|
410
|
+
### Recent Updates
|
|
411
|
+
|
|
412
|
+
- **v0.0.6**: Enhanced PerlinDOM to preserve design transforms
|
|
413
|
+
- **v0.0.4**: Package now public on npm, enhanced module exports configuration
|
|
414
|
+
- **v0.0.3**: Fixed positioning conflicts, improved automatic position detection
|
|
415
|
+
- **v0.0.2**: Added percentage support and responsive animations
|
|
416
|
+
- **v0.0.1**: Initial release with core Perlin noise animations
|
|
417
|
+
|
|
418
|
+
## 📄 License
|
|
419
|
+
|
|
420
|
+
MIT License - see [LICENSE](./LICENSE) file for details.
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
**Made with ❤️ by [Andres Clua](https://github.com/andresclua)**
|
|
425
|
+
|
|
426
|
+
*PerlinDOM - Bringing organic motion to the web*
|