lenis 1.3.19-dev.0 → 1.3.20-dev.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lenis.mjs CHANGED
@@ -1,1128 +1,1015 @@
1
- // package.json
2
- var version = "1.3.19-dev.0";
3
-
4
- // packages/core/src/maths.ts
1
+ //#region package.json
2
+ var version = "1.3.20-dev.0";
3
+ //#endregion
4
+ //#region packages/core/src/maths.ts
5
+ /**
6
+ * Clamp a value between a minimum and maximum value
7
+ *
8
+ * @param min Minimum value
9
+ * @param input Value to clamp
10
+ * @param max Maximum value
11
+ * @returns Clamped value
12
+ */
5
13
  function clamp(min, input, max) {
6
- return Math.max(min, Math.min(input, max));
14
+ return Math.max(min, Math.min(input, max));
7
15
  }
16
+ /**
17
+ * Linearly interpolate between two values using an amount (0 <= t <= 1)
18
+ *
19
+ * @param x First value
20
+ * @param y Second value
21
+ * @param t Amount to interpolate (0 <= t <= 1)
22
+ * @returns Interpolated value
23
+ */
8
24
  function lerp(x, y, t) {
9
- return (1 - t) * x + t * y;
25
+ return (1 - t) * x + t * y;
10
26
  }
27
+ /**
28
+ * Damp a value over time using a damping factor
29
+ * {@link http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/}
30
+ *
31
+ * @param x Initial value
32
+ * @param y Target value
33
+ * @param lambda Damping factor
34
+ * @param dt Time elapsed since the last update
35
+ * @returns Damped value
36
+ */
11
37
  function damp(x, y, lambda, deltaTime) {
12
- return lerp(x, y, 1 - Math.exp(-lambda * deltaTime));
38
+ return lerp(x, y, 1 - Math.exp(-lambda * deltaTime));
13
39
  }
40
+ /**
41
+ * Calculate the modulo of the dividend and divisor while keeping the result within the same sign as the divisor
42
+ * {@link https://anguscroll.com/just/just-modulo}
43
+ *
44
+ * @param n Dividend
45
+ * @param d Divisor
46
+ * @returns Modulo
47
+ */
14
48
  function modulo(n, d) {
15
- return (n % d + d) % d;
49
+ return (n % d + d) % d;
16
50
  }
17
-
18
- // packages/core/src/animate.ts
51
+ //#endregion
52
+ //#region packages/core/src/animate.ts
53
+ /**
54
+ * Animate class to handle value animations with lerping or easing
55
+ *
56
+ * @example
57
+ * const animate = new Animate()
58
+ * animate.fromTo(0, 100, { duration: 1, easing: (t) => t })
59
+ * animate.advance(0.5) // 50
60
+ */
19
61
  var Animate = class {
20
- isRunning = false;
21
- value = 0;
22
- from = 0;
23
- to = 0;
24
- currentTime = 0;
25
- // These are instanciated in the fromTo method
26
- lerp;
27
- duration;
28
- easing;
29
- onUpdate;
30
- /**
31
- * Advance the animation by the given delta time
32
- *
33
- * @param deltaTime - The time in seconds to advance the animation
34
- */
35
- advance(deltaTime) {
36
- if (!this.isRunning) return;
37
- let completed = false;
38
- if (this.duration && this.easing) {
39
- this.currentTime += deltaTime;
40
- const linearProgress = clamp(0, this.currentTime / this.duration, 1);
41
- completed = linearProgress >= 1;
42
- const easedProgress = completed ? 1 : this.easing(linearProgress);
43
- this.value = this.from + (this.to - this.from) * easedProgress;
44
- } else if (this.lerp) {
45
- this.value = damp(this.value, this.to, this.lerp * 60, deltaTime);
46
- if (Math.round(this.value) === this.to) {
47
- this.value = this.to;
48
- completed = true;
49
- }
50
- } else {
51
- this.value = this.to;
52
- completed = true;
53
- }
54
- if (completed) {
55
- this.stop();
56
- }
57
- this.onUpdate?.(this.value, completed);
58
- }
59
- /** Stop the animation */
60
- stop() {
61
- this.isRunning = false;
62
- }
63
- /**
64
- * Set up the animation from a starting value to an ending value
65
- * with optional parameters for lerping, duration, easing, and onUpdate callback
66
- *
67
- * @param from - The starting value
68
- * @param to - The ending value
69
- * @param options - Options for the animation
70
- */
71
- fromTo(from, to, { lerp: lerp2, duration, easing, onStart, onUpdate }) {
72
- this.from = this.value = from;
73
- this.to = to;
74
- this.lerp = lerp2;
75
- this.duration = duration;
76
- this.easing = easing;
77
- this.currentTime = 0;
78
- this.isRunning = true;
79
- onStart?.();
80
- this.onUpdate = onUpdate;
81
- }
62
+ isRunning = false;
63
+ value = 0;
64
+ from = 0;
65
+ to = 0;
66
+ currentTime = 0;
67
+ lerp;
68
+ duration;
69
+ easing;
70
+ onUpdate;
71
+ /**
72
+ * Advance the animation by the given delta time
73
+ *
74
+ * @param deltaTime - The time in seconds to advance the animation
75
+ */
76
+ advance(deltaTime) {
77
+ if (!this.isRunning) return;
78
+ let completed = false;
79
+ if (this.duration && this.easing) {
80
+ this.currentTime += deltaTime;
81
+ const linearProgress = clamp(0, this.currentTime / this.duration, 1);
82
+ completed = linearProgress >= 1;
83
+ const easedProgress = completed ? 1 : this.easing(linearProgress);
84
+ this.value = this.from + (this.to - this.from) * easedProgress;
85
+ } else if (this.lerp) {
86
+ this.value = damp(this.value, this.to, this.lerp * 60, deltaTime);
87
+ if (Math.round(this.value) === this.to) {
88
+ this.value = this.to;
89
+ completed = true;
90
+ }
91
+ } else {
92
+ this.value = this.to;
93
+ completed = true;
94
+ }
95
+ if (completed) this.stop();
96
+ this.onUpdate?.(this.value, completed);
97
+ }
98
+ /** Stop the animation */
99
+ stop() {
100
+ this.isRunning = false;
101
+ }
102
+ /**
103
+ * Set up the animation from a starting value to an ending value
104
+ * with optional parameters for lerping, duration, easing, and onUpdate callback
105
+ *
106
+ * @param from - The starting value
107
+ * @param to - The ending value
108
+ * @param options - Options for the animation
109
+ */
110
+ fromTo(from, to, { lerp, duration, easing, onStart, onUpdate }) {
111
+ this.from = this.value = from;
112
+ this.to = to;
113
+ this.lerp = lerp;
114
+ this.duration = duration;
115
+ this.easing = easing;
116
+ this.currentTime = 0;
117
+ this.isRunning = true;
118
+ onStart?.();
119
+ this.onUpdate = onUpdate;
120
+ }
82
121
  };
83
-
84
- // packages/core/src/debounce.ts
122
+ //#endregion
123
+ //#region packages/core/src/debounce.ts
85
124
  function debounce(callback, delay) {
86
- let timer;
87
- return function(...args) {
88
- clearTimeout(timer);
89
- timer = setTimeout(() => {
90
- timer = void 0;
91
- callback.apply(this, args);
92
- }, delay);
93
- };
125
+ let timer;
126
+ return function(...args) {
127
+ clearTimeout(timer);
128
+ timer = setTimeout(() => {
129
+ timer = void 0;
130
+ callback.apply(this, args);
131
+ }, delay);
132
+ };
94
133
  }
95
-
96
- // packages/core/src/dimensions.ts
134
+ //#endregion
135
+ //#region packages/core/src/dimensions.ts
136
+ /**
137
+ * Dimensions class to handle the size of the content and wrapper
138
+ *
139
+ * @example
140
+ * const dimensions = new Dimensions(wrapper, content)
141
+ * dimensions.on('resize', (e) => {
142
+ * console.log(e.width, e.height)
143
+ * })
144
+ */
97
145
  var Dimensions = class {
98
- constructor(wrapper, content, { autoResize = true, debounce: debounceValue = 250 } = {}) {
99
- this.wrapper = wrapper;
100
- this.content = content;
101
- if (autoResize) {
102
- this.debouncedResize = debounce(this.resize, debounceValue);
103
- if (this.wrapper instanceof Window) {
104
- window.addEventListener("resize", this.debouncedResize);
105
- } else {
106
- this.wrapperResizeObserver = new ResizeObserver(this.debouncedResize);
107
- this.wrapperResizeObserver.observe(this.wrapper);
108
- }
109
- this.contentResizeObserver = new ResizeObserver(this.debouncedResize);
110
- this.contentResizeObserver.observe(this.content);
111
- }
112
- this.resize();
113
- }
114
- width = 0;
115
- height = 0;
116
- scrollHeight = 0;
117
- scrollWidth = 0;
118
- // These are instanciated in the constructor as they need information from the options
119
- debouncedResize;
120
- wrapperResizeObserver;
121
- contentResizeObserver;
122
- destroy() {
123
- this.wrapperResizeObserver?.disconnect();
124
- this.contentResizeObserver?.disconnect();
125
- if (this.wrapper === window && this.debouncedResize) {
126
- window.removeEventListener("resize", this.debouncedResize);
127
- }
128
- }
129
- resize = () => {
130
- this.onWrapperResize();
131
- this.onContentResize();
132
- };
133
- onWrapperResize = () => {
134
- if (this.wrapper instanceof Window) {
135
- this.width = window.innerWidth;
136
- this.height = window.innerHeight;
137
- } else {
138
- this.width = this.wrapper.clientWidth;
139
- this.height = this.wrapper.clientHeight;
140
- }
141
- };
142
- onContentResize = () => {
143
- if (this.wrapper instanceof Window) {
144
- this.scrollHeight = this.content.scrollHeight;
145
- this.scrollWidth = this.content.scrollWidth;
146
- } else {
147
- this.scrollHeight = this.wrapper.scrollHeight;
148
- this.scrollWidth = this.wrapper.scrollWidth;
149
- }
150
- };
151
- get limit() {
152
- return {
153
- x: this.scrollWidth - this.width,
154
- y: this.scrollHeight - this.height
155
- };
156
- }
146
+ width = 0;
147
+ height = 0;
148
+ scrollHeight = 0;
149
+ scrollWidth = 0;
150
+ debouncedResize;
151
+ wrapperResizeObserver;
152
+ contentResizeObserver;
153
+ constructor(wrapper, content, { autoResize = true, debounce: debounceValue = 250 } = {}) {
154
+ this.wrapper = wrapper;
155
+ this.content = content;
156
+ if (autoResize) {
157
+ this.debouncedResize = debounce(this.resize, debounceValue);
158
+ if (this.wrapper instanceof Window) window.addEventListener("resize", this.debouncedResize);
159
+ else {
160
+ this.wrapperResizeObserver = new ResizeObserver(this.debouncedResize);
161
+ this.wrapperResizeObserver.observe(this.wrapper);
162
+ }
163
+ this.contentResizeObserver = new ResizeObserver(this.debouncedResize);
164
+ this.contentResizeObserver.observe(this.content);
165
+ }
166
+ this.resize();
167
+ }
168
+ destroy() {
169
+ this.wrapperResizeObserver?.disconnect();
170
+ this.contentResizeObserver?.disconnect();
171
+ if (this.wrapper === window && this.debouncedResize) window.removeEventListener("resize", this.debouncedResize);
172
+ }
173
+ resize = () => {
174
+ this.onWrapperResize();
175
+ this.onContentResize();
176
+ };
177
+ onWrapperResize = () => {
178
+ if (this.wrapper instanceof Window) {
179
+ this.width = window.innerWidth;
180
+ this.height = window.innerHeight;
181
+ } else {
182
+ this.width = this.wrapper.clientWidth;
183
+ this.height = this.wrapper.clientHeight;
184
+ }
185
+ };
186
+ onContentResize = () => {
187
+ if (this.wrapper instanceof Window) {
188
+ this.scrollHeight = this.content.scrollHeight;
189
+ this.scrollWidth = this.content.scrollWidth;
190
+ } else {
191
+ this.scrollHeight = this.wrapper.scrollHeight;
192
+ this.scrollWidth = this.wrapper.scrollWidth;
193
+ }
194
+ };
195
+ get limit() {
196
+ return {
197
+ x: this.scrollWidth - this.width,
198
+ y: this.scrollHeight - this.height
199
+ };
200
+ }
157
201
  };
158
-
159
- // packages/core/src/emitter.ts
202
+ //#endregion
203
+ //#region packages/core/src/emitter.ts
204
+ /**
205
+ * Emitter class to handle events
206
+ * @example
207
+ * const emitter = new Emitter()
208
+ * emitter.on('event', (data) => {
209
+ * console.log(data)
210
+ * })
211
+ * emitter.emit('event', 'data')
212
+ */
160
213
  var Emitter = class {
161
- events = {};
162
- /**
163
- * Emit an event with the given data
164
- * @param event Event name
165
- * @param args Data to pass to the event handlers
166
- */
167
- emit(event, ...args) {
168
- const callbacks = this.events[event] || [];
169
- for (let i = 0, length = callbacks.length; i < length; i++) {
170
- callbacks[i]?.(...args);
171
- }
172
- }
173
- /**
174
- * Add a callback to the event
175
- * @param event Event name
176
- * @param cb Callback function
177
- * @returns Unsubscribe function
178
- */
179
- on(event, cb) {
180
- if (this.events[event]) {
181
- this.events[event].push(cb);
182
- } else {
183
- this.events[event] = [cb];
184
- }
185
- return () => {
186
- this.events[event] = this.events[event]?.filter((i) => cb !== i);
187
- };
188
- }
189
- /**
190
- * Remove a callback from the event
191
- * @param event Event name
192
- * @param callback Callback function
193
- */
194
- off(event, callback) {
195
- this.events[event] = this.events[event]?.filter((i) => callback !== i);
196
- }
197
- /**
198
- * Remove all event listeners and clean up
199
- */
200
- destroy() {
201
- this.events = {};
202
- }
214
+ events = {};
215
+ /**
216
+ * Emit an event with the given data
217
+ * @param event Event name
218
+ * @param args Data to pass to the event handlers
219
+ */
220
+ emit(event, ...args) {
221
+ const callbacks = this.events[event] || [];
222
+ for (let i = 0, length = callbacks.length; i < length; i++) callbacks[i]?.(...args);
223
+ }
224
+ /**
225
+ * Add a callback to the event
226
+ * @param event Event name
227
+ * @param cb Callback function
228
+ * @returns Unsubscribe function
229
+ */
230
+ on(event, cb) {
231
+ if (this.events[event]) this.events[event].push(cb);
232
+ else this.events[event] = [cb];
233
+ return () => {
234
+ this.events[event] = this.events[event]?.filter((i) => cb !== i);
235
+ };
236
+ }
237
+ /**
238
+ * Remove a callback from the event
239
+ * @param event Event name
240
+ * @param callback Callback function
241
+ */
242
+ off(event, callback) {
243
+ this.events[event] = this.events[event]?.filter((i) => callback !== i);
244
+ }
245
+ /**
246
+ * Remove all event listeners and clean up
247
+ */
248
+ destroy() {
249
+ this.events = {};
250
+ }
203
251
  };
204
-
205
- // packages/core/src/virtual-scroll.ts
206
- var LINE_HEIGHT = 100 / 6;
207
- var listenerOptions = { passive: false };
252
+ //#endregion
253
+ //#region packages/core/src/virtual-scroll.ts
254
+ const LINE_HEIGHT = 100 / 6;
255
+ const listenerOptions = { passive: false };
208
256
  function getDeltaMultiplier(deltaMode, size) {
209
- if (deltaMode === 1) return LINE_HEIGHT;
210
- if (deltaMode === 2) return size;
211
- return 1;
257
+ if (deltaMode === 1) return LINE_HEIGHT;
258
+ if (deltaMode === 2) return size;
259
+ return 1;
212
260
  }
213
261
  var VirtualScroll = class {
214
- constructor(element, options = { wheelMultiplier: 1, touchMultiplier: 1 }) {
215
- this.element = element;
216
- this.options = options;
217
- window.addEventListener("resize", this.onWindowResize);
218
- this.onWindowResize();
219
- this.element.addEventListener("wheel", this.onWheel, listenerOptions);
220
- this.element.addEventListener(
221
- "touchstart",
222
- this.onTouchStart,
223
- listenerOptions
224
- );
225
- this.element.addEventListener(
226
- "touchmove",
227
- this.onTouchMove,
228
- listenerOptions
229
- );
230
- this.element.addEventListener("touchend", this.onTouchEnd, listenerOptions);
231
- }
232
- touchStart = {
233
- x: 0,
234
- y: 0
235
- };
236
- lastDelta = {
237
- x: 0,
238
- y: 0
239
- };
240
- window = {
241
- width: 0,
242
- height: 0
243
- };
244
- emitter = new Emitter();
245
- /**
246
- * Add an event listener for the given event and callback
247
- *
248
- * @param event Event name
249
- * @param callback Callback function
250
- */
251
- on(event, callback) {
252
- return this.emitter.on(event, callback);
253
- }
254
- /** Remove all event listeners and clean up */
255
- destroy() {
256
- this.emitter.destroy();
257
- window.removeEventListener("resize", this.onWindowResize);
258
- this.element.removeEventListener("wheel", this.onWheel, listenerOptions);
259
- this.element.removeEventListener(
260
- "touchstart",
261
- this.onTouchStart,
262
- listenerOptions
263
- );
264
- this.element.removeEventListener(
265
- "touchmove",
266
- this.onTouchMove,
267
- listenerOptions
268
- );
269
- this.element.removeEventListener(
270
- "touchend",
271
- this.onTouchEnd,
272
- listenerOptions
273
- );
274
- }
275
- /**
276
- * Event handler for 'touchstart' event
277
- *
278
- * @param event Touch event
279
- */
280
- onTouchStart = (event) => {
281
- const { clientX, clientY } = event.targetTouches ? event.targetTouches[0] : event;
282
- this.touchStart.x = clientX;
283
- this.touchStart.y = clientY;
284
- this.lastDelta = {
285
- x: 0,
286
- y: 0
287
- };
288
- this.emitter.emit("scroll", {
289
- deltaX: 0,
290
- deltaY: 0,
291
- event
292
- });
293
- };
294
- /** Event handler for 'touchmove' event */
295
- onTouchMove = (event) => {
296
- const { clientX, clientY } = event.targetTouches ? event.targetTouches[0] : event;
297
- const deltaX = -(clientX - this.touchStart.x) * this.options.touchMultiplier;
298
- const deltaY = -(clientY - this.touchStart.y) * this.options.touchMultiplier;
299
- this.touchStart.x = clientX;
300
- this.touchStart.y = clientY;
301
- this.lastDelta = {
302
- x: deltaX,
303
- y: deltaY
304
- };
305
- this.emitter.emit("scroll", {
306
- deltaX,
307
- deltaY,
308
- event
309
- });
310
- };
311
- onTouchEnd = (event) => {
312
- this.emitter.emit("scroll", {
313
- deltaX: this.lastDelta.x,
314
- deltaY: this.lastDelta.y,
315
- event
316
- });
317
- };
318
- /** Event handler for 'wheel' event */
319
- onWheel = (event) => {
320
- let { deltaX, deltaY, deltaMode } = event;
321
- const multiplierX = getDeltaMultiplier(deltaMode, this.window.width);
322
- const multiplierY = getDeltaMultiplier(deltaMode, this.window.height);
323
- deltaX *= multiplierX;
324
- deltaY *= multiplierY;
325
- deltaX *= this.options.wheelMultiplier;
326
- deltaY *= this.options.wheelMultiplier;
327
- this.emitter.emit("scroll", { deltaX, deltaY, event });
328
- };
329
- onWindowResize = () => {
330
- this.window = {
331
- width: window.innerWidth,
332
- height: window.innerHeight
333
- };
334
- };
262
+ touchStart = {
263
+ x: 0,
264
+ y: 0
265
+ };
266
+ lastDelta = {
267
+ x: 0,
268
+ y: 0
269
+ };
270
+ window = {
271
+ width: 0,
272
+ height: 0
273
+ };
274
+ emitter = new Emitter();
275
+ constructor(element, options = {
276
+ wheelMultiplier: 1,
277
+ touchMultiplier: 1
278
+ }) {
279
+ this.element = element;
280
+ this.options = options;
281
+ window.addEventListener("resize", this.onWindowResize);
282
+ this.onWindowResize();
283
+ this.element.addEventListener("wheel", this.onWheel, listenerOptions);
284
+ this.element.addEventListener("touchstart", this.onTouchStart, listenerOptions);
285
+ this.element.addEventListener("touchmove", this.onTouchMove, listenerOptions);
286
+ this.element.addEventListener("touchend", this.onTouchEnd, listenerOptions);
287
+ }
288
+ /**
289
+ * Add an event listener for the given event and callback
290
+ *
291
+ * @param event Event name
292
+ * @param callback Callback function
293
+ */
294
+ on(event, callback) {
295
+ return this.emitter.on(event, callback);
296
+ }
297
+ /** Remove all event listeners and clean up */
298
+ destroy() {
299
+ this.emitter.destroy();
300
+ window.removeEventListener("resize", this.onWindowResize);
301
+ this.element.removeEventListener("wheel", this.onWheel, listenerOptions);
302
+ this.element.removeEventListener("touchstart", this.onTouchStart, listenerOptions);
303
+ this.element.removeEventListener("touchmove", this.onTouchMove, listenerOptions);
304
+ this.element.removeEventListener("touchend", this.onTouchEnd, listenerOptions);
305
+ }
306
+ /**
307
+ * Event handler for 'touchstart' event
308
+ *
309
+ * @param event Touch event
310
+ */
311
+ onTouchStart = (event) => {
312
+ const { clientX, clientY } = event.targetTouches ? event.targetTouches[0] : event;
313
+ this.touchStart.x = clientX;
314
+ this.touchStart.y = clientY;
315
+ this.lastDelta = {
316
+ x: 0,
317
+ y: 0
318
+ };
319
+ this.emitter.emit("scroll", {
320
+ deltaX: 0,
321
+ deltaY: 0,
322
+ event
323
+ });
324
+ };
325
+ /** Event handler for 'touchmove' event */
326
+ onTouchMove = (event) => {
327
+ const { clientX, clientY } = event.targetTouches ? event.targetTouches[0] : event;
328
+ const deltaX = -(clientX - this.touchStart.x) * this.options.touchMultiplier;
329
+ const deltaY = -(clientY - this.touchStart.y) * this.options.touchMultiplier;
330
+ this.touchStart.x = clientX;
331
+ this.touchStart.y = clientY;
332
+ this.lastDelta = {
333
+ x: deltaX,
334
+ y: deltaY
335
+ };
336
+ this.emitter.emit("scroll", {
337
+ deltaX,
338
+ deltaY,
339
+ event
340
+ });
341
+ };
342
+ onTouchEnd = (event) => {
343
+ this.emitter.emit("scroll", {
344
+ deltaX: this.lastDelta.x,
345
+ deltaY: this.lastDelta.y,
346
+ event
347
+ });
348
+ };
349
+ /** Event handler for 'wheel' event */
350
+ onWheel = (event) => {
351
+ let { deltaX, deltaY, deltaMode } = event;
352
+ const multiplierX = getDeltaMultiplier(deltaMode, this.window.width);
353
+ const multiplierY = getDeltaMultiplier(deltaMode, this.window.height);
354
+ deltaX *= multiplierX;
355
+ deltaY *= multiplierY;
356
+ deltaX *= this.options.wheelMultiplier;
357
+ deltaY *= this.options.wheelMultiplier;
358
+ this.emitter.emit("scroll", {
359
+ deltaX,
360
+ deltaY,
361
+ event
362
+ });
363
+ };
364
+ onWindowResize = () => {
365
+ this.window = {
366
+ width: window.innerWidth,
367
+ height: window.innerHeight
368
+ };
369
+ };
335
370
  };
336
-
337
- // packages/core/src/lenis.ts
338
- var defaultEasing = (t) => Math.min(1, 1.001 - 2 ** (-10 * t));
371
+ //#endregion
372
+ //#region packages/core/src/lenis.ts
373
+ const defaultEasing = (t) => Math.min(1, 1.001 - 2 ** (-10 * t));
339
374
  var Lenis = class {
340
- _isScrolling = false;
341
- // true when scroll is animating
342
- _isStopped = false;
343
- // true if user should not be able to scroll - enable/disable programmatically
344
- _isLocked = false;
345
- // same as isStopped but enabled/disabled when scroll reaches target
346
- _preventNextNativeScrollEvent = false;
347
- _resetVelocityTimeout = null;
348
- _rafId = null;
349
- /**
350
- * Whether or not the user is touching the screen
351
- */
352
- isTouching;
353
- /**
354
- * The time in ms since the lenis instance was created
355
- */
356
- time = 0;
357
- /**
358
- * User data that will be forwarded through the scroll event
359
- *
360
- * @example
361
- * lenis.scrollTo(100, {
362
- * userData: {
363
- * foo: 'bar'
364
- * }
365
- * })
366
- */
367
- userData = {};
368
- /**
369
- * The last velocity of the scroll
370
- */
371
- lastVelocity = 0;
372
- /**
373
- * The current velocity of the scroll
374
- */
375
- velocity = 0;
376
- /**
377
- * The direction of the scroll
378
- */
379
- direction = 0;
380
- /**
381
- * The options passed to the lenis instance
382
- */
383
- options;
384
- /**
385
- * The target scroll value
386
- */
387
- targetScroll;
388
- /**
389
- * The animated scroll value
390
- */
391
- animatedScroll;
392
- // These are instanciated here as they don't need information from the options
393
- animate = new Animate();
394
- emitter = new Emitter();
395
- // These are instanciated in the constructor as they need information from the options
396
- dimensions;
397
- // This is not private because it's used in the Snap class
398
- virtualScroll;
399
- constructor({
400
- wrapper = window,
401
- content = document.documentElement,
402
- eventsTarget = wrapper,
403
- smoothWheel = true,
404
- syncTouch = false,
405
- syncTouchLerp = 0.075,
406
- touchInertiaExponent = 1.7,
407
- duration,
408
- // in seconds
409
- easing,
410
- lerp: lerp2 = 0.1,
411
- infinite = false,
412
- orientation = "vertical",
413
- // vertical, horizontal
414
- gestureOrientation = orientation === "horizontal" ? "both" : "vertical",
415
- // vertical, horizontal, both
416
- touchMultiplier = 1,
417
- wheelMultiplier = 1,
418
- autoResize = true,
419
- prevent,
420
- virtualScroll,
421
- overscroll = true,
422
- autoRaf = false,
423
- anchors = false,
424
- autoToggle = false,
425
- // https://caniuse.com/?search=transition-behavior
426
- allowNestedScroll = false,
427
- __experimental__naiveDimensions = false,
428
- naiveDimensions = __experimental__naiveDimensions,
429
- stopInertiaOnNavigate = false
430
- } = {}) {
431
- window.lenisVersion = version;
432
- if (!window.lenis) {
433
- window.lenis = {};
434
- }
435
- window.lenis.version = version;
436
- if (orientation === "horizontal") {
437
- window.lenis.horizontal = true;
438
- }
439
- if (!wrapper || wrapper === document.documentElement) {
440
- wrapper = window;
441
- }
442
- if (typeof duration === "number" && typeof easing !== "function") {
443
- easing = defaultEasing;
444
- } else if (typeof easing === "function" && typeof duration !== "number") {
445
- duration = 1;
446
- }
447
- this.options = {
448
- wrapper,
449
- content,
450
- eventsTarget,
451
- smoothWheel,
452
- syncTouch,
453
- syncTouchLerp,
454
- touchInertiaExponent,
455
- duration,
456
- easing,
457
- lerp: lerp2,
458
- infinite,
459
- gestureOrientation,
460
- orientation,
461
- touchMultiplier,
462
- wheelMultiplier,
463
- autoResize,
464
- prevent,
465
- virtualScroll,
466
- overscroll,
467
- autoRaf,
468
- anchors,
469
- autoToggle,
470
- allowNestedScroll,
471
- naiveDimensions,
472
- stopInertiaOnNavigate
473
- };
474
- this.dimensions = new Dimensions(wrapper, content, { autoResize });
475
- this.updateClassName();
476
- this.targetScroll = this.animatedScroll = this.actualScroll;
477
- this.options.wrapper.addEventListener("scroll", this.onNativeScroll);
478
- this.options.wrapper.addEventListener("scrollend", this.onScrollEnd, {
479
- capture: true
480
- });
481
- if (this.options.anchors || this.options.stopInertiaOnNavigate) {
482
- this.options.wrapper.addEventListener(
483
- "click",
484
- this.onClick
485
- );
486
- }
487
- this.options.wrapper.addEventListener(
488
- "pointerdown",
489
- this.onPointerDown
490
- );
491
- this.virtualScroll = new VirtualScroll(eventsTarget, {
492
- touchMultiplier,
493
- wheelMultiplier
494
- });
495
- this.virtualScroll.on("scroll", this.onVirtualScroll);
496
- if (this.options.autoToggle) {
497
- this.checkOverflow();
498
- this.rootElement.addEventListener("transitionend", this.onTransitionEnd);
499
- }
500
- if (this.options.autoRaf) {
501
- this._rafId = requestAnimationFrame(this.raf);
502
- }
503
- }
504
- /**
505
- * Destroy the lenis instance, remove all event listeners and clean up the class name
506
- */
507
- destroy() {
508
- this.emitter.destroy();
509
- this.options.wrapper.removeEventListener("scroll", this.onNativeScroll);
510
- this.options.wrapper.removeEventListener("scrollend", this.onScrollEnd, {
511
- capture: true
512
- });
513
- this.options.wrapper.removeEventListener(
514
- "pointerdown",
515
- this.onPointerDown
516
- );
517
- if (this.options.anchors || this.options.stopInertiaOnNavigate) {
518
- this.options.wrapper.removeEventListener(
519
- "click",
520
- this.onClick
521
- );
522
- }
523
- this.virtualScroll.destroy();
524
- this.dimensions.destroy();
525
- this.cleanUpClassName();
526
- if (this._rafId) {
527
- cancelAnimationFrame(this._rafId);
528
- }
529
- }
530
- on(event, callback) {
531
- return this.emitter.on(event, callback);
532
- }
533
- off(event, callback) {
534
- return this.emitter.off(event, callback);
535
- }
536
- onScrollEnd = (e) => {
537
- if (!(e instanceof CustomEvent)) {
538
- if (this.isScrolling === "smooth" || this.isScrolling === false) {
539
- e.stopPropagation();
540
- }
541
- }
542
- };
543
- dispatchScrollendEvent = () => {
544
- this.options.wrapper.dispatchEvent(
545
- new CustomEvent("scrollend", {
546
- bubbles: this.options.wrapper === window,
547
- // cancelable: false,
548
- detail: {
549
- lenisScrollEnd: true
550
- }
551
- })
552
- );
553
- };
554
- get overflow() {
555
- const property = this.isHorizontal ? "overflow-x" : "overflow-y";
556
- return getComputedStyle(this.rootElement)[property];
557
- }
558
- checkOverflow() {
559
- if (["hidden", "clip"].includes(this.overflow)) {
560
- this.internalStop();
561
- } else {
562
- this.internalStart();
563
- }
564
- }
565
- onTransitionEnd = (event) => {
566
- if (event.propertyName.includes("overflow")) {
567
- this.checkOverflow();
568
- }
569
- };
570
- setScroll(scroll) {
571
- if (this.isHorizontal) {
572
- this.options.wrapper.scrollTo({ left: scroll, behavior: "instant" });
573
- } else {
574
- this.options.wrapper.scrollTo({ top: scroll, behavior: "instant" });
575
- }
576
- }
577
- onClick = (event) => {
578
- const path = event.composedPath();
579
- const linkElements = path.filter(
580
- (node) => node instanceof HTMLAnchorElement && node.href
581
- );
582
- const linkElementsUrls = linkElements.map(
583
- (element) => new URL(element.href)
584
- );
585
- const currentUrl = new URL(window.location.href);
586
- if (this.options.anchors) {
587
- const anchorElementUrl = linkElementsUrls.find(
588
- (targetUrl) => currentUrl.host === targetUrl.host && currentUrl.pathname === targetUrl.pathname && targetUrl.hash
589
- );
590
- if (anchorElementUrl) {
591
- const options = typeof this.options.anchors === "object" && this.options.anchors ? this.options.anchors : void 0;
592
- const target = `#${anchorElementUrl.hash.split("#")[1]}`;
593
- this.scrollTo(target, options);
594
- return;
595
- }
596
- }
597
- if (this.options.stopInertiaOnNavigate) {
598
- const hasPageLinkElementUrl = linkElementsUrls.some(
599
- (targetUrl) => currentUrl.host === targetUrl.host && currentUrl.pathname !== targetUrl.pathname
600
- );
601
- if (hasPageLinkElementUrl) {
602
- this.reset();
603
- return;
604
- }
605
- }
606
- };
607
- onPointerDown = (event) => {
608
- if (event.button === 1) {
609
- this.reset();
610
- }
611
- };
612
- onVirtualScroll = (data) => {
613
- if (typeof this.options.virtualScroll === "function" && this.options.virtualScroll(data) === false)
614
- return;
615
- const { deltaX, deltaY, event } = data;
616
- this.emitter.emit("virtual-scroll", { deltaX, deltaY, event });
617
- if (event.ctrlKey) return;
618
- if (event.lenisStopPropagation) return;
619
- const isTouch = event.type.includes("touch");
620
- const isWheel = event.type.includes("wheel");
621
- this.isTouching = event.type === "touchstart" || event.type === "touchmove";
622
- const isClickOrTap = deltaX === 0 && deltaY === 0;
623
- const isTapToStop = this.options.syncTouch && isTouch && event.type === "touchstart" && isClickOrTap && !this.isStopped && !this.isLocked;
624
- if (isTapToStop) {
625
- this.reset();
626
- return;
627
- }
628
- const isUnknownGesture = this.options.gestureOrientation === "vertical" && deltaY === 0 || this.options.gestureOrientation === "horizontal" && deltaX === 0;
629
- if (isClickOrTap || isUnknownGesture) {
630
- return;
631
- }
632
- let composedPath = event.composedPath();
633
- composedPath = composedPath.slice(0, composedPath.indexOf(this.rootElement));
634
- const prevent = this.options.prevent;
635
- const gestureOrientation = Math.abs(deltaX) >= Math.abs(deltaY) ? "horizontal" : "vertical";
636
- if (composedPath.find(
637
- (node) => node instanceof HTMLElement && (typeof prevent === "function" && prevent?.(node) || node.hasAttribute?.("data-lenis-prevent") || gestureOrientation === "vertical" && node.hasAttribute?.("data-lenis-prevent-vertical") || gestureOrientation === "horizontal" && node.hasAttribute?.("data-lenis-prevent-horizontal") || isTouch && node.hasAttribute?.("data-lenis-prevent-touch") || isWheel && node.hasAttribute?.("data-lenis-prevent-wheel") || this.options.allowNestedScroll && this.hasNestedScroll(node, {
638
- deltaX,
639
- deltaY
640
- }))
641
- ))
642
- return;
643
- if (this.isStopped || this.isLocked) {
644
- if (event.cancelable) {
645
- event.preventDefault();
646
- }
647
- return;
648
- }
649
- const isSmooth = this.options.syncTouch && isTouch || this.options.smoothWheel && isWheel;
650
- if (!isSmooth) {
651
- this.isScrolling = "native";
652
- this.animate.stop();
653
- event.lenisStopPropagation = true;
654
- return;
655
- }
656
- let delta = deltaY;
657
- if (this.options.gestureOrientation === "both") {
658
- delta = Math.abs(deltaY) > Math.abs(deltaX) ? deltaY : deltaX;
659
- } else if (this.options.gestureOrientation === "horizontal") {
660
- delta = deltaX;
661
- }
662
- if (!this.options.overscroll || this.options.infinite || this.options.wrapper !== window && this.limit > 0 && (this.animatedScroll > 0 && this.animatedScroll < this.limit || this.animatedScroll === 0 && deltaY > 0 || this.animatedScroll === this.limit && deltaY < 0)) {
663
- event.lenisStopPropagation = true;
664
- }
665
- if (event.cancelable) {
666
- event.preventDefault();
667
- }
668
- const isSyncTouch = isTouch && this.options.syncTouch;
669
- const isTouchEnd = isTouch && event.type === "touchend";
670
- const hasTouchInertia = isTouchEnd;
671
- if (hasTouchInertia) {
672
- delta = Math.sign(this.velocity) * Math.abs(this.velocity) ** this.options.touchInertiaExponent;
673
- }
674
- this.scrollTo(this.targetScroll + delta, {
675
- programmatic: false,
676
- ...isSyncTouch ? {
677
- lerp: hasTouchInertia ? this.options.syncTouchLerp : 1
678
- } : {
679
- lerp: this.options.lerp,
680
- duration: this.options.duration,
681
- easing: this.options.easing
682
- }
683
- });
684
- };
685
- /**
686
- * Force lenis to recalculate the dimensions
687
- */
688
- resize() {
689
- this.dimensions.resize();
690
- this.animatedScroll = this.targetScroll = this.actualScroll;
691
- this.emit();
692
- }
693
- emit() {
694
- this.emitter.emit("scroll", this);
695
- }
696
- onNativeScroll = () => {
697
- if (this._resetVelocityTimeout !== null) {
698
- clearTimeout(this._resetVelocityTimeout);
699
- this._resetVelocityTimeout = null;
700
- }
701
- if (this._preventNextNativeScrollEvent) {
702
- this._preventNextNativeScrollEvent = false;
703
- return;
704
- }
705
- if (this.isScrolling === false || this.isScrolling === "native") {
706
- const lastScroll = this.animatedScroll;
707
- this.animatedScroll = this.targetScroll = this.actualScroll;
708
- this.lastVelocity = this.velocity;
709
- this.velocity = this.animatedScroll - lastScroll;
710
- this.direction = Math.sign(
711
- this.animatedScroll - lastScroll
712
- );
713
- if (!this.isStopped) {
714
- this.isScrolling = "native";
715
- }
716
- this.emit();
717
- if (this.velocity !== 0) {
718
- this._resetVelocityTimeout = setTimeout(() => {
719
- this.lastVelocity = this.velocity;
720
- this.velocity = 0;
721
- this.isScrolling = false;
722
- this.emit();
723
- }, 400);
724
- }
725
- }
726
- };
727
- reset() {
728
- this.isLocked = false;
729
- this.isScrolling = false;
730
- this.animatedScroll = this.targetScroll = this.actualScroll;
731
- this.lastVelocity = this.velocity = 0;
732
- this.animate.stop();
733
- }
734
- /**
735
- * Start lenis scroll after it has been stopped
736
- */
737
- start() {
738
- if (!this.isStopped) return;
739
- if (this.options.autoToggle) {
740
- this.rootElement.style.removeProperty("overflow");
741
- return;
742
- }
743
- this.internalStart();
744
- }
745
- internalStart() {
746
- if (!this.isStopped) return;
747
- this.reset();
748
- this.isStopped = false;
749
- this.emit();
750
- }
751
- /**
752
- * Stop lenis scroll
753
- */
754
- stop() {
755
- if (this.isStopped) return;
756
- if (this.options.autoToggle) {
757
- this.rootElement.style.setProperty("overflow", "clip");
758
- return;
759
- }
760
- this.internalStop();
761
- }
762
- internalStop() {
763
- if (this.isStopped) return;
764
- this.reset();
765
- this.isStopped = true;
766
- this.emit();
767
- }
768
- /**
769
- * RequestAnimationFrame for lenis
770
- *
771
- * @param time The time in ms from an external clock like `requestAnimationFrame` or Tempus
772
- */
773
- raf = (time) => {
774
- const deltaTime = time - (this.time || time);
775
- this.time = time;
776
- this.animate.advance(deltaTime * 1e-3);
777
- if (this.options.autoRaf) {
778
- this._rafId = requestAnimationFrame(this.raf);
779
- }
780
- };
781
- /**
782
- * Scroll to a target value
783
- *
784
- * @param target The target value to scroll to
785
- * @param options The options for the scroll
786
- *
787
- * @example
788
- * lenis.scrollTo(100, {
789
- * offset: 100,
790
- * duration: 1,
791
- * easing: (t) => 1 - Math.cos((t * Math.PI) / 2),
792
- * lerp: 0.1,
793
- * onStart: () => {
794
- * console.log('onStart')
795
- * },
796
- * onComplete: () => {
797
- * console.log('onComplete')
798
- * },
799
- * })
800
- */
801
- scrollTo(_target, {
802
- offset = 0,
803
- immediate = false,
804
- lock = false,
805
- programmatic = true,
806
- // called from outside of the class
807
- lerp: lerp2 = programmatic ? this.options.lerp : void 0,
808
- duration = programmatic ? this.options.duration : void 0,
809
- easing = programmatic ? this.options.easing : void 0,
810
- onStart,
811
- onComplete,
812
- force = false,
813
- // scroll even if stopped
814
- userData
815
- } = {}) {
816
- if ((this.isStopped || this.isLocked) && !force) return;
817
- let target = _target;
818
- let adjustedOffset = offset;
819
- if (typeof target === "string" && ["top", "left", "start", "#"].includes(target)) {
820
- target = 0;
821
- } else if (typeof target === "string" && ["bottom", "right", "end"].includes(target)) {
822
- target = this.limit;
823
- } else {
824
- let node = null;
825
- if (typeof target === "string") {
826
- node = document.querySelector(target);
827
- if (!node) {
828
- if (target === "#top") {
829
- target = 0;
830
- } else {
831
- console.warn("Lenis: Target not found", target);
832
- }
833
- }
834
- } else if (target instanceof HTMLElement && target?.nodeType) {
835
- node = target;
836
- }
837
- if (node) {
838
- if (this.options.wrapper !== window) {
839
- const wrapperRect = this.rootElement.getBoundingClientRect();
840
- adjustedOffset -= this.isHorizontal ? wrapperRect.left : wrapperRect.top;
841
- }
842
- const rect = node.getBoundingClientRect();
843
- target = (this.isHorizontal ? rect.left : rect.top) + this.animatedScroll;
844
- }
845
- }
846
- if (typeof target !== "number") return;
847
- target += adjustedOffset;
848
- target = Math.round(target);
849
- if (this.options.infinite) {
850
- if (programmatic) {
851
- this.targetScroll = this.animatedScroll = this.scroll;
852
- const distance = target - this.animatedScroll;
853
- if (distance > this.limit / 2) {
854
- target -= this.limit;
855
- } else if (distance < -this.limit / 2) {
856
- target += this.limit;
857
- }
858
- }
859
- } else {
860
- target = clamp(0, target, this.limit);
861
- }
862
- if (target === this.targetScroll) {
863
- onStart?.(this);
864
- onComplete?.(this);
865
- return;
866
- }
867
- this.userData = userData ?? {};
868
- if (immediate) {
869
- this.animatedScroll = this.targetScroll = target;
870
- this.setScroll(this.scroll);
871
- this.reset();
872
- this.preventNextNativeScrollEvent();
873
- this.emit();
874
- onComplete?.(this);
875
- this.userData = {};
876
- requestAnimationFrame(() => {
877
- this.dispatchScrollendEvent();
878
- });
879
- return;
880
- }
881
- if (!programmatic) {
882
- this.targetScroll = target;
883
- }
884
- if (typeof duration === "number" && typeof easing !== "function") {
885
- easing = defaultEasing;
886
- } else if (typeof easing === "function" && typeof duration !== "number") {
887
- duration = 1;
888
- }
889
- this.animate.fromTo(this.animatedScroll, target, {
890
- duration,
891
- easing,
892
- lerp: lerp2,
893
- onStart: () => {
894
- if (lock) this.isLocked = true;
895
- this.isScrolling = "smooth";
896
- onStart?.(this);
897
- },
898
- onUpdate: (value, completed) => {
899
- this.isScrolling = "smooth";
900
- this.lastVelocity = this.velocity;
901
- this.velocity = value - this.animatedScroll;
902
- this.direction = Math.sign(this.velocity);
903
- this.animatedScroll = value;
904
- this.setScroll(this.scroll);
905
- if (programmatic) {
906
- this.targetScroll = value;
907
- }
908
- if (!completed) this.emit();
909
- if (completed) {
910
- this.reset();
911
- this.emit();
912
- onComplete?.(this);
913
- this.userData = {};
914
- requestAnimationFrame(() => {
915
- this.dispatchScrollendEvent();
916
- });
917
- this.preventNextNativeScrollEvent();
918
- }
919
- }
920
- });
921
- }
922
- preventNextNativeScrollEvent() {
923
- this._preventNextNativeScrollEvent = true;
924
- requestAnimationFrame(() => {
925
- this._preventNextNativeScrollEvent = false;
926
- });
927
- }
928
- hasNestedScroll(node, { deltaX, deltaY }) {
929
- const time = Date.now();
930
- if (!node._lenis) node._lenis = {};
931
- const cache = node._lenis;
932
- let hasOverflowX;
933
- let hasOverflowY;
934
- let isScrollableX;
935
- let isScrollableY;
936
- let hasOverscrollBehaviorX;
937
- let hasOverscrollBehaviorY;
938
- let scrollWidth;
939
- let scrollHeight;
940
- let clientWidth;
941
- let clientHeight;
942
- if (time - (cache.time ?? 0) > 2e3) {
943
- cache.time = Date.now();
944
- const computedStyle = window.getComputedStyle(node);
945
- cache.computedStyle = computedStyle;
946
- hasOverflowX = ["auto", "overlay", "scroll"].includes(
947
- computedStyle.overflowX
948
- );
949
- hasOverflowY = ["auto", "overlay", "scroll"].includes(
950
- computedStyle.overflowY
951
- );
952
- hasOverscrollBehaviorX = ["auto"].includes(
953
- computedStyle.overscrollBehaviorX
954
- );
955
- hasOverscrollBehaviorY = ["auto"].includes(
956
- computedStyle.overscrollBehaviorY
957
- );
958
- cache.hasOverflowX = hasOverflowX;
959
- cache.hasOverflowY = hasOverflowY;
960
- if (!(hasOverflowX || hasOverflowY)) return false;
961
- scrollWidth = node.scrollWidth;
962
- scrollHeight = node.scrollHeight;
963
- clientWidth = node.clientWidth;
964
- clientHeight = node.clientHeight;
965
- isScrollableX = scrollWidth > clientWidth;
966
- isScrollableY = scrollHeight > clientHeight;
967
- cache.isScrollableX = isScrollableX;
968
- cache.isScrollableY = isScrollableY;
969
- cache.scrollWidth = scrollWidth;
970
- cache.scrollHeight = scrollHeight;
971
- cache.clientWidth = clientWidth;
972
- cache.clientHeight = clientHeight;
973
- cache.hasOverscrollBehaviorX = hasOverscrollBehaviorX;
974
- cache.hasOverscrollBehaviorY = hasOverscrollBehaviorY;
975
- } else {
976
- isScrollableX = cache.isScrollableX;
977
- isScrollableY = cache.isScrollableY;
978
- hasOverflowX = cache.hasOverflowX;
979
- hasOverflowY = cache.hasOverflowY;
980
- scrollWidth = cache.scrollWidth;
981
- scrollHeight = cache.scrollHeight;
982
- clientWidth = cache.clientWidth;
983
- clientHeight = cache.clientHeight;
984
- hasOverscrollBehaviorX = cache.hasOverscrollBehaviorX;
985
- hasOverscrollBehaviorY = cache.hasOverscrollBehaviorY;
986
- }
987
- if (!(hasOverflowX && isScrollableX || hasOverflowY && isScrollableY)) {
988
- return false;
989
- }
990
- const orientation = Math.abs(deltaX) >= Math.abs(deltaY) ? "horizontal" : "vertical";
991
- let scroll;
992
- let maxScroll;
993
- let delta;
994
- let hasOverflow;
995
- let isScrollable;
996
- let hasOverscrollBehavior;
997
- if (orientation === "horizontal") {
998
- scroll = Math.round(node.scrollLeft);
999
- maxScroll = scrollWidth - clientWidth;
1000
- delta = deltaX;
1001
- hasOverflow = hasOverflowX;
1002
- isScrollable = isScrollableX;
1003
- hasOverscrollBehavior = hasOverscrollBehaviorX;
1004
- } else if (orientation === "vertical") {
1005
- scroll = Math.round(node.scrollTop);
1006
- maxScroll = scrollHeight - clientHeight;
1007
- delta = deltaY;
1008
- hasOverflow = hasOverflowY;
1009
- isScrollable = isScrollableY;
1010
- hasOverscrollBehavior = hasOverscrollBehaviorY;
1011
- } else {
1012
- return false;
1013
- }
1014
- if (!hasOverscrollBehavior && (scroll >= maxScroll || scroll <= 0)) {
1015
- return true;
1016
- }
1017
- const willScroll = delta > 0 ? scroll < maxScroll : scroll > 0;
1018
- return willScroll && hasOverflow && isScrollable;
1019
- }
1020
- /**
1021
- * The root element on which lenis is instanced
1022
- */
1023
- get rootElement() {
1024
- return this.options.wrapper === window ? document.documentElement : this.options.wrapper;
1025
- }
1026
- /**
1027
- * The limit which is the maximum scroll value
1028
- */
1029
- get limit() {
1030
- if (this.options.naiveDimensions) {
1031
- if (this.isHorizontal) {
1032
- return this.rootElement.scrollWidth - this.rootElement.clientWidth;
1033
- }
1034
- return this.rootElement.scrollHeight - this.rootElement.clientHeight;
1035
- }
1036
- return this.dimensions.limit[this.isHorizontal ? "x" : "y"];
1037
- }
1038
- /**
1039
- * Whether or not the scroll is horizontal
1040
- */
1041
- get isHorizontal() {
1042
- return this.options.orientation === "horizontal";
1043
- }
1044
- /**
1045
- * The actual scroll value
1046
- */
1047
- get actualScroll() {
1048
- const wrapper = this.options.wrapper;
1049
- return this.isHorizontal ? wrapper.scrollX ?? wrapper.scrollLeft : wrapper.scrollY ?? wrapper.scrollTop;
1050
- }
1051
- /**
1052
- * The current scroll value
1053
- */
1054
- get scroll() {
1055
- return this.options.infinite ? modulo(this.animatedScroll, this.limit) : this.animatedScroll;
1056
- }
1057
- /**
1058
- * The progress of the scroll relative to the limit
1059
- */
1060
- get progress() {
1061
- return this.limit === 0 ? 1 : this.scroll / this.limit;
1062
- }
1063
- /**
1064
- * Current scroll state
1065
- */
1066
- get isScrolling() {
1067
- return this._isScrolling;
1068
- }
1069
- set isScrolling(value) {
1070
- if (this._isScrolling !== value) {
1071
- this._isScrolling = value;
1072
- this.updateClassName();
1073
- }
1074
- }
1075
- /**
1076
- * Check if lenis is stopped
1077
- */
1078
- get isStopped() {
1079
- return this._isStopped;
1080
- }
1081
- set isStopped(value) {
1082
- if (this._isStopped !== value) {
1083
- this._isStopped = value;
1084
- this.updateClassName();
1085
- }
1086
- }
1087
- /**
1088
- * Check if lenis is locked
1089
- */
1090
- get isLocked() {
1091
- return this._isLocked;
1092
- }
1093
- set isLocked(value) {
1094
- if (this._isLocked !== value) {
1095
- this._isLocked = value;
1096
- this.updateClassName();
1097
- }
1098
- }
1099
- /**
1100
- * Check if lenis is smooth scrolling
1101
- */
1102
- get isSmooth() {
1103
- return this.isScrolling === "smooth";
1104
- }
1105
- /**
1106
- * The class name applied to the wrapper element
1107
- */
1108
- get className() {
1109
- let className = "lenis";
1110
- if (this.options.autoToggle) className += " lenis-autoToggle";
1111
- if (this.isStopped) className += " lenis-stopped";
1112
- if (this.isLocked) className += " lenis-locked";
1113
- if (this.isScrolling) className += " lenis-scrolling";
1114
- if (this.isScrolling === "smooth") className += " lenis-smooth";
1115
- return className;
1116
- }
1117
- updateClassName() {
1118
- this.cleanUpClassName();
1119
- this.rootElement.className = `${this.rootElement.className} ${this.className}`.trim();
1120
- }
1121
- cleanUpClassName() {
1122
- this.rootElement.className = this.rootElement.className.replace(/lenis(-\w+)?/g, "").trim();
1123
- }
1124
- };
1125
- export {
1126
- Lenis as default
375
+ _isScrolling = false;
376
+ _isStopped = false;
377
+ _isLocked = false;
378
+ _preventNextNativeScrollEvent = false;
379
+ _resetVelocityTimeout = null;
380
+ _rafId = null;
381
+ /**
382
+ * Whether or not the user is touching the screen
383
+ */
384
+ isTouching;
385
+ /**
386
+ * The time in ms since the lenis instance was created
387
+ */
388
+ time = 0;
389
+ /**
390
+ * User data that will be forwarded through the scroll event
391
+ *
392
+ * @example
393
+ * lenis.scrollTo(100, {
394
+ * userData: {
395
+ * foo: 'bar'
396
+ * }
397
+ * })
398
+ */
399
+ userData = {};
400
+ /**
401
+ * The last velocity of the scroll
402
+ */
403
+ lastVelocity = 0;
404
+ /**
405
+ * The current velocity of the scroll
406
+ */
407
+ velocity = 0;
408
+ /**
409
+ * The direction of the scroll
410
+ */
411
+ direction = 0;
412
+ /**
413
+ * The options passed to the lenis instance
414
+ */
415
+ options;
416
+ /**
417
+ * The target scroll value
418
+ */
419
+ targetScroll;
420
+ /**
421
+ * The animated scroll value
422
+ */
423
+ animatedScroll;
424
+ animate = new Animate();
425
+ emitter = new Emitter();
426
+ dimensions;
427
+ virtualScroll;
428
+ constructor({ wrapper = window, content = document.documentElement, eventsTarget = wrapper, smoothWheel = true, syncTouch = false, syncTouchLerp = .075, touchInertiaExponent = 1.7, duration, easing, lerp = .1, infinite = false, orientation = "vertical", gestureOrientation = orientation === "horizontal" ? "both" : "vertical", touchMultiplier = 1, wheelMultiplier = 1, autoResize = true, prevent, virtualScroll, overscroll = true, autoRaf = false, anchors = false, autoToggle = false, allowNestedScroll = false, __experimental__naiveDimensions = false, naiveDimensions = __experimental__naiveDimensions, stopInertiaOnNavigate = false } = {}) {
429
+ window.lenisVersion = version;
430
+ if (!window.lenis) window.lenis = {};
431
+ window.lenis.version = version;
432
+ if (orientation === "horizontal") window.lenis.horizontal = true;
433
+ if (syncTouch === true) window.lenis.touch = true;
434
+ if (!wrapper || wrapper === document.documentElement) wrapper = window;
435
+ if (typeof duration === "number" && typeof easing !== "function") easing = defaultEasing;
436
+ else if (typeof easing === "function" && typeof duration !== "number") duration = 1;
437
+ this.options = {
438
+ wrapper,
439
+ content,
440
+ eventsTarget,
441
+ smoothWheel,
442
+ syncTouch,
443
+ syncTouchLerp,
444
+ touchInertiaExponent,
445
+ duration,
446
+ easing,
447
+ lerp,
448
+ infinite,
449
+ gestureOrientation,
450
+ orientation,
451
+ touchMultiplier,
452
+ wheelMultiplier,
453
+ autoResize,
454
+ prevent,
455
+ virtualScroll,
456
+ overscroll,
457
+ autoRaf,
458
+ anchors,
459
+ autoToggle,
460
+ allowNestedScroll,
461
+ naiveDimensions,
462
+ stopInertiaOnNavigate
463
+ };
464
+ this.dimensions = new Dimensions(wrapper, content, { autoResize });
465
+ this.updateClassName();
466
+ this.targetScroll = this.animatedScroll = this.actualScroll;
467
+ this.options.wrapper.addEventListener("scroll", this.onNativeScroll);
468
+ this.options.wrapper.addEventListener("scrollend", this.onScrollEnd, { capture: true });
469
+ if (this.options.anchors || this.options.stopInertiaOnNavigate) this.options.wrapper.addEventListener("click", this.onClick);
470
+ this.options.wrapper.addEventListener("pointerdown", this.onPointerDown);
471
+ this.virtualScroll = new VirtualScroll(eventsTarget, {
472
+ touchMultiplier,
473
+ wheelMultiplier
474
+ });
475
+ this.virtualScroll.on("scroll", this.onVirtualScroll);
476
+ if (this.options.autoToggle) {
477
+ this.checkOverflow();
478
+ this.rootElement.addEventListener("transitionend", this.onTransitionEnd);
479
+ }
480
+ if (this.options.autoRaf) this._rafId = requestAnimationFrame(this.raf);
481
+ }
482
+ /**
483
+ * Destroy the lenis instance, remove all event listeners and clean up the class name
484
+ */
485
+ destroy() {
486
+ this.emitter.destroy();
487
+ this.options.wrapper.removeEventListener("scroll", this.onNativeScroll);
488
+ this.options.wrapper.removeEventListener("scrollend", this.onScrollEnd, { capture: true });
489
+ this.options.wrapper.removeEventListener("pointerdown", this.onPointerDown);
490
+ if (this.options.anchors || this.options.stopInertiaOnNavigate) this.options.wrapper.removeEventListener("click", this.onClick);
491
+ this.virtualScroll.destroy();
492
+ this.dimensions.destroy();
493
+ this.cleanUpClassName();
494
+ if (this._rafId) cancelAnimationFrame(this._rafId);
495
+ }
496
+ on(event, callback) {
497
+ return this.emitter.on(event, callback);
498
+ }
499
+ off(event, callback) {
500
+ return this.emitter.off(event, callback);
501
+ }
502
+ onScrollEnd = (e) => {
503
+ if (!(e instanceof CustomEvent)) {
504
+ if (this.isScrolling === "smooth" || this.isScrolling === false) e.stopPropagation();
505
+ }
506
+ };
507
+ dispatchScrollendEvent = () => {
508
+ this.options.wrapper.dispatchEvent(new CustomEvent("scrollend", {
509
+ bubbles: this.options.wrapper === window,
510
+ detail: { lenisScrollEnd: true }
511
+ }));
512
+ };
513
+ get overflow() {
514
+ const property = this.isHorizontal ? "overflow-x" : "overflow-y";
515
+ return getComputedStyle(this.rootElement)[property];
516
+ }
517
+ checkOverflow() {
518
+ if (["hidden", "clip"].includes(this.overflow)) this.internalStop();
519
+ else this.internalStart();
520
+ }
521
+ onTransitionEnd = (event) => {
522
+ if (event.propertyName.includes("overflow")) this.checkOverflow();
523
+ };
524
+ setScroll(scroll) {
525
+ if (this.isHorizontal) this.options.wrapper.scrollTo({
526
+ left: scroll,
527
+ behavior: "instant"
528
+ });
529
+ else this.options.wrapper.scrollTo({
530
+ top: scroll,
531
+ behavior: "instant"
532
+ });
533
+ }
534
+ onClick = (event) => {
535
+ const linkElementsUrls = event.composedPath().filter((node) => node instanceof HTMLAnchorElement && node.href).map((element) => new URL(element.href));
536
+ const currentUrl = new URL(window.location.href);
537
+ if (this.options.anchors) {
538
+ const anchorElementUrl = linkElementsUrls.find((targetUrl) => currentUrl.host === targetUrl.host && currentUrl.pathname === targetUrl.pathname && targetUrl.hash);
539
+ if (anchorElementUrl) {
540
+ const options = typeof this.options.anchors === "object" && this.options.anchors ? this.options.anchors : void 0;
541
+ const target = `#${anchorElementUrl.hash.split("#")[1]}`;
542
+ this.scrollTo(target, options);
543
+ return;
544
+ }
545
+ }
546
+ if (this.options.stopInertiaOnNavigate) {
547
+ if (linkElementsUrls.some((targetUrl) => currentUrl.host === targetUrl.host && currentUrl.pathname !== targetUrl.pathname)) {
548
+ this.reset();
549
+ return;
550
+ }
551
+ }
552
+ };
553
+ onPointerDown = (event) => {
554
+ if (event.button === 1) this.reset();
555
+ };
556
+ onVirtualScroll = (data) => {
557
+ if (typeof this.options.virtualScroll === "function" && this.options.virtualScroll(data) === false) return;
558
+ const { deltaX, deltaY, event } = data;
559
+ this.emitter.emit("virtual-scroll", {
560
+ deltaX,
561
+ deltaY,
562
+ event
563
+ });
564
+ if (event.ctrlKey) return;
565
+ if (event.lenisStopPropagation) return;
566
+ const isTouch = event.type.includes("touch");
567
+ const isWheel = event.type.includes("wheel");
568
+ this.isTouching = event.type === "touchstart" || event.type === "touchmove";
569
+ const isClickOrTap = deltaX === 0 && deltaY === 0;
570
+ if (this.options.syncTouch && isTouch && event.type === "touchstart" && isClickOrTap && !this.isStopped && !this.isLocked) {
571
+ this.reset();
572
+ return;
573
+ }
574
+ const isUnknownGesture = this.options.gestureOrientation === "vertical" && deltaY === 0 || this.options.gestureOrientation === "horizontal" && deltaX === 0;
575
+ if (isClickOrTap || isUnknownGesture) return;
576
+ let composedPath = event.composedPath();
577
+ composedPath = composedPath.slice(0, composedPath.indexOf(this.rootElement));
578
+ const prevent = this.options.prevent;
579
+ const gestureOrientation = Math.abs(deltaX) >= Math.abs(deltaY) ? "horizontal" : "vertical";
580
+ if (composedPath.find((node) => node instanceof HTMLElement && (typeof prevent === "function" && prevent?.(node) || node.hasAttribute?.("data-lenis-prevent") || gestureOrientation === "vertical" && node.hasAttribute?.("data-lenis-prevent-vertical") || gestureOrientation === "horizontal" && node.hasAttribute?.("data-lenis-prevent-horizontal") || isTouch && node.hasAttribute?.("data-lenis-prevent-touch") || isWheel && node.hasAttribute?.("data-lenis-prevent-wheel") || this.options.allowNestedScroll && this.hasNestedScroll(node, {
581
+ deltaX,
582
+ deltaY
583
+ })))) return;
584
+ if (this.isStopped || this.isLocked) {
585
+ if (event.cancelable) event.preventDefault();
586
+ return;
587
+ }
588
+ if (!(this.options.syncTouch && isTouch || this.options.smoothWheel && isWheel)) {
589
+ this.isScrolling = "native";
590
+ this.animate.stop();
591
+ event.lenisStopPropagation = true;
592
+ return;
593
+ }
594
+ let delta = deltaY;
595
+ if (this.options.gestureOrientation === "both") delta = Math.abs(deltaY) > Math.abs(deltaX) ? deltaY : deltaX;
596
+ else if (this.options.gestureOrientation === "horizontal") delta = deltaX;
597
+ if (!this.options.overscroll || this.options.infinite || this.options.wrapper !== window && this.limit > 0 && (this.animatedScroll > 0 && this.animatedScroll < this.limit || this.animatedScroll === 0 && deltaY > 0 || this.animatedScroll === this.limit && deltaY < 0)) event.lenisStopPropagation = true;
598
+ if (event.cancelable) event.preventDefault();
599
+ const isSyncTouch = isTouch && this.options.syncTouch;
600
+ const hasTouchInertia = isTouch && event.type === "touchend";
601
+ if (hasTouchInertia) delta = Math.sign(this.velocity) * Math.abs(this.velocity) ** this.options.touchInertiaExponent;
602
+ this.scrollTo(this.targetScroll + delta, {
603
+ programmatic: false,
604
+ ...isSyncTouch ? { lerp: hasTouchInertia ? this.options.syncTouchLerp : 1 } : {
605
+ lerp: this.options.lerp,
606
+ duration: this.options.duration,
607
+ easing: this.options.easing
608
+ }
609
+ });
610
+ };
611
+ /**
612
+ * Force lenis to recalculate the dimensions
613
+ */
614
+ resize() {
615
+ this.dimensions.resize();
616
+ this.animatedScroll = this.targetScroll = this.actualScroll;
617
+ this.emit();
618
+ }
619
+ emit() {
620
+ this.emitter.emit("scroll", this);
621
+ }
622
+ onNativeScroll = () => {
623
+ if (this._resetVelocityTimeout !== null) {
624
+ clearTimeout(this._resetVelocityTimeout);
625
+ this._resetVelocityTimeout = null;
626
+ }
627
+ if (this._preventNextNativeScrollEvent) {
628
+ this._preventNextNativeScrollEvent = false;
629
+ return;
630
+ }
631
+ if (this.isScrolling === false || this.isScrolling === "native") {
632
+ const lastScroll = this.animatedScroll;
633
+ this.animatedScroll = this.targetScroll = this.actualScroll;
634
+ this.lastVelocity = this.velocity;
635
+ this.velocity = this.animatedScroll - lastScroll;
636
+ this.direction = Math.sign(this.animatedScroll - lastScroll);
637
+ if (!this.isStopped) this.isScrolling = "native";
638
+ this.emit();
639
+ if (this.velocity !== 0) this._resetVelocityTimeout = setTimeout(() => {
640
+ this.lastVelocity = this.velocity;
641
+ this.velocity = 0;
642
+ this.isScrolling = false;
643
+ this.emit();
644
+ }, 400);
645
+ }
646
+ };
647
+ reset() {
648
+ this.isLocked = false;
649
+ this.isScrolling = false;
650
+ this.animatedScroll = this.targetScroll = this.actualScroll;
651
+ this.lastVelocity = this.velocity = 0;
652
+ this.animate.stop();
653
+ }
654
+ /**
655
+ * Start lenis scroll after it has been stopped
656
+ */
657
+ start() {
658
+ if (!this.isStopped) return;
659
+ if (this.options.autoToggle) {
660
+ this.rootElement.style.removeProperty("overflow");
661
+ return;
662
+ }
663
+ this.internalStart();
664
+ }
665
+ internalStart() {
666
+ if (!this.isStopped) return;
667
+ this.reset();
668
+ this.isStopped = false;
669
+ this.emit();
670
+ }
671
+ /**
672
+ * Stop lenis scroll
673
+ */
674
+ stop() {
675
+ if (this.isStopped) return;
676
+ if (this.options.autoToggle) {
677
+ this.rootElement.style.setProperty("overflow", "clip");
678
+ return;
679
+ }
680
+ this.internalStop();
681
+ }
682
+ internalStop() {
683
+ if (this.isStopped) return;
684
+ this.reset();
685
+ this.isStopped = true;
686
+ this.emit();
687
+ }
688
+ /**
689
+ * RequestAnimationFrame for lenis
690
+ *
691
+ * @param time The time in ms from an external clock like `requestAnimationFrame` or Tempus
692
+ */
693
+ raf = (time) => {
694
+ const deltaTime = time - (this.time || time);
695
+ this.time = time;
696
+ this.animate.advance(deltaTime * .001);
697
+ if (this.options.autoRaf) this._rafId = requestAnimationFrame(this.raf);
698
+ };
699
+ /**
700
+ * Scroll to a target value
701
+ *
702
+ * @param target The target value to scroll to
703
+ * @param options The options for the scroll
704
+ *
705
+ * @example
706
+ * lenis.scrollTo(100, {
707
+ * offset: 100,
708
+ * duration: 1,
709
+ * easing: (t) => 1 - Math.cos((t * Math.PI) / 2),
710
+ * lerp: 0.1,
711
+ * onStart: () => {
712
+ * console.log('onStart')
713
+ * },
714
+ * onComplete: () => {
715
+ * console.log('onComplete')
716
+ * },
717
+ * })
718
+ */
719
+ scrollTo(_target, { offset = 0, immediate = false, lock = false, programmatic = true, lerp = programmatic ? this.options.lerp : void 0, duration = programmatic ? this.options.duration : void 0, easing = programmatic ? this.options.easing : void 0, onStart, onComplete, force = false, userData } = {}) {
720
+ if ((this.isStopped || this.isLocked) && !force) return;
721
+ let target = _target;
722
+ let adjustedOffset = offset;
723
+ if (typeof target === "string" && [
724
+ "top",
725
+ "left",
726
+ "start",
727
+ "#"
728
+ ].includes(target)) target = 0;
729
+ else if (typeof target === "string" && [
730
+ "bottom",
731
+ "right",
732
+ "end"
733
+ ].includes(target)) target = this.limit;
734
+ else {
735
+ let node = null;
736
+ if (typeof target === "string") {
737
+ node = document.querySelector(target);
738
+ if (!node) if (target === "#top") target = 0;
739
+ else console.warn("Lenis: Target not found", target);
740
+ } else if (target instanceof HTMLElement && target?.nodeType) node = target;
741
+ if (node) {
742
+ if (this.options.wrapper !== window) {
743
+ const wrapperRect = this.rootElement.getBoundingClientRect();
744
+ adjustedOffset -= this.isHorizontal ? wrapperRect.left : wrapperRect.top;
745
+ }
746
+ const rect = node.getBoundingClientRect();
747
+ const targetStyle = getComputedStyle(node);
748
+ const scrollMargin = this.isHorizontal ? Number.parseFloat(targetStyle.scrollMarginLeft) : Number.parseFloat(targetStyle.scrollMarginTop);
749
+ const containerStyle = getComputedStyle(this.rootElement);
750
+ const scrollPadding = this.isHorizontal ? Number.parseFloat(containerStyle.scrollPaddingLeft) : Number.parseFloat(containerStyle.scrollPaddingTop);
751
+ target = (this.isHorizontal ? rect.left : rect.top) + this.animatedScroll - (Number.isNaN(scrollMargin) ? 0 : scrollMargin) - (Number.isNaN(scrollPadding) ? 0 : scrollPadding);
752
+ }
753
+ }
754
+ if (typeof target !== "number") return;
755
+ target += adjustedOffset;
756
+ target = Math.round(target);
757
+ if (this.options.infinite) {
758
+ if (programmatic) {
759
+ this.targetScroll = this.animatedScroll = this.scroll;
760
+ const distance = target - this.animatedScroll;
761
+ if (distance > this.limit / 2) target -= this.limit;
762
+ else if (distance < -this.limit / 2) target += this.limit;
763
+ }
764
+ } else target = clamp(0, target, this.limit);
765
+ if (target === this.targetScroll) {
766
+ onStart?.(this);
767
+ onComplete?.(this);
768
+ return;
769
+ }
770
+ this.userData = userData ?? {};
771
+ if (immediate) {
772
+ this.animatedScroll = this.targetScroll = target;
773
+ this.setScroll(this.scroll);
774
+ this.reset();
775
+ this.preventNextNativeScrollEvent();
776
+ this.emit();
777
+ onComplete?.(this);
778
+ this.userData = {};
779
+ requestAnimationFrame(() => {
780
+ this.dispatchScrollendEvent();
781
+ });
782
+ return;
783
+ }
784
+ if (!programmatic) this.targetScroll = target;
785
+ if (typeof duration === "number" && typeof easing !== "function") easing = defaultEasing;
786
+ else if (typeof easing === "function" && typeof duration !== "number") duration = 1;
787
+ this.animate.fromTo(this.animatedScroll, target, {
788
+ duration,
789
+ easing,
790
+ lerp,
791
+ onStart: () => {
792
+ if (lock) this.isLocked = true;
793
+ this.isScrolling = "smooth";
794
+ onStart?.(this);
795
+ },
796
+ onUpdate: (value, completed) => {
797
+ this.isScrolling = "smooth";
798
+ this.lastVelocity = this.velocity;
799
+ this.velocity = value - this.animatedScroll;
800
+ this.direction = Math.sign(this.velocity);
801
+ this.animatedScroll = value;
802
+ this.setScroll(this.scroll);
803
+ if (programmatic) this.targetScroll = value;
804
+ if (!completed) this.emit();
805
+ if (completed) {
806
+ this.reset();
807
+ this.emit();
808
+ onComplete?.(this);
809
+ this.userData = {};
810
+ requestAnimationFrame(() => {
811
+ this.dispatchScrollendEvent();
812
+ });
813
+ this.preventNextNativeScrollEvent();
814
+ }
815
+ }
816
+ });
817
+ }
818
+ preventNextNativeScrollEvent() {
819
+ this._preventNextNativeScrollEvent = true;
820
+ requestAnimationFrame(() => {
821
+ this._preventNextNativeScrollEvent = false;
822
+ });
823
+ }
824
+ hasNestedScroll(node, { deltaX, deltaY }) {
825
+ const time = Date.now();
826
+ if (!node._lenis) node._lenis = {};
827
+ const cache = node._lenis;
828
+ let hasOverflowX;
829
+ let hasOverflowY;
830
+ let isScrollableX;
831
+ let isScrollableY;
832
+ let hasOverscrollBehaviorX;
833
+ let hasOverscrollBehaviorY;
834
+ let scrollWidth;
835
+ let scrollHeight;
836
+ let clientWidth;
837
+ let clientHeight;
838
+ if (time - (cache.time ?? 0) > 2e3) {
839
+ cache.time = Date.now();
840
+ const computedStyle = window.getComputedStyle(node);
841
+ cache.computedStyle = computedStyle;
842
+ hasOverflowX = [
843
+ "auto",
844
+ "overlay",
845
+ "scroll"
846
+ ].includes(computedStyle.overflowX);
847
+ hasOverflowY = [
848
+ "auto",
849
+ "overlay",
850
+ "scroll"
851
+ ].includes(computedStyle.overflowY);
852
+ hasOverscrollBehaviorX = ["auto"].includes(computedStyle.overscrollBehaviorX);
853
+ hasOverscrollBehaviorY = ["auto"].includes(computedStyle.overscrollBehaviorY);
854
+ cache.hasOverflowX = hasOverflowX;
855
+ cache.hasOverflowY = hasOverflowY;
856
+ if (!(hasOverflowX || hasOverflowY)) return false;
857
+ scrollWidth = node.scrollWidth;
858
+ scrollHeight = node.scrollHeight;
859
+ clientWidth = node.clientWidth;
860
+ clientHeight = node.clientHeight;
861
+ isScrollableX = scrollWidth > clientWidth;
862
+ isScrollableY = scrollHeight > clientHeight;
863
+ cache.isScrollableX = isScrollableX;
864
+ cache.isScrollableY = isScrollableY;
865
+ cache.scrollWidth = scrollWidth;
866
+ cache.scrollHeight = scrollHeight;
867
+ cache.clientWidth = clientWidth;
868
+ cache.clientHeight = clientHeight;
869
+ cache.hasOverscrollBehaviorX = hasOverscrollBehaviorX;
870
+ cache.hasOverscrollBehaviorY = hasOverscrollBehaviorY;
871
+ } else {
872
+ isScrollableX = cache.isScrollableX;
873
+ isScrollableY = cache.isScrollableY;
874
+ hasOverflowX = cache.hasOverflowX;
875
+ hasOverflowY = cache.hasOverflowY;
876
+ scrollWidth = cache.scrollWidth;
877
+ scrollHeight = cache.scrollHeight;
878
+ clientWidth = cache.clientWidth;
879
+ clientHeight = cache.clientHeight;
880
+ hasOverscrollBehaviorX = cache.hasOverscrollBehaviorX;
881
+ hasOverscrollBehaviorY = cache.hasOverscrollBehaviorY;
882
+ }
883
+ if (!(hasOverflowX && isScrollableX || hasOverflowY && isScrollableY)) return false;
884
+ const orientation = Math.abs(deltaX) >= Math.abs(deltaY) ? "horizontal" : "vertical";
885
+ let scroll;
886
+ let maxScroll;
887
+ let delta;
888
+ let hasOverflow;
889
+ let isScrollable;
890
+ let hasOverscrollBehavior;
891
+ if (orientation === "horizontal") {
892
+ scroll = Math.round(node.scrollLeft);
893
+ maxScroll = scrollWidth - clientWidth;
894
+ delta = deltaX;
895
+ hasOverflow = hasOverflowX;
896
+ isScrollable = isScrollableX;
897
+ hasOverscrollBehavior = hasOverscrollBehaviorX;
898
+ } else if (orientation === "vertical") {
899
+ scroll = Math.round(node.scrollTop);
900
+ maxScroll = scrollHeight - clientHeight;
901
+ delta = deltaY;
902
+ hasOverflow = hasOverflowY;
903
+ isScrollable = isScrollableY;
904
+ hasOverscrollBehavior = hasOverscrollBehaviorY;
905
+ } else return false;
906
+ if (!hasOverscrollBehavior && (scroll >= maxScroll || scroll <= 0)) return true;
907
+ return (delta > 0 ? scroll < maxScroll : scroll > 0) && hasOverflow && isScrollable;
908
+ }
909
+ /**
910
+ * The root element on which lenis is instanced
911
+ */
912
+ get rootElement() {
913
+ return this.options.wrapper === window ? document.documentElement : this.options.wrapper;
914
+ }
915
+ /**
916
+ * The limit which is the maximum scroll value
917
+ */
918
+ get limit() {
919
+ if (this.options.naiveDimensions) {
920
+ if (this.isHorizontal) return this.rootElement.scrollWidth - this.rootElement.clientWidth;
921
+ return this.rootElement.scrollHeight - this.rootElement.clientHeight;
922
+ }
923
+ return this.dimensions.limit[this.isHorizontal ? "x" : "y"];
924
+ }
925
+ /**
926
+ * Whether or not the scroll is horizontal
927
+ */
928
+ get isHorizontal() {
929
+ return this.options.orientation === "horizontal";
930
+ }
931
+ /**
932
+ * The actual scroll value
933
+ */
934
+ get actualScroll() {
935
+ const wrapper = this.options.wrapper;
936
+ return this.isHorizontal ? wrapper.scrollX ?? wrapper.scrollLeft : wrapper.scrollY ?? wrapper.scrollTop;
937
+ }
938
+ /**
939
+ * The current scroll value
940
+ */
941
+ get scroll() {
942
+ return this.options.infinite ? modulo(this.animatedScroll, this.limit) : this.animatedScroll;
943
+ }
944
+ /**
945
+ * The progress of the scroll relative to the limit
946
+ */
947
+ get progress() {
948
+ return this.limit === 0 ? 1 : this.scroll / this.limit;
949
+ }
950
+ /**
951
+ * Current scroll state
952
+ */
953
+ get isScrolling() {
954
+ return this._isScrolling;
955
+ }
956
+ set isScrolling(value) {
957
+ if (this._isScrolling !== value) {
958
+ this._isScrolling = value;
959
+ this.updateClassName();
960
+ }
961
+ }
962
+ /**
963
+ * Check if lenis is stopped
964
+ */
965
+ get isStopped() {
966
+ return this._isStopped;
967
+ }
968
+ set isStopped(value) {
969
+ if (this._isStopped !== value) {
970
+ this._isStopped = value;
971
+ this.updateClassName();
972
+ }
973
+ }
974
+ /**
975
+ * Check if lenis is locked
976
+ */
977
+ get isLocked() {
978
+ return this._isLocked;
979
+ }
980
+ set isLocked(value) {
981
+ if (this._isLocked !== value) {
982
+ this._isLocked = value;
983
+ this.updateClassName();
984
+ }
985
+ }
986
+ /**
987
+ * Check if lenis is smooth scrolling
988
+ */
989
+ get isSmooth() {
990
+ return this.isScrolling === "smooth";
991
+ }
992
+ /**
993
+ * The class name applied to the wrapper element
994
+ */
995
+ get className() {
996
+ let className = "lenis";
997
+ if (this.options.autoToggle) className += " lenis-autoToggle";
998
+ if (this.isStopped) className += " lenis-stopped";
999
+ if (this.isLocked) className += " lenis-locked";
1000
+ if (this.isScrolling) className += " lenis-scrolling";
1001
+ if (this.isScrolling === "smooth") className += " lenis-smooth";
1002
+ return className;
1003
+ }
1004
+ updateClassName() {
1005
+ this.cleanUpClassName();
1006
+ this.rootElement.className = `${this.rootElement.className} ${this.className}`.trim();
1007
+ }
1008
+ cleanUpClassName() {
1009
+ this.rootElement.className = this.rootElement.className.replace(/lenis(-\w+)?/g, "").trim();
1010
+ }
1127
1011
  };
1012
+ //#endregion
1013
+ export { Lenis as default };
1014
+
1128
1015
  //# sourceMappingURL=lenis.mjs.map