lenis 1.1.13 → 1.1.14-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.js CHANGED
@@ -1,923 +1,854 @@
1
- (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
- typeof define === 'function' && define.amd ? define(factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Lenis = factory());
5
- })(this, (function () { 'use strict';
1
+ // package.json
2
+ var version = "1.1.14-dev.0";
6
3
 
7
- var version = "1.1.13";
4
+ // packages/core/src/maths.ts
5
+ function clamp(min, input, max) {
6
+ return Math.max(min, Math.min(input, max));
7
+ }
8
+ function lerp(x, y, t) {
9
+ return (1 - t) * x + t * y;
10
+ }
11
+ function damp(x, y, lambda, deltaTime) {
12
+ return lerp(x, y, 1 - Math.exp(-lambda * deltaTime));
13
+ }
14
+ function modulo(n, d) {
15
+ return (n % d + d) % d;
16
+ }
8
17
 
18
+ // packages/core/src/animate.ts
19
+ 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;
9
30
  /**
10
- * Clamp a value between a minimum and maximum value
31
+ * Advance the animation by the given delta time
11
32
  *
12
- * @param min Minimum value
13
- * @param input Value to clamp
14
- * @param max Maximum value
15
- * @returns Clamped value
33
+ * @param deltaTime - The time in seconds to advance the animation
16
34
  */
17
- function clamp(min, input, max) {
18
- return Math.max(min, Math.min(input, max));
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;
19
62
  }
20
63
  /**
21
- * Linearly interpolate between two values using an amount (0 <= t <= 1)
64
+ * Set up the animation from a starting value to an ending value
65
+ * with optional parameters for lerping, duration, easing, and onUpdate callback
22
66
  *
23
- * @param x First value
24
- * @param y Second value
25
- * @param t Amount to interpolate (0 <= t <= 1)
26
- * @returns Interpolated value
67
+ * @param from - The starting value
68
+ * @param to - The ending value
69
+ * @param options - Options for the animation
27
70
  */
28
- function lerp(x, y, t) {
29
- return (1 - t) * x + t * y;
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
+ }
82
+ };
83
+
84
+ // packages/core/src/debounce.ts
85
+ function debounce(callback, delay) {
86
+ let timer;
87
+ return function(...args) {
88
+ let context = this;
89
+ clearTimeout(timer);
90
+ timer = setTimeout(() => {
91
+ timer = void 0;
92
+ callback.apply(context, args);
93
+ }, delay);
94
+ };
95
+ }
96
+
97
+ // packages/core/src/dimensions.ts
98
+ var Dimensions = class {
99
+ constructor(wrapper, content, { autoResize = true, debounce: debounceValue = 250 } = {}) {
100
+ this.wrapper = wrapper;
101
+ this.content = content;
102
+ if (autoResize) {
103
+ this.debouncedResize = debounce(this.resize, debounceValue);
104
+ if (this.wrapper instanceof Window) {
105
+ window.addEventListener("resize", this.debouncedResize, false);
106
+ } else {
107
+ this.wrapperResizeObserver = new ResizeObserver(this.debouncedResize);
108
+ this.wrapperResizeObserver.observe(this.wrapper);
109
+ }
110
+ this.contentResizeObserver = new ResizeObserver(this.debouncedResize);
111
+ this.contentResizeObserver.observe(this.content);
112
+ }
113
+ this.resize();
114
+ }
115
+ width = 0;
116
+ height = 0;
117
+ scrollHeight = 0;
118
+ scrollWidth = 0;
119
+ // These are instanciated in the constructor as they need information from the options
120
+ debouncedResize;
121
+ wrapperResizeObserver;
122
+ contentResizeObserver;
123
+ destroy() {
124
+ this.wrapperResizeObserver?.disconnect();
125
+ this.contentResizeObserver?.disconnect();
126
+ if (this.wrapper === window && this.debouncedResize) {
127
+ window.removeEventListener("resize", this.debouncedResize, false);
128
+ }
30
129
  }
130
+ resize = () => {
131
+ this.onWrapperResize();
132
+ this.onContentResize();
133
+ };
134
+ onWrapperResize = () => {
135
+ if (this.wrapper instanceof Window) {
136
+ this.width = window.innerWidth;
137
+ this.height = window.innerHeight;
138
+ } else {
139
+ this.width = this.wrapper.clientWidth;
140
+ this.height = this.wrapper.clientHeight;
141
+ }
142
+ };
143
+ onContentResize = () => {
144
+ if (this.wrapper instanceof Window) {
145
+ this.scrollHeight = this.content.scrollHeight;
146
+ this.scrollWidth = this.content.scrollWidth;
147
+ } else {
148
+ this.scrollHeight = this.wrapper.scrollHeight;
149
+ this.scrollWidth = this.wrapper.scrollWidth;
150
+ }
151
+ };
152
+ get limit() {
153
+ return {
154
+ x: this.scrollWidth - this.width,
155
+ y: this.scrollHeight - this.height
156
+ };
157
+ }
158
+ };
159
+
160
+ // packages/core/src/emitter.ts
161
+ var Emitter = class {
162
+ events = {};
31
163
  /**
32
- * Damp a value over time using a damping factor
33
- * {@link http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/}
34
- *
35
- * @param x Initial value
36
- * @param y Target value
37
- * @param lambda Damping factor
38
- * @param dt Time elapsed since the last update
39
- * @returns Damped value
164
+ * Emit an event with the given data
165
+ * @param event Event name
166
+ * @param args Data to pass to the event handlers
40
167
  */
41
- function damp(x, y, lambda, deltaTime) {
42
- return lerp(x, y, 1 - Math.exp(-lambda * deltaTime));
168
+ emit(event, ...args) {
169
+ let callbacks = this.events[event] || [];
170
+ for (let i = 0, length = callbacks.length; i < length; i++) {
171
+ callbacks[i]?.(...args);
172
+ }
43
173
  }
44
174
  /**
45
- * Calculate the modulo of the dividend and divisor while keeping the result within the same sign as the divisor
46
- * {@link https://anguscroll.com/just/just-modulo}
47
- *
48
- * @param n Dividend
49
- * @param d Divisor
50
- * @returns Modulo
175
+ * Add a callback to the event
176
+ * @param event Event name
177
+ * @param cb Callback function
178
+ * @returns Unsubscribe function
179
+ */
180
+ on(event, cb) {
181
+ this.events[event]?.push(cb) || (this.events[event] = [cb]);
182
+ return () => {
183
+ this.events[event] = this.events[event]?.filter((i) => cb !== i);
184
+ };
185
+ }
186
+ /**
187
+ * Remove a callback from the event
188
+ * @param event Event name
189
+ * @param callback Callback function
190
+ */
191
+ off(event, callback) {
192
+ this.events[event] = this.events[event]?.filter((i) => callback !== i);
193
+ }
194
+ /**
195
+ * Remove all event listeners and clean up
51
196
  */
52
- function modulo(n, d) {
53
- return ((n % d) + d) % d;
197
+ destroy() {
198
+ this.events = {};
54
199
  }
200
+ };
55
201
 
202
+ // packages/core/src/virtual-scroll.ts
203
+ var LINE_HEIGHT = 100 / 6;
204
+ var listenerOptions = { passive: false };
205
+ var VirtualScroll = class {
206
+ constructor(element, options = { wheelMultiplier: 1, touchMultiplier: 1 }) {
207
+ this.element = element;
208
+ this.options = options;
209
+ window.addEventListener("resize", this.onWindowResize, false);
210
+ this.onWindowResize();
211
+ this.element.addEventListener("wheel", this.onWheel, listenerOptions);
212
+ this.element.addEventListener(
213
+ "touchstart",
214
+ this.onTouchStart,
215
+ listenerOptions
216
+ );
217
+ this.element.addEventListener(
218
+ "touchmove",
219
+ this.onTouchMove,
220
+ listenerOptions
221
+ );
222
+ this.element.addEventListener("touchend", this.onTouchEnd, listenerOptions);
223
+ }
224
+ touchStart = {
225
+ x: 0,
226
+ y: 0
227
+ };
228
+ lastDelta = {
229
+ x: 0,
230
+ y: 0
231
+ };
232
+ window = {
233
+ width: 0,
234
+ height: 0
235
+ };
236
+ emitter = new Emitter();
56
237
  /**
57
- * Animate class to handle value animations with lerping or easing
238
+ * Add an event listener for the given event and callback
58
239
  *
59
- * @example
60
- * const animate = new Animate()
61
- * animate.fromTo(0, 100, { duration: 1, easing: (t) => t })
62
- * animate.advance(0.5) // 50
63
- */
64
- class Animate {
65
- constructor() {
66
- this.isRunning = false;
67
- this.value = 0;
68
- this.from = 0;
69
- this.to = 0;
70
- this.currentTime = 0;
71
- }
72
- /**
73
- * Advance the animation by the given delta time
74
- *
75
- * @param deltaTime - The time in seconds to advance the animation
76
- */
77
- advance(deltaTime) {
78
- var _a;
79
- if (!this.isRunning)
80
- return;
81
- let completed = false;
82
- if (this.duration && this.easing) {
83
- this.currentTime += deltaTime;
84
- const linearProgress = clamp(0, this.currentTime / this.duration, 1);
85
- completed = linearProgress >= 1;
86
- const easedProgress = completed ? 1 : this.easing(linearProgress);
87
- this.value = this.from + (this.to - this.from) * easedProgress;
88
- }
89
- else if (this.lerp) {
90
- this.value = damp(this.value, this.to, this.lerp * 60, deltaTime);
91
- if (Math.round(this.value) === this.to) {
92
- this.value = this.to;
93
- completed = true;
94
- }
95
- }
96
- else {
97
- // If no easing or lerp, just jump to the end value
98
- this.value = this.to;
99
- completed = true;
100
- }
101
- if (completed) {
102
- this.stop();
103
- }
104
- // Call the onUpdate callback with the current value and completed status
105
- (_a = this.onUpdate) === null || _a === void 0 ? void 0 : _a.call(this, this.value, completed);
106
- }
107
- /** Stop the animation */
108
- stop() {
109
- this.isRunning = false;
110
- }
111
- /**
112
- * Set up the animation from a starting value to an ending value
113
- * with optional parameters for lerping, duration, easing, and onUpdate callback
114
- *
115
- * @param from - The starting value
116
- * @param to - The ending value
117
- * @param options - Options for the animation
118
- */
119
- fromTo(from, to, { lerp, duration, easing, onStart, onUpdate }) {
120
- this.from = this.value = from;
121
- this.to = to;
122
- this.lerp = lerp;
123
- this.duration = duration;
124
- this.easing = easing;
125
- this.currentTime = 0;
126
- this.isRunning = true;
127
- onStart === null || onStart === void 0 ? void 0 : onStart();
128
- this.onUpdate = onUpdate;
129
- }
240
+ * @param event Event name
241
+ * @param callback Callback function
242
+ */
243
+ on(event, callback) {
244
+ return this.emitter.on(event, callback);
130
245
  }
131
-
132
- function debounce(callback, delay) {
133
- let timer;
134
- return function (...args) {
135
- let context = this;
136
- clearTimeout(timer);
137
- timer = setTimeout(() => {
138
- timer = undefined;
139
- callback.apply(context, args);
140
- }, delay);
141
- };
246
+ /** Remove all event listeners and clean up */
247
+ destroy() {
248
+ this.emitter.destroy();
249
+ window.removeEventListener("resize", this.onWindowResize, false);
250
+ this.element.removeEventListener("wheel", this.onWheel, listenerOptions);
251
+ this.element.removeEventListener(
252
+ "touchstart",
253
+ this.onTouchStart,
254
+ listenerOptions
255
+ );
256
+ this.element.removeEventListener(
257
+ "touchmove",
258
+ this.onTouchMove,
259
+ listenerOptions
260
+ );
261
+ this.element.removeEventListener(
262
+ "touchend",
263
+ this.onTouchEnd,
264
+ listenerOptions
265
+ );
142
266
  }
267
+ /**
268
+ * Event handler for 'touchstart' event
269
+ *
270
+ * @param event Touch event
271
+ */
272
+ onTouchStart = (event) => {
273
+ const { clientX, clientY } = event.targetTouches ? event.targetTouches[0] : event;
274
+ this.touchStart.x = clientX;
275
+ this.touchStart.y = clientY;
276
+ this.lastDelta = {
277
+ x: 0,
278
+ y: 0
279
+ };
280
+ this.emitter.emit("scroll", {
281
+ deltaX: 0,
282
+ deltaY: 0,
283
+ event
284
+ });
285
+ };
286
+ /** Event handler for 'touchmove' event */
287
+ onTouchMove = (event) => {
288
+ const { clientX, clientY } = event.targetTouches ? event.targetTouches[0] : event;
289
+ const deltaX = -(clientX - this.touchStart.x) * this.options.touchMultiplier;
290
+ const deltaY = -(clientY - this.touchStart.y) * this.options.touchMultiplier;
291
+ this.touchStart.x = clientX;
292
+ this.touchStart.y = clientY;
293
+ this.lastDelta = {
294
+ x: deltaX,
295
+ y: deltaY
296
+ };
297
+ this.emitter.emit("scroll", {
298
+ deltaX,
299
+ deltaY,
300
+ event
301
+ });
302
+ };
303
+ onTouchEnd = (event) => {
304
+ this.emitter.emit("scroll", {
305
+ deltaX: this.lastDelta.x,
306
+ deltaY: this.lastDelta.y,
307
+ event
308
+ });
309
+ };
310
+ /** Event handler for 'wheel' event */
311
+ onWheel = (event) => {
312
+ let { deltaX, deltaY, deltaMode } = event;
313
+ const multiplierX = deltaMode === 1 ? LINE_HEIGHT : deltaMode === 2 ? this.window.width : 1;
314
+ const multiplierY = deltaMode === 1 ? LINE_HEIGHT : deltaMode === 2 ? this.window.height : 1;
315
+ deltaX *= multiplierX;
316
+ deltaY *= multiplierY;
317
+ deltaX *= this.options.wheelMultiplier;
318
+ deltaY *= this.options.wheelMultiplier;
319
+ this.emitter.emit("scroll", { deltaX, deltaY, event });
320
+ };
321
+ onWindowResize = () => {
322
+ this.window = {
323
+ width: window.innerWidth,
324
+ height: window.innerHeight
325
+ };
326
+ };
327
+ };
143
328
 
329
+ // packages/core/src/lenis.ts
330
+ var Lenis = class {
331
+ _isScrolling = false;
332
+ // true when scroll is animating
333
+ _isStopped = false;
334
+ // true if user should not be able to scroll - enable/disable programmatically
335
+ _isLocked = false;
336
+ // same as isStopped but enabled/disabled when scroll reaches target
337
+ _preventNextNativeScrollEvent = false;
338
+ _resetVelocityTimeout = null;
339
+ /**
340
+ * Whether or not the user is touching the screen
341
+ */
342
+ isTouching;
343
+ /**
344
+ * The time in ms since the lenis instance was created
345
+ */
346
+ time = 0;
144
347
  /**
145
- * Dimensions class to handle the size of the content and wrapper
348
+ * User data that will be forwarded through the scroll event
146
349
  *
147
350
  * @example
148
- * const dimensions = new Dimensions(wrapper, content)
149
- * dimensions.on('resize', (e) => {
150
- * console.log(e.width, e.height)
351
+ * lenis.scrollTo(100, {
352
+ * userData: {
353
+ * foo: 'bar'
354
+ * }
151
355
  * })
152
356
  */
153
- class Dimensions {
154
- constructor(wrapper, content, { autoResize = true, debounce: debounceValue = 250 } = {}) {
155
- this.wrapper = wrapper;
156
- this.content = content;
157
- this.width = 0;
158
- this.height = 0;
159
- this.scrollHeight = 0;
160
- this.scrollWidth = 0;
161
- this.resize = () => {
162
- this.onWrapperResize();
163
- this.onContentResize();
164
- };
165
- this.onWrapperResize = () => {
166
- if (this.wrapper instanceof Window) {
167
- this.width = window.innerWidth;
168
- this.height = window.innerHeight;
169
- }
170
- else {
171
- this.width = this.wrapper.clientWidth;
172
- this.height = this.wrapper.clientHeight;
173
- }
174
- };
175
- this.onContentResize = () => {
176
- if (this.wrapper instanceof Window) {
177
- this.scrollHeight = this.content.scrollHeight;
178
- this.scrollWidth = this.content.scrollWidth;
179
- }
180
- else {
181
- this.scrollHeight = this.wrapper.scrollHeight;
182
- this.scrollWidth = this.wrapper.scrollWidth;
183
- }
184
- };
185
- if (autoResize) {
186
- this.debouncedResize = debounce(this.resize, debounceValue);
187
- if (this.wrapper instanceof Window) {
188
- window.addEventListener('resize', this.debouncedResize, false);
189
- }
190
- else {
191
- this.wrapperResizeObserver = new ResizeObserver(this.debouncedResize);
192
- this.wrapperResizeObserver.observe(this.wrapper);
193
- }
194
- this.contentResizeObserver = new ResizeObserver(this.debouncedResize);
195
- this.contentResizeObserver.observe(this.content);
196
- }
197
- this.resize();
198
- }
199
- destroy() {
200
- var _a, _b;
201
- (_a = this.wrapperResizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
202
- (_b = this.contentResizeObserver) === null || _b === void 0 ? void 0 : _b.disconnect();
203
- if (this.wrapper === window && this.debouncedResize) {
204
- window.removeEventListener('resize', this.debouncedResize, false);
205
- }
206
- }
207
- get limit() {
208
- return {
209
- x: this.scrollWidth - this.width,
210
- y: this.scrollHeight - this.height,
211
- };
212
- }
357
+ userData = {};
358
+ /**
359
+ * The last velocity of the scroll
360
+ */
361
+ lastVelocity = 0;
362
+ /**
363
+ * The current velocity of the scroll
364
+ */
365
+ velocity = 0;
366
+ /**
367
+ * The direction of the scroll
368
+ */
369
+ direction = 0;
370
+ /**
371
+ * The options passed to the lenis instance
372
+ */
373
+ options;
374
+ /**
375
+ * The target scroll value
376
+ */
377
+ targetScroll;
378
+ /**
379
+ * The animated scroll value
380
+ */
381
+ animatedScroll;
382
+ // These are instanciated here as they don't need information from the options
383
+ animate = new Animate();
384
+ emitter = new Emitter();
385
+ // These are instanciated in the constructor as they need information from the options
386
+ dimensions;
387
+ // This is not private because it's used in the Snap class
388
+ virtualScroll;
389
+ constructor({
390
+ wrapper = window,
391
+ content = document.documentElement,
392
+ eventsTarget = wrapper,
393
+ smoothWheel = true,
394
+ syncTouch = false,
395
+ syncTouchLerp = 0.075,
396
+ touchInertiaMultiplier = 35,
397
+ duration,
398
+ // in seconds
399
+ easing = (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
400
+ lerp: lerp2 = 0.1,
401
+ infinite = false,
402
+ orientation = "vertical",
403
+ // vertical, horizontal
404
+ gestureOrientation = "vertical",
405
+ // vertical, horizontal, both
406
+ touchMultiplier = 1,
407
+ wheelMultiplier = 1,
408
+ autoResize = true,
409
+ prevent,
410
+ virtualScroll,
411
+ __experimental__naiveDimensions = false
412
+ } = {}) {
413
+ window.lenisVersion = version;
414
+ if (!wrapper || wrapper === document.documentElement || wrapper === document.body) {
415
+ wrapper = window;
416
+ }
417
+ this.options = {
418
+ wrapper,
419
+ content,
420
+ eventsTarget,
421
+ smoothWheel,
422
+ syncTouch,
423
+ syncTouchLerp,
424
+ touchInertiaMultiplier,
425
+ duration,
426
+ easing,
427
+ lerp: lerp2,
428
+ infinite,
429
+ gestureOrientation,
430
+ orientation,
431
+ touchMultiplier,
432
+ wheelMultiplier,
433
+ autoResize,
434
+ prevent,
435
+ virtualScroll,
436
+ __experimental__naiveDimensions
437
+ };
438
+ this.dimensions = new Dimensions(wrapper, content, { autoResize });
439
+ this.updateClassName();
440
+ this.targetScroll = this.animatedScroll = this.actualScroll;
441
+ this.options.wrapper.addEventListener("scroll", this.onNativeScroll, false);
442
+ this.options.wrapper.addEventListener(
443
+ "pointerdown",
444
+ this.onPointerDown,
445
+ false
446
+ );
447
+ this.virtualScroll = new VirtualScroll(eventsTarget, {
448
+ touchMultiplier,
449
+ wheelMultiplier
450
+ });
451
+ this.virtualScroll.on("scroll", this.onVirtualScroll);
213
452
  }
214
-
215
453
  /**
216
- * Emitter class to handle events
217
- * @example
218
- * const emitter = new Emitter()
219
- * emitter.on('event', (data) => {
220
- * console.log(data)
221
- * })
222
- * emitter.emit('event', 'data')
454
+ * Destroy the lenis instance, remove all event listeners and clean up the class name
223
455
  */
224
- class Emitter {
225
- constructor() {
226
- this.events = {};
227
- }
228
- /**
229
- * Emit an event with the given data
230
- * @param event Event name
231
- * @param args Data to pass to the event handlers
232
- */
233
- emit(event, ...args) {
234
- var _a;
235
- let callbacks = this.events[event] || [];
236
- for (let i = 0, length = callbacks.length; i < length; i++) {
237
- (_a = callbacks[i]) === null || _a === void 0 ? void 0 : _a.call(callbacks, ...args);
238
- }
239
- }
240
- /**
241
- * Add a callback to the event
242
- * @param event Event name
243
- * @param cb Callback function
244
- * @returns Unsubscribe function
245
- */
246
- on(event, cb) {
247
- var _a;
248
- // Add the callback to the event's callback list, or create a new list with the callback
249
- ((_a = this.events[event]) === null || _a === void 0 ? void 0 : _a.push(cb)) || (this.events[event] = [cb]);
250
- // Return an unsubscribe function
251
- return () => {
252
- var _a;
253
- this.events[event] = (_a = this.events[event]) === null || _a === void 0 ? void 0 : _a.filter((i) => cb !== i);
254
- };
255
- }
256
- /**
257
- * Remove a callback from the event
258
- * @param event Event name
259
- * @param callback Callback function
260
- */
261
- off(event, callback) {
262
- var _a;
263
- this.events[event] = (_a = this.events[event]) === null || _a === void 0 ? void 0 : _a.filter((i) => callback !== i);
264
- }
265
- /**
266
- * Remove all event listeners and clean up
267
- */
268
- destroy() {
269
- this.events = {};
270
- }
456
+ destroy() {
457
+ this.emitter.destroy();
458
+ this.options.wrapper.removeEventListener(
459
+ "scroll",
460
+ this.onNativeScroll,
461
+ false
462
+ );
463
+ this.options.wrapper.removeEventListener(
464
+ "pointerdown",
465
+ this.onPointerDown,
466
+ false
467
+ );
468
+ this.virtualScroll.destroy();
469
+ this.dimensions.destroy();
470
+ this.cleanUpClassName();
271
471
  }
272
-
273
- const LINE_HEIGHT = 100 / 6;
274
- const listenerOptions = { passive: false };
275
- class VirtualScroll {
276
- constructor(element, options = { wheelMultiplier: 1, touchMultiplier: 1 }) {
277
- this.element = element;
278
- this.options = options;
279
- this.touchStart = {
280
- x: 0,
281
- y: 0,
282
- };
283
- this.lastDelta = {
284
- x: 0,
285
- y: 0,
286
- };
287
- this.window = {
288
- width: 0,
289
- height: 0,
290
- };
291
- this.emitter = new Emitter();
292
- /**
293
- * Event handler for 'touchstart' event
294
- *
295
- * @param event Touch event
296
- */
297
- this.onTouchStart = (event) => {
298
- // @ts-expect-error - event.targetTouches is not defined
299
- const { clientX, clientY } = event.targetTouches
300
- ? event.targetTouches[0]
301
- : event;
302
- this.touchStart.x = clientX;
303
- this.touchStart.y = clientY;
304
- this.lastDelta = {
305
- x: 0,
306
- y: 0,
307
- };
308
- this.emitter.emit('scroll', {
309
- deltaX: 0,
310
- deltaY: 0,
311
- event,
312
- });
313
- };
314
- /** Event handler for 'touchmove' event */
315
- this.onTouchMove = (event) => {
316
- // @ts-expect-error - event.targetTouches is not defined
317
- const { clientX, clientY } = event.targetTouches
318
- ? event.targetTouches[0]
319
- : event;
320
- const deltaX = -(clientX - this.touchStart.x) * this.options.touchMultiplier;
321
- const deltaY = -(clientY - this.touchStart.y) * this.options.touchMultiplier;
322
- this.touchStart.x = clientX;
323
- this.touchStart.y = clientY;
324
- this.lastDelta = {
325
- x: deltaX,
326
- y: deltaY,
327
- };
328
- this.emitter.emit('scroll', {
329
- deltaX,
330
- deltaY,
331
- event,
332
- });
333
- };
334
- this.onTouchEnd = (event) => {
335
- this.emitter.emit('scroll', {
336
- deltaX: this.lastDelta.x,
337
- deltaY: this.lastDelta.y,
338
- event,
339
- });
340
- };
341
- /** Event handler for 'wheel' event */
342
- this.onWheel = (event) => {
343
- let { deltaX, deltaY, deltaMode } = event;
344
- const multiplierX = deltaMode === 1 ? LINE_HEIGHT : deltaMode === 2 ? this.window.width : 1;
345
- const multiplierY = deltaMode === 1 ? LINE_HEIGHT : deltaMode === 2 ? this.window.height : 1;
346
- deltaX *= multiplierX;
347
- deltaY *= multiplierY;
348
- deltaX *= this.options.wheelMultiplier;
349
- deltaY *= this.options.wheelMultiplier;
350
- this.emitter.emit('scroll', { deltaX, deltaY, event });
351
- };
352
- this.onWindowResize = () => {
353
- this.window = {
354
- width: window.innerWidth,
355
- height: window.innerHeight,
356
- };
357
- };
358
- window.addEventListener('resize', this.onWindowResize, false);
359
- this.onWindowResize();
360
- this.element.addEventListener('wheel', this.onWheel, listenerOptions);
361
- this.element.addEventListener('touchstart', this.onTouchStart, listenerOptions);
362
- this.element.addEventListener('touchmove', this.onTouchMove, listenerOptions);
363
- this.element.addEventListener('touchend', this.onTouchEnd, listenerOptions);
364
- }
365
- /**
366
- * Add an event listener for the given event and callback
367
- *
368
- * @param event Event name
369
- * @param callback Callback function
370
- */
371
- on(event, callback) {
372
- return this.emitter.on(event, callback);
373
- }
374
- /** Remove all event listeners and clean up */
375
- destroy() {
376
- this.emitter.destroy();
377
- window.removeEventListener('resize', this.onWindowResize, false);
378
- this.element.removeEventListener('wheel', this.onWheel, listenerOptions);
379
- this.element.removeEventListener('touchstart', this.onTouchStart, listenerOptions);
380
- this.element.removeEventListener('touchmove', this.onTouchMove, listenerOptions);
381
- this.element.removeEventListener('touchend', this.onTouchEnd, listenerOptions);
382
- }
472
+ on(event, callback) {
473
+ return this.emitter.on(event, callback);
383
474
  }
384
-
385
- class Lenis {
386
- constructor({ wrapper = window, content = document.documentElement, eventsTarget = wrapper, smoothWheel = true, syncTouch = false, syncTouchLerp = 0.075, touchInertiaMultiplier = 35, duration, // in seconds
387
- easing = (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), lerp = 0.1, infinite = false, orientation = 'vertical', // vertical, horizontal
388
- gestureOrientation = 'vertical', // vertical, horizontal, both
389
- touchMultiplier = 1, wheelMultiplier = 1, autoResize = true, prevent, virtualScroll, __experimental__naiveDimensions = false, } = {}) {
390
- this._isScrolling = false; // true when scroll is animating
391
- this._isStopped = false; // true if user should not be able to scroll - enable/disable programmatically
392
- this._isLocked = false; // same as isStopped but enabled/disabled when scroll reaches target
393
- this._preventNextNativeScrollEvent = false;
394
- this._resetVelocityTimeout = null;
395
- /**
396
- * The time in ms since the lenis instance was created
397
- */
398
- this.time = 0;
399
- /**
400
- * User data that will be forwarded through the scroll event
401
- *
402
- * @example
403
- * lenis.scrollTo(100, {
404
- * userData: {
405
- * foo: 'bar'
406
- * }
407
- * })
408
- */
409
- this.userData = {};
410
- /**
411
- * The last velocity of the scroll
412
- */
413
- this.lastVelocity = 0;
414
- /**
415
- * The current velocity of the scroll
416
- */
475
+ off(event, callback) {
476
+ return this.emitter.off(event, callback);
477
+ }
478
+ setScroll(scroll) {
479
+ if (this.isHorizontal) {
480
+ this.rootElement.scrollLeft = scroll;
481
+ } else {
482
+ this.rootElement.scrollTop = scroll;
483
+ }
484
+ }
485
+ onPointerDown = (event) => {
486
+ if (event.button === 1) {
487
+ this.reset();
488
+ }
489
+ };
490
+ onVirtualScroll = (data) => {
491
+ if (typeof this.options.virtualScroll === "function" && this.options.virtualScroll(data) === false)
492
+ return;
493
+ const { deltaX, deltaY, event } = data;
494
+ this.emitter.emit("virtual-scroll", { deltaX, deltaY, event });
495
+ if (event.ctrlKey) return;
496
+ const isTouch = event.type.includes("touch");
497
+ const isWheel = event.type.includes("wheel");
498
+ this.isTouching = event.type === "touchstart" || event.type === "touchmove";
499
+ const isTapToStop = this.options.syncTouch && isTouch && event.type === "touchstart" && !this.isStopped && !this.isLocked;
500
+ if (isTapToStop) {
501
+ this.reset();
502
+ return;
503
+ }
504
+ const isClick = deltaX === 0 && deltaY === 0;
505
+ const isUnknownGesture = this.options.gestureOrientation === "vertical" && deltaY === 0 || this.options.gestureOrientation === "horizontal" && deltaX === 0;
506
+ if (isClick || isUnknownGesture) {
507
+ return;
508
+ }
509
+ let composedPath = event.composedPath();
510
+ composedPath = composedPath.slice(0, composedPath.indexOf(this.rootElement));
511
+ const prevent = this.options.prevent;
512
+ if (!!composedPath.find(
513
+ (node) => node instanceof HTMLElement && (typeof prevent === "function" && prevent?.(node) || node.hasAttribute?.("data-lenis-prevent") || isTouch && node.hasAttribute?.("data-lenis-prevent-touch") || isWheel && node.hasAttribute?.("data-lenis-prevent-wheel") || node.classList?.contains("lenis") && !node.classList?.contains("lenis-stopped"))
514
+ // nested lenis instance
515
+ ))
516
+ return;
517
+ if (this.isStopped || this.isLocked) {
518
+ event.preventDefault();
519
+ return;
520
+ }
521
+ const isSmooth = this.options.syncTouch && isTouch || this.options.smoothWheel && isWheel;
522
+ if (!isSmooth) {
523
+ this.isScrolling = "native";
524
+ this.animate.stop();
525
+ return;
526
+ }
527
+ event.preventDefault();
528
+ let delta = deltaY;
529
+ if (this.options.gestureOrientation === "both") {
530
+ delta = Math.abs(deltaY) > Math.abs(deltaX) ? deltaY : deltaX;
531
+ } else if (this.options.gestureOrientation === "horizontal") {
532
+ delta = deltaX;
533
+ }
534
+ const syncTouch = isTouch && this.options.syncTouch;
535
+ const isTouchEnd = isTouch && event.type === "touchend";
536
+ const hasTouchInertia = isTouchEnd && Math.abs(delta) > 5;
537
+ if (hasTouchInertia) {
538
+ delta = this.velocity * this.options.touchInertiaMultiplier;
539
+ }
540
+ this.scrollTo(this.targetScroll + delta, {
541
+ programmatic: false,
542
+ ...syncTouch ? {
543
+ lerp: hasTouchInertia ? this.options.syncTouchLerp : 1
544
+ } : {
545
+ lerp: this.options.lerp,
546
+ duration: this.options.duration,
547
+ easing: this.options.easing
548
+ }
549
+ });
550
+ };
551
+ /**
552
+ * Force lenis to recalculate the dimensions
553
+ */
554
+ resize() {
555
+ this.dimensions.resize();
556
+ this.animatedScroll = this.targetScroll = this.actualScroll;
557
+ this.emit();
558
+ }
559
+ emit() {
560
+ this.emitter.emit("scroll", this);
561
+ }
562
+ onNativeScroll = () => {
563
+ if (this._resetVelocityTimeout !== null) {
564
+ clearTimeout(this._resetVelocityTimeout);
565
+ this._resetVelocityTimeout = null;
566
+ }
567
+ if (this._preventNextNativeScrollEvent) {
568
+ this._preventNextNativeScrollEvent = false;
569
+ return;
570
+ }
571
+ if (this.isScrolling === false || this.isScrolling === "native") {
572
+ const lastScroll = this.animatedScroll;
573
+ this.animatedScroll = this.targetScroll = this.actualScroll;
574
+ this.lastVelocity = this.velocity;
575
+ this.velocity = this.animatedScroll - lastScroll;
576
+ this.direction = Math.sign(
577
+ this.animatedScroll - lastScroll
578
+ );
579
+ this.isScrolling = "native";
580
+ this.emit();
581
+ if (this.velocity !== 0) {
582
+ this._resetVelocityTimeout = setTimeout(() => {
583
+ this.lastVelocity = this.velocity;
417
584
  this.velocity = 0;
418
- /**
419
- * The direction of the scroll
420
- */
421
- this.direction = 0;
422
- // These are instanciated here as they don't need information from the options
423
- this.animate = new Animate();
424
- this.emitter = new Emitter();
425
- this.onPointerDown = (event) => {
426
- if (event.button === 1) {
427
- this.reset();
428
- }
429
- };
430
- this.onVirtualScroll = (data) => {
431
- if (typeof this.options.virtualScroll === 'function' &&
432
- this.options.virtualScroll(data) === false)
433
- return;
434
- const { deltaX, deltaY, event } = data;
435
- this.emitter.emit('virtual-scroll', { deltaX, deltaY, event });
436
- // keep zoom feature
437
- if (event.ctrlKey)
438
- return;
439
- const isTouch = event.type.includes('touch');
440
- const isWheel = event.type.includes('wheel');
441
- this.isTouching = event.type === 'touchstart' || event.type === 'touchmove';
442
- // if (event.type === 'touchend') {
443
- // console.log('touchend', this.scroll)
444
- // // this.lastVelocity = this.velocity
445
- // // this.velocity = 0
446
- // // this.isScrolling = false
447
- // this.emit({ type: 'touchend' })
448
- // // alert('touchend')
449
- // return
450
- // }
451
- const isTapToStop = this.options.syncTouch &&
452
- isTouch &&
453
- event.type === 'touchstart' &&
454
- !this.isStopped &&
455
- !this.isLocked;
456
- if (isTapToStop) {
457
- this.reset();
458
- return;
459
- }
460
- const isClick = deltaX === 0 && deltaY === 0; // click event
461
- // const isPullToRefresh =
462
- // this.options.gestureOrientation === 'vertical' &&
463
- // this.scroll === 0 &&
464
- // !this.options.infinite &&
465
- // deltaY <= 5 // touch pull to refresh, not reliable yet
466
- const isUnknownGesture = (this.options.gestureOrientation === 'vertical' && deltaY === 0) ||
467
- (this.options.gestureOrientation === 'horizontal' && deltaX === 0);
468
- if (isClick || isUnknownGesture) {
469
- // console.log('prevent')
470
- return;
471
- }
472
- // catch if scrolling on nested scroll elements
473
- let composedPath = event.composedPath();
474
- composedPath = composedPath.slice(0, composedPath.indexOf(this.rootElement)); // remove parents elements
475
- const prevent = this.options.prevent;
476
- if (!!composedPath.find((node) => {
477
- var _a, _b, _c, _d, _e;
478
- return node instanceof HTMLElement &&
479
- ((typeof prevent === 'function' && (prevent === null || prevent === void 0 ? void 0 : prevent(node))) ||
480
- ((_a = node.hasAttribute) === null || _a === void 0 ? void 0 : _a.call(node, 'data-lenis-prevent')) ||
481
- (isTouch && ((_b = node.hasAttribute) === null || _b === void 0 ? void 0 : _b.call(node, 'data-lenis-prevent-touch'))) ||
482
- (isWheel && ((_c = node.hasAttribute) === null || _c === void 0 ? void 0 : _c.call(node, 'data-lenis-prevent-wheel'))) ||
483
- (((_d = node.classList) === null || _d === void 0 ? void 0 : _d.contains('lenis')) &&
484
- !((_e = node.classList) === null || _e === void 0 ? void 0 : _e.contains('lenis-stopped'))));
485
- } // nested lenis instance
486
- ))
487
- return;
488
- if (this.isStopped || this.isLocked) {
489
- event.preventDefault(); // this will stop forwarding the event to the parent, this is problematic
490
- return;
491
- }
492
- const isSmooth = (this.options.syncTouch && isTouch) ||
493
- (this.options.smoothWheel && isWheel);
494
- if (!isSmooth) {
495
- this.isScrolling = 'native';
496
- this.animate.stop();
497
- return;
498
- }
499
- event.preventDefault();
500
- let delta = deltaY;
501
- if (this.options.gestureOrientation === 'both') {
502
- delta = Math.abs(deltaY) > Math.abs(deltaX) ? deltaY : deltaX;
503
- }
504
- else if (this.options.gestureOrientation === 'horizontal') {
505
- delta = deltaX;
506
- }
507
- const syncTouch = isTouch && this.options.syncTouch;
508
- const isTouchEnd = isTouch && event.type === 'touchend';
509
- const hasTouchInertia = isTouchEnd && Math.abs(delta) > 5;
510
- if (hasTouchInertia) {
511
- delta = this.velocity * this.options.touchInertiaMultiplier;
512
- }
513
- this.scrollTo(this.targetScroll + delta, Object.assign({ programmatic: false }, (syncTouch
514
- ? {
515
- lerp: hasTouchInertia ? this.options.syncTouchLerp : 1,
516
- }
517
- : {
518
- lerp: this.options.lerp,
519
- duration: this.options.duration,
520
- easing: this.options.easing,
521
- })));
522
- };
523
- this.onNativeScroll = () => {
524
- if (this._resetVelocityTimeout !== null) {
525
- clearTimeout(this._resetVelocityTimeout);
526
- this._resetVelocityTimeout = null;
527
- }
528
- if (this._preventNextNativeScrollEvent) {
529
- this._preventNextNativeScrollEvent = false;
530
- return;
531
- }
532
- if (this.isScrolling === false || this.isScrolling === 'native') {
533
- const lastScroll = this.animatedScroll;
534
- this.animatedScroll = this.targetScroll = this.actualScroll;
535
- this.lastVelocity = this.velocity;
536
- this.velocity = this.animatedScroll - lastScroll;
537
- this.direction = Math.sign(this.animatedScroll - lastScroll);
538
- this.isScrolling = 'native';
539
- this.emit();
540
- if (this.velocity !== 0) {
541
- this._resetVelocityTimeout = setTimeout(() => {
542
- this.lastVelocity = this.velocity;
543
- this.velocity = 0;
544
- this.isScrolling = false;
545
- this.emit();
546
- }, 400);
547
- }
548
- }
549
- };
550
- // Set version
551
- window.lenisVersion = version;
552
- // Check if wrapper is html or body, fallback to window
553
- if (!wrapper ||
554
- wrapper === document.documentElement ||
555
- wrapper === document.body) {
556
- wrapper = window;
557
- }
558
- // Setup options
559
- this.options = {
560
- wrapper,
561
- content,
562
- eventsTarget,
563
- smoothWheel,
564
- syncTouch,
565
- syncTouchLerp,
566
- touchInertiaMultiplier,
567
- duration,
568
- easing,
569
- lerp,
570
- infinite,
571
- gestureOrientation,
572
- orientation,
573
- touchMultiplier,
574
- wheelMultiplier,
575
- autoResize,
576
- prevent,
577
- virtualScroll,
578
- __experimental__naiveDimensions,
579
- };
580
- // Setup dimensions instance
581
- this.dimensions = new Dimensions(wrapper, content, { autoResize });
582
- // Setup class name
583
- this.updateClassName();
584
- // Set the initial scroll value for all scroll information
585
- this.targetScroll = this.animatedScroll = this.actualScroll;
586
- // Add event listeners
587
- this.options.wrapper.addEventListener('scroll', this.onNativeScroll, false);
588
- this.options.wrapper.addEventListener('pointerdown', this.onPointerDown, false);
589
- // Setup virtual scroll instance
590
- this.virtualScroll = new VirtualScroll(eventsTarget, {
591
- touchMultiplier,
592
- wheelMultiplier,
593
- });
594
- this.virtualScroll.on('scroll', this.onVirtualScroll);
595
- }
596
- /**
597
- * Destroy the lenis instance, remove all event listeners and clean up the class name
598
- */
599
- destroy() {
600
- this.emitter.destroy();
601
- this.options.wrapper.removeEventListener('scroll', this.onNativeScroll, false);
602
- this.options.wrapper.removeEventListener('pointerdown', this.onPointerDown, false);
603
- this.virtualScroll.destroy();
604
- this.dimensions.destroy();
605
- this.cleanUpClassName();
606
- }
607
- on(event, callback) {
608
- return this.emitter.on(event, callback);
609
- }
610
- off(event, callback) {
611
- return this.emitter.off(event, callback);
612
- }
613
- setScroll(scroll) {
614
- // apply scroll value immediately
615
- if (this.isHorizontal) {
616
- this.rootElement.scrollLeft = scroll;
617
- }
618
- else {
619
- this.rootElement.scrollTop = scroll;
620
- }
621
- }
622
- /**
623
- * Force lenis to recalculate the dimensions
624
- */
625
- resize() {
626
- this.dimensions.resize();
627
- this.animatedScroll = this.targetScroll = this.actualScroll;
628
- this.emit();
629
- }
630
- emit() {
631
- this.emitter.emit('scroll', this);
632
- }
633
- reset() {
634
- this.isLocked = false;
635
585
  this.isScrolling = false;
636
- this.animatedScroll = this.targetScroll = this.actualScroll;
637
- this.lastVelocity = this.velocity = 0;
638
- this.animate.stop();
639
- }
640
- /**
641
- * Start lenis scroll after it has been stopped
642
- */
643
- start() {
644
- if (!this.isStopped)
645
- return;
646
- this.isStopped = false;
647
- this.reset();
648
- }
649
- /**
650
- * Stop lenis scroll
651
- */
652
- stop() {
653
- if (this.isStopped)
654
- return;
655
- this.isStopped = true;
656
- this.animate.stop();
586
+ this.emit();
587
+ }, 400);
588
+ }
589
+ }
590
+ };
591
+ reset() {
592
+ this.isLocked = false;
593
+ this.isScrolling = false;
594
+ this.animatedScroll = this.targetScroll = this.actualScroll;
595
+ this.lastVelocity = this.velocity = 0;
596
+ this.animate.stop();
597
+ }
598
+ /**
599
+ * Start lenis scroll after it has been stopped
600
+ */
601
+ start() {
602
+ if (!this.isStopped) return;
603
+ this.isStopped = false;
604
+ this.reset();
605
+ }
606
+ /**
607
+ * Stop lenis scroll
608
+ */
609
+ stop() {
610
+ if (this.isStopped) return;
611
+ this.isStopped = true;
612
+ this.animate.stop();
613
+ this.reset();
614
+ }
615
+ /**
616
+ * RequestAnimationFrame for lenis
617
+ *
618
+ * @param time The time in ms from an external clock like `requestAnimationFrame` or Tempus
619
+ */
620
+ raf(time) {
621
+ const deltaTime = time - (this.time || time);
622
+ this.time = time;
623
+ this.animate.advance(deltaTime * 1e-3);
624
+ }
625
+ /**
626
+ * Scroll to a target value
627
+ *
628
+ * @param target The target value to scroll to
629
+ * @param options The options for the scroll
630
+ *
631
+ * @example
632
+ * lenis.scrollTo(100, {
633
+ * offset: 100,
634
+ * duration: 1,
635
+ * easing: (t) => 1 - Math.cos((t * Math.PI) / 2),
636
+ * lerp: 0.1,
637
+ * onStart: () => {
638
+ * console.log('onStart')
639
+ * },
640
+ * onComplete: () => {
641
+ * console.log('onComplete')
642
+ * },
643
+ * })
644
+ */
645
+ scrollTo(target, {
646
+ offset = 0,
647
+ immediate = false,
648
+ lock = false,
649
+ duration = this.options.duration,
650
+ easing = this.options.easing,
651
+ lerp: lerp2 = this.options.lerp,
652
+ onStart,
653
+ onComplete,
654
+ force = false,
655
+ // scroll even if stopped
656
+ programmatic = true,
657
+ // called from outside of the class
658
+ userData
659
+ } = {}) {
660
+ if ((this.isStopped || this.isLocked) && !force) return;
661
+ if (typeof target === "string" && ["top", "left", "start"].includes(target)) {
662
+ target = 0;
663
+ } else if (typeof target === "string" && ["bottom", "right", "end"].includes(target)) {
664
+ target = this.limit;
665
+ } else {
666
+ let node;
667
+ if (typeof target === "string") {
668
+ node = document.querySelector(target);
669
+ } else if (target instanceof HTMLElement && target?.nodeType) {
670
+ node = target;
671
+ }
672
+ if (node) {
673
+ if (this.options.wrapper !== window) {
674
+ const wrapperRect = this.rootElement.getBoundingClientRect();
675
+ offset -= this.isHorizontal ? wrapperRect.left : wrapperRect.top;
676
+ }
677
+ const rect = node.getBoundingClientRect();
678
+ target = (this.isHorizontal ? rect.left : rect.top) + this.animatedScroll;
679
+ }
680
+ }
681
+ if (typeof target !== "number") return;
682
+ target += offset;
683
+ target = Math.round(target);
684
+ if (this.options.infinite) {
685
+ if (programmatic) {
686
+ this.targetScroll = this.animatedScroll = this.scroll;
687
+ }
688
+ } else {
689
+ target = clamp(0, target, this.limit);
690
+ }
691
+ if (target === this.targetScroll) {
692
+ onStart?.(this);
693
+ onComplete?.(this);
694
+ return;
695
+ }
696
+ this.userData = userData ?? {};
697
+ if (immediate) {
698
+ this.animatedScroll = this.targetScroll = target;
699
+ this.setScroll(this.scroll);
700
+ this.reset();
701
+ this.preventNextNativeScrollEvent();
702
+ this.emit();
703
+ onComplete?.(this);
704
+ this.userData = {};
705
+ return;
706
+ }
707
+ if (!programmatic) {
708
+ this.targetScroll = target;
709
+ }
710
+ this.animate.fromTo(this.animatedScroll, target, {
711
+ duration,
712
+ easing,
713
+ lerp: lerp2,
714
+ onStart: () => {
715
+ if (lock) this.isLocked = true;
716
+ this.isScrolling = "smooth";
717
+ onStart?.(this);
718
+ },
719
+ onUpdate: (value, completed) => {
720
+ this.isScrolling = "smooth";
721
+ this.lastVelocity = this.velocity;
722
+ this.velocity = value - this.animatedScroll;
723
+ this.direction = Math.sign(this.velocity);
724
+ this.animatedScroll = value;
725
+ this.setScroll(this.scroll);
726
+ if (programmatic) {
727
+ this.targetScroll = value;
728
+ }
729
+ if (!completed) this.emit();
730
+ if (completed) {
657
731
  this.reset();
732
+ this.emit();
733
+ onComplete?.(this);
734
+ this.userData = {};
735
+ this.preventNextNativeScrollEvent();
736
+ }
658
737
  }
659
- /**
660
- * RequestAnimationFrame for lenis
661
- *
662
- * @param time The time in ms from an external clock like `requestAnimationFrame` or Tempus
663
- */
664
- raf(time) {
665
- const deltaTime = time - (this.time || time);
666
- this.time = time;
667
- this.animate.advance(deltaTime * 0.001);
668
- }
669
- /**
670
- * Scroll to a target value
671
- *
672
- * @param target The target value to scroll to
673
- * @param options The options for the scroll
674
- *
675
- * @example
676
- * lenis.scrollTo(100, {
677
- * offset: 100,
678
- * duration: 1,
679
- * easing: (t) => 1 - Math.cos((t * Math.PI) / 2),
680
- * lerp: 0.1,
681
- * onStart: () => {
682
- * console.log('onStart')
683
- * },
684
- * onComplete: () => {
685
- * console.log('onComplete')
686
- * },
687
- * })
688
- */
689
- scrollTo(target, { offset = 0, immediate = false, lock = false, duration = this.options.duration, easing = this.options.easing, lerp = this.options.lerp, onStart, onComplete, force = false, // scroll even if stopped
690
- programmatic = true, // called from outside of the class
691
- userData, } = {}) {
692
- if ((this.isStopped || this.isLocked) && !force)
693
- return;
694
- // keywords
695
- if (typeof target === 'string' &&
696
- ['top', 'left', 'start'].includes(target)) {
697
- target = 0;
698
- }
699
- else if (typeof target === 'string' &&
700
- ['bottom', 'right', 'end'].includes(target)) {
701
- target = this.limit;
702
- }
703
- else {
704
- let node;
705
- if (typeof target === 'string') {
706
- // CSS selector
707
- node = document.querySelector(target);
708
- }
709
- else if (target instanceof HTMLElement && (target === null || target === void 0 ? void 0 : target.nodeType)) {
710
- // Node element
711
- node = target;
712
- }
713
- if (node) {
714
- if (this.options.wrapper !== window) {
715
- // nested scroll offset correction
716
- const wrapperRect = this.rootElement.getBoundingClientRect();
717
- offset -= this.isHorizontal ? wrapperRect.left : wrapperRect.top;
718
- }
719
- const rect = node.getBoundingClientRect();
720
- target =
721
- (this.isHorizontal ? rect.left : rect.top) + this.animatedScroll;
722
- }
723
- }
724
- if (typeof target !== 'number')
725
- return;
726
- target += offset;
727
- target = Math.round(target);
728
- if (this.options.infinite) {
729
- if (programmatic) {
730
- this.targetScroll = this.animatedScroll = this.scroll;
731
- }
732
- }
733
- else {
734
- target = clamp(0, target, this.limit);
735
- }
736
- if (target === this.targetScroll) {
737
- onStart === null || onStart === void 0 ? void 0 : onStart(this);
738
- onComplete === null || onComplete === void 0 ? void 0 : onComplete(this);
739
- return;
740
- }
741
- this.userData = userData !== null && userData !== void 0 ? userData : {};
742
- if (immediate) {
743
- this.animatedScroll = this.targetScroll = target;
744
- this.setScroll(this.scroll);
745
- this.reset();
746
- this.preventNextNativeScrollEvent();
747
- this.emit();
748
- onComplete === null || onComplete === void 0 ? void 0 : onComplete(this);
749
- this.userData = {};
750
- return;
751
- }
752
- if (!programmatic) {
753
- this.targetScroll = target;
754
- }
755
- this.animate.fromTo(this.animatedScroll, target, {
756
- duration,
757
- easing,
758
- lerp,
759
- onStart: () => {
760
- // started
761
- if (lock)
762
- this.isLocked = true;
763
- this.isScrolling = 'smooth';
764
- onStart === null || onStart === void 0 ? void 0 : onStart(this);
765
- },
766
- onUpdate: (value, completed) => {
767
- this.isScrolling = 'smooth';
768
- // updated
769
- this.lastVelocity = this.velocity;
770
- this.velocity = value - this.animatedScroll;
771
- this.direction = Math.sign(this.velocity);
772
- this.animatedScroll = value;
773
- this.setScroll(this.scroll);
774
- if (programmatic) {
775
- // wheel during programmatic should stop it
776
- this.targetScroll = value;
777
- }
778
- if (!completed)
779
- this.emit();
780
- if (completed) {
781
- this.reset();
782
- this.emit();
783
- onComplete === null || onComplete === void 0 ? void 0 : onComplete(this);
784
- this.userData = {};
785
- // avoid emitting event twice
786
- this.preventNextNativeScrollEvent();
787
- }
788
- },
789
- });
790
- }
791
- preventNextNativeScrollEvent() {
792
- this._preventNextNativeScrollEvent = true;
793
- requestAnimationFrame(() => {
794
- this._preventNextNativeScrollEvent = false;
795
- });
796
- }
797
- /**
798
- * The root element on which lenis is instanced
799
- */
800
- get rootElement() {
801
- return (this.options.wrapper === window
802
- ? document.documentElement
803
- : this.options.wrapper);
804
- }
805
- /**
806
- * The limit which is the maximum scroll value
807
- */
808
- get limit() {
809
- if (this.options.__experimental__naiveDimensions) {
810
- if (this.isHorizontal) {
811
- return this.rootElement.scrollWidth - this.rootElement.clientWidth;
812
- }
813
- else {
814
- return this.rootElement.scrollHeight - this.rootElement.clientHeight;
815
- }
816
- }
817
- else {
818
- return this.dimensions.limit[this.isHorizontal ? 'x' : 'y'];
819
- }
820
- }
821
- /**
822
- * Whether or not the scroll is horizontal
823
- */
824
- get isHorizontal() {
825
- return this.options.orientation === 'horizontal';
826
- }
827
- /**
828
- * The actual scroll value
829
- */
830
- get actualScroll() {
831
- // value browser takes into account
832
- return this.isHorizontal
833
- ? this.rootElement.scrollLeft
834
- : this.rootElement.scrollTop;
835
- }
836
- /**
837
- * The current scroll value
838
- */
839
- get scroll() {
840
- return this.options.infinite
841
- ? modulo(this.animatedScroll, this.limit)
842
- : this.animatedScroll;
843
- }
844
- /**
845
- * The progress of the scroll relative to the limit
846
- */
847
- get progress() {
848
- // avoid progress to be NaN
849
- return this.limit === 0 ? 1 : this.scroll / this.limit;
850
- }
851
- /**
852
- * Current scroll state
853
- */
854
- get isScrolling() {
855
- return this._isScrolling;
856
- }
857
- set isScrolling(value) {
858
- if (this._isScrolling !== value) {
859
- this._isScrolling = value;
860
- this.updateClassName();
861
- }
862
- }
863
- /**
864
- * Check if lenis is stopped
865
- */
866
- get isStopped() {
867
- return this._isStopped;
868
- }
869
- set isStopped(value) {
870
- if (this._isStopped !== value) {
871
- this._isStopped = value;
872
- this.updateClassName();
873
- }
874
- }
875
- /**
876
- * Check if lenis is locked
877
- */
878
- get isLocked() {
879
- return this._isLocked;
880
- }
881
- set isLocked(value) {
882
- if (this._isLocked !== value) {
883
- this._isLocked = value;
884
- this.updateClassName();
885
- }
886
- }
887
- /**
888
- * Check if lenis is smooth scrolling
889
- */
890
- get isSmooth() {
891
- return this.isScrolling === 'smooth';
892
- }
893
- /**
894
- * The class name applied to the wrapper element
895
- */
896
- get className() {
897
- let className = 'lenis';
898
- if (this.isStopped)
899
- className += ' lenis-stopped';
900
- if (this.isLocked)
901
- className += ' lenis-locked';
902
- if (this.isScrolling)
903
- className += ' lenis-scrolling';
904
- if (this.isScrolling === 'smooth')
905
- className += ' lenis-smooth';
906
- return className;
907
- }
908
- updateClassName() {
909
- this.cleanUpClassName();
910
- this.rootElement.className =
911
- `${this.rootElement.className} ${this.className}`.trim();
912
- }
913
- cleanUpClassName() {
914
- this.rootElement.className = this.rootElement.className
915
- .replace(/lenis(-\w+)?/g, '')
916
- .trim();
917
- }
738
+ });
918
739
  }
740
+ preventNextNativeScrollEvent() {
741
+ this._preventNextNativeScrollEvent = true;
742
+ requestAnimationFrame(() => {
743
+ this._preventNextNativeScrollEvent = false;
744
+ });
745
+ }
746
+ /**
747
+ * The root element on which lenis is instanced
748
+ */
749
+ get rootElement() {
750
+ return this.options.wrapper === window ? document.documentElement : this.options.wrapper;
751
+ }
752
+ /**
753
+ * The limit which is the maximum scroll value
754
+ */
755
+ get limit() {
756
+ if (this.options.__experimental__naiveDimensions) {
757
+ if (this.isHorizontal) {
758
+ return this.rootElement.scrollWidth - this.rootElement.clientWidth;
759
+ } else {
760
+ return this.rootElement.scrollHeight - this.rootElement.clientHeight;
761
+ }
762
+ } else {
763
+ return this.dimensions.limit[this.isHorizontal ? "x" : "y"];
764
+ }
765
+ }
766
+ /**
767
+ * Whether or not the scroll is horizontal
768
+ */
769
+ get isHorizontal() {
770
+ return this.options.orientation === "horizontal";
771
+ }
772
+ /**
773
+ * The actual scroll value
774
+ */
775
+ get actualScroll() {
776
+ return this.isHorizontal ? this.rootElement.scrollLeft : this.rootElement.scrollTop;
777
+ }
778
+ /**
779
+ * The current scroll value
780
+ */
781
+ get scroll() {
782
+ return this.options.infinite ? modulo(this.animatedScroll, this.limit) : this.animatedScroll;
783
+ }
784
+ /**
785
+ * The progress of the scroll relative to the limit
786
+ */
787
+ get progress() {
788
+ return this.limit === 0 ? 1 : this.scroll / this.limit;
789
+ }
790
+ /**
791
+ * Current scroll state
792
+ */
793
+ get isScrolling() {
794
+ return this._isScrolling;
795
+ }
796
+ set isScrolling(value) {
797
+ if (this._isScrolling !== value) {
798
+ this._isScrolling = value;
799
+ this.updateClassName();
800
+ }
801
+ }
802
+ /**
803
+ * Check if lenis is stopped
804
+ */
805
+ get isStopped() {
806
+ return this._isStopped;
807
+ }
808
+ set isStopped(value) {
809
+ if (this._isStopped !== value) {
810
+ this._isStopped = value;
811
+ this.updateClassName();
812
+ }
813
+ }
814
+ /**
815
+ * Check if lenis is locked
816
+ */
817
+ get isLocked() {
818
+ return this._isLocked;
819
+ }
820
+ set isLocked(value) {
821
+ if (this._isLocked !== value) {
822
+ this._isLocked = value;
823
+ this.updateClassName();
824
+ }
825
+ }
826
+ /**
827
+ * Check if lenis is smooth scrolling
828
+ */
829
+ get isSmooth() {
830
+ return this.isScrolling === "smooth";
831
+ }
832
+ /**
833
+ * The class name applied to the wrapper element
834
+ */
835
+ get className() {
836
+ let className = "lenis";
837
+ if (this.isStopped) className += " lenis-stopped";
838
+ if (this.isLocked) className += " lenis-locked";
839
+ if (this.isScrolling) className += " lenis-scrolling";
840
+ if (this.isScrolling === "smooth") className += " lenis-smooth";
841
+ return className;
842
+ }
843
+ updateClassName() {
844
+ this.cleanUpClassName();
845
+ this.rootElement.className = `${this.rootElement.className} ${this.className}`.trim();
846
+ }
847
+ cleanUpClassName() {
848
+ this.rootElement.className = this.rootElement.className.replace(/lenis(-\w+)?/g, "").trim();
849
+ }
850
+ };
919
851
 
920
- return Lenis;
921
-
922
- }));
923
- //# sourceMappingURL=lenis.js.map
852
+ // packages/core/browser.ts
853
+ globalThis.Lenis = Lenis;
854
+ //# sourceMappingURL=lenis.js.map