| 1 | //#region package.json
|
|---|
| 2 | var version = "1.3.26";
|
|---|
| 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 | */
|
|---|
| 13 | function clamp(min, input, max) {
|
|---|
| 14 | return Math.max(min, Math.min(input, max));
|
|---|
| 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 | */
|
|---|
| 24 | function lerp(x, y, t) {
|
|---|
| 25 | return (1 - t) * x + t * y;
|
|---|
| 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 | */
|
|---|
| 37 | function damp(x, y, lambda, deltaTime) {
|
|---|
| 38 | return lerp(x, y, 1 - Math.exp(-lambda * deltaTime));
|
|---|
| 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 | */
|
|---|
| 48 | function modulo(n, d) {
|
|---|
| 49 | return (n % d + d) % d;
|
|---|
| 50 | }
|
|---|
| 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 | */
|
|---|
| 61 | var Animate = class {
|
|---|
| 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) === Math.round(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 | }
|
|---|
| 121 | };
|
|---|
| 122 | //#endregion
|
|---|
| 123 | //#region packages/core/src/debounce.ts
|
|---|
| 124 | function debounce(callback, delay) {
|
|---|
| 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 | };
|
|---|
| 133 | }
|
|---|
| 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 | */
|
|---|
| 145 | var Dimensions = class {
|
|---|
| 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 | }
|
|---|
| 201 | };
|
|---|
| 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 | */
|
|---|
| 213 | var Emitter = class {
|
|---|
| 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 | }
|
|---|
| 251 | };
|
|---|
| 252 | //#endregion
|
|---|
| 253 | //#region packages/core/src/virtual-scroll.ts
|
|---|
| 254 | const LINE_HEIGHT = 100 / 6;
|
|---|
| 255 | const listenerOptions = { passive: false };
|
|---|
| 256 | function getDeltaMultiplier(deltaMode, size) {
|
|---|
| 257 | if (deltaMode === 1) return LINE_HEIGHT;
|
|---|
| 258 | if (deltaMode === 2) return size;
|
|---|
| 259 | return 1;
|
|---|
| 260 | }
|
|---|
| 261 | var VirtualScroll = class {
|
|---|
| 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 | };
|
|---|
| 370 | };
|
|---|
| 371 | //#endregion
|
|---|
| 372 | //#region packages/core/src/lenis.ts
|
|---|
| 373 | const defaultEasing = (t) => Math.min(1, 1.001 - 2 ** (-10 * t));
|
|---|
| 374 | var Lenis = class {
|
|---|
| 375 | _isScrolling = false;
|
|---|
| 376 | _isStopped = false;
|
|---|
| 377 | _isLocked = false;
|
|---|
| 378 | _preventNextNativeScrollEvent = false;
|
|---|
| 379 | _resetVelocityTimeout = null;
|
|---|
| 380 | _rafId = null;
|
|---|
| 381 | _isDraggingSelection = false;
|
|---|
| 382 | reducedMotionMediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|---|
| 383 | /**
|
|---|
| 384 | * Whether or not the user is touching the screen
|
|---|
| 385 | */
|
|---|
| 386 | isTouching;
|
|---|
| 387 | /**
|
|---|
| 388 | * Whether or not the device is running iOS
|
|---|
| 389 | */
|
|---|
| 390 | isIos;
|
|---|
| 391 | /**
|
|---|
| 392 | * The time in ms since the lenis instance was created
|
|---|
| 393 | */
|
|---|
| 394 | time = 0;
|
|---|
| 395 | /**
|
|---|
| 396 | * User data that will be forwarded through the scroll event
|
|---|
| 397 | *
|
|---|
| 398 | * @example
|
|---|
| 399 | * lenis.scrollTo(100, {
|
|---|
| 400 | * userData: {
|
|---|
| 401 | * foo: 'bar'
|
|---|
| 402 | * }
|
|---|
| 403 | * })
|
|---|
| 404 | */
|
|---|
| 405 | userData = {};
|
|---|
| 406 | /**
|
|---|
| 407 | * The last velocity of the scroll
|
|---|
| 408 | */
|
|---|
| 409 | lastVelocity = 0;
|
|---|
| 410 | /**
|
|---|
| 411 | * The current velocity of the scroll
|
|---|
| 412 | */
|
|---|
| 413 | velocity = 0;
|
|---|
| 414 | /**
|
|---|
| 415 | * The direction of the scroll
|
|---|
| 416 | */
|
|---|
| 417 | direction = 0;
|
|---|
| 418 | /**
|
|---|
| 419 | * The options passed to the lenis instance
|
|---|
| 420 | */
|
|---|
| 421 | options;
|
|---|
| 422 | /**
|
|---|
| 423 | * The target scroll value
|
|---|
| 424 | */
|
|---|
| 425 | targetScroll;
|
|---|
| 426 | /**
|
|---|
| 427 | * The animated scroll value
|
|---|
| 428 | */
|
|---|
| 429 | animatedScroll;
|
|---|
| 430 | animate = new Animate();
|
|---|
| 431 | emitter = new Emitter();
|
|---|
| 432 | dimensions;
|
|---|
| 433 | virtualScroll;
|
|---|
| 434 | 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, respectReducedMotion = true } = {}) {
|
|---|
| 435 | window.lenisVersion = version;
|
|---|
| 436 | if (!window.lenis) window.lenis = {};
|
|---|
| 437 | window.lenis.version = version;
|
|---|
| 438 | if (orientation === "horizontal") window.lenis.horizontal = true;
|
|---|
| 439 | if (syncTouch === true) window.lenis.touch = true;
|
|---|
| 440 | this.isIos = /(iPad|iPhone|iPod)/g.test(navigator.userAgent);
|
|---|
| 441 | if (!wrapper || wrapper === document.documentElement) wrapper = window;
|
|---|
| 442 | if (typeof duration === "number" && typeof easing !== "function") easing = defaultEasing;
|
|---|
| 443 | else if (typeof easing === "function" && typeof duration !== "number") duration = 1;
|
|---|
| 444 | this.options = {
|
|---|
| 445 | wrapper,
|
|---|
| 446 | content,
|
|---|
| 447 | eventsTarget,
|
|---|
| 448 | smoothWheel,
|
|---|
| 449 | syncTouch,
|
|---|
| 450 | syncTouchLerp,
|
|---|
| 451 | touchInertiaExponent,
|
|---|
| 452 | duration,
|
|---|
| 453 | easing,
|
|---|
| 454 | lerp,
|
|---|
| 455 | infinite,
|
|---|
| 456 | gestureOrientation,
|
|---|
| 457 | orientation,
|
|---|
| 458 | touchMultiplier,
|
|---|
| 459 | wheelMultiplier,
|
|---|
| 460 | autoResize,
|
|---|
| 461 | prevent,
|
|---|
| 462 | virtualScroll,
|
|---|
| 463 | overscroll,
|
|---|
| 464 | autoRaf,
|
|---|
| 465 | anchors,
|
|---|
| 466 | autoToggle,
|
|---|
| 467 | allowNestedScroll,
|
|---|
| 468 | naiveDimensions,
|
|---|
| 469 | stopInertiaOnNavigate,
|
|---|
| 470 | respectReducedMotion
|
|---|
| 471 | };
|
|---|
| 472 | this.dimensions = new Dimensions(wrapper, content, { autoResize });
|
|---|
| 473 | this.updateClassName();
|
|---|
| 474 | this.targetScroll = this.animatedScroll = this.actualScroll;
|
|---|
| 475 | this.options.wrapper.addEventListener("scroll", this.onNativeScroll);
|
|---|
| 476 | this.options.wrapper.addEventListener("scrollend", this.onScrollEnd, { capture: true });
|
|---|
| 477 | if (this.options.anchors || this.options.stopInertiaOnNavigate) this.options.wrapper.addEventListener("click", this.onClick);
|
|---|
| 478 | this.options.wrapper.addEventListener("pointerdown", this.onPointerDown);
|
|---|
| 479 | this.virtualScroll = new VirtualScroll(eventsTarget, {
|
|---|
| 480 | touchMultiplier,
|
|---|
| 481 | wheelMultiplier
|
|---|
| 482 | });
|
|---|
| 483 | this.virtualScroll.on("scroll", this.onVirtualScroll);
|
|---|
| 484 | if (this.options.autoToggle) {
|
|---|
| 485 | this.checkOverflow();
|
|---|
| 486 | this.rootElement.addEventListener("transitionend", this.onTransitionEnd);
|
|---|
| 487 | }
|
|---|
| 488 | if (this.options.autoRaf) this._rafId = requestAnimationFrame(this.raf);
|
|---|
| 489 | }
|
|---|
| 490 | /**
|
|---|
| 491 | * Destroy the lenis instance, remove all event listeners and clean up the class name
|
|---|
| 492 | */
|
|---|
| 493 | destroy() {
|
|---|
| 494 | this.emitter.destroy();
|
|---|
| 495 | this.options.wrapper.removeEventListener("scroll", this.onNativeScroll);
|
|---|
| 496 | this.options.wrapper.removeEventListener("scrollend", this.onScrollEnd, { capture: true });
|
|---|
| 497 | this.options.wrapper.removeEventListener("pointerdown", this.onPointerDown);
|
|---|
| 498 | if (this.options.anchors || this.options.stopInertiaOnNavigate) this.options.wrapper.removeEventListener("click", this.onClick);
|
|---|
| 499 | this.virtualScroll.destroy();
|
|---|
| 500 | this.dimensions.destroy();
|
|---|
| 501 | this.cleanUpClassName();
|
|---|
| 502 | if (this._rafId) cancelAnimationFrame(this._rafId);
|
|---|
| 503 | }
|
|---|
| 504 | on(event, callback) {
|
|---|
| 505 | return this.emitter.on(event, callback);
|
|---|
| 506 | }
|
|---|
| 507 | off(event, callback) {
|
|---|
| 508 | return this.emitter.off(event, callback);
|
|---|
| 509 | }
|
|---|
| 510 | onScrollEnd = (e) => {
|
|---|
| 511 | if (!(e instanceof CustomEvent)) {
|
|---|
| 512 | if (this.isScrolling === "smooth" || this.isScrolling === false) e.stopPropagation();
|
|---|
| 513 | }
|
|---|
| 514 | };
|
|---|
| 515 | dispatchScrollendEvent = () => {
|
|---|
| 516 | this.options.wrapper.dispatchEvent(new CustomEvent("scrollend", {
|
|---|
| 517 | bubbles: this.options.wrapper === window,
|
|---|
| 518 | detail: { lenisScrollEnd: true }
|
|---|
| 519 | }));
|
|---|
| 520 | };
|
|---|
| 521 | get overflow() {
|
|---|
| 522 | const property = this.isHorizontal ? "overflow-x" : "overflow-y";
|
|---|
| 523 | return getComputedStyle(this.rootElement)[property];
|
|---|
| 524 | }
|
|---|
| 525 | checkOverflow() {
|
|---|
| 526 | if (["hidden", "clip"].includes(this.overflow)) this.internalStop();
|
|---|
| 527 | else this.internalStart();
|
|---|
| 528 | }
|
|---|
| 529 | onTransitionEnd = (event) => {
|
|---|
| 530 | if (event.propertyName?.includes("overflow") && event.target === this.rootElement) this.checkOverflow();
|
|---|
| 531 | };
|
|---|
| 532 | setScroll(scroll) {
|
|---|
| 533 | if (this.isHorizontal) this.options.wrapper.scrollTo({
|
|---|
| 534 | left: scroll,
|
|---|
| 535 | behavior: "instant"
|
|---|
| 536 | });
|
|---|
| 537 | else this.options.wrapper.scrollTo({
|
|---|
| 538 | top: scroll,
|
|---|
| 539 | behavior: "instant"
|
|---|
| 540 | });
|
|---|
| 541 | }
|
|---|
| 542 | onClick = (event) => {
|
|---|
| 543 | const linkElementsUrls = event.composedPath().filter((node) => node instanceof HTMLAnchorElement && node.href).map((element) => new URL(element.href));
|
|---|
| 544 | const currentUrl = new URL(window.location.href);
|
|---|
| 545 | if (this.options.anchors) {
|
|---|
| 546 | const anchorElementUrl = linkElementsUrls.find((targetUrl) => currentUrl.host === targetUrl.host && currentUrl.pathname === targetUrl.pathname && targetUrl.hash);
|
|---|
| 547 | if (anchorElementUrl) {
|
|---|
| 548 | const options = typeof this.options.anchors === "object" && this.options.anchors ? this.options.anchors : void 0;
|
|---|
| 549 | const target = decodeURIComponent(anchorElementUrl.hash);
|
|---|
| 550 | this.scrollTo(target, options);
|
|---|
| 551 | return;
|
|---|
| 552 | }
|
|---|
| 553 | }
|
|---|
| 554 | if (this.options.stopInertiaOnNavigate) {
|
|---|
| 555 | if (linkElementsUrls.some((targetUrl) => currentUrl.host === targetUrl.host && currentUrl.pathname !== targetUrl.pathname)) {
|
|---|
| 556 | this.reset();
|
|---|
| 557 | return;
|
|---|
| 558 | }
|
|---|
| 559 | }
|
|---|
| 560 | };
|
|---|
| 561 | onPointerDown = (event) => {
|
|---|
| 562 | if (event.button === 1) this.reset();
|
|---|
| 563 | };
|
|---|
| 564 | isTouchOnSelectionHandle(event) {
|
|---|
| 565 | const selection = window.getSelection();
|
|---|
| 566 | if (!selection || selection.isCollapsed || selection.rangeCount === 0) return false;
|
|---|
| 567 | const touch = event.targetTouches[0] ?? event.changedTouches[0];
|
|---|
| 568 | if (!touch) return false;
|
|---|
| 569 | const rects = selection.getRangeAt(0).getClientRects();
|
|---|
| 570 | if (rects.length === 0) return false;
|
|---|
| 571 | const first = rects[0];
|
|---|
| 572 | const last = rects[rects.length - 1];
|
|---|
| 573 | const HANDLE_RADIUS = 40;
|
|---|
| 574 | const nearStart = Math.hypot(touch.clientX - first.left, touch.clientY - first.top) <= HANDLE_RADIUS;
|
|---|
| 575 | const nearEnd = Math.hypot(touch.clientX - last.right, touch.clientY - last.bottom) <= HANDLE_RADIUS;
|
|---|
| 576 | return nearStart || nearEnd;
|
|---|
| 577 | }
|
|---|
| 578 | onVirtualScroll = (data) => {
|
|---|
| 579 | if (typeof this.options.virtualScroll === "function" && this.options.virtualScroll(data) === false) return;
|
|---|
| 580 | const { deltaX, deltaY, event } = data;
|
|---|
| 581 | this.emitter.emit("virtual-scroll", {
|
|---|
| 582 | deltaX,
|
|---|
| 583 | deltaY,
|
|---|
| 584 | event
|
|---|
| 585 | });
|
|---|
| 586 | if (event.ctrlKey) return;
|
|---|
| 587 | if (event.lenisStopPropagation) return;
|
|---|
| 588 | const isTouch = event.type.includes("touch");
|
|---|
| 589 | const isWheel = event.type.includes("wheel");
|
|---|
| 590 | if (isTouch && this.isIos) {
|
|---|
| 591 | if (event.type === "touchstart") this._isDraggingSelection = this.isTouchOnSelectionHandle(event);
|
|---|
| 592 | if (this._isDraggingSelection) {
|
|---|
| 593 | if (event.type === "touchend") this._isDraggingSelection = false;
|
|---|
| 594 | return;
|
|---|
| 595 | }
|
|---|
| 596 | }
|
|---|
| 597 | this.isTouching = event.type === "touchstart" || event.type === "touchmove";
|
|---|
| 598 | const isClickOrTap = deltaX === 0 && deltaY === 0;
|
|---|
| 599 | if (this.options.syncTouch && isTouch && event.type === "touchstart" && isClickOrTap && !this.isStopped && !this.isLocked) {
|
|---|
| 600 | this.reset();
|
|---|
| 601 | return;
|
|---|
| 602 | }
|
|---|
| 603 | const isUnknownGesture = this.options.gestureOrientation === "vertical" && deltaY === 0 || this.options.gestureOrientation === "horizontal" && deltaX === 0;
|
|---|
| 604 | if (isClickOrTap || isUnknownGesture) return;
|
|---|
| 605 | let composedPath = event.composedPath();
|
|---|
| 606 | composedPath = composedPath.slice(0, composedPath.indexOf(this.rootElement));
|
|---|
| 607 | const prevent = this.options.prevent;
|
|---|
| 608 | const gestureOrientation = Math.abs(deltaX) >= Math.abs(deltaY) ? "horizontal" : "vertical";
|
|---|
| 609 | 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, {
|
|---|
| 610 | deltaX,
|
|---|
| 611 | deltaY
|
|---|
| 612 | })))) return;
|
|---|
| 613 | if (this.isStopped || this.isLocked) {
|
|---|
| 614 | if (event.cancelable) event.preventDefault();
|
|---|
| 615 | return;
|
|---|
| 616 | }
|
|---|
| 617 | if (!(this.options.syncTouch && isTouch || this.options.smoothWheel && isWheel)) {
|
|---|
| 618 | this.isScrolling = "native";
|
|---|
| 619 | this.animate.stop();
|
|---|
| 620 | event.lenisStopPropagation = true;
|
|---|
| 621 | return;
|
|---|
| 622 | }
|
|---|
| 623 | let delta = deltaY;
|
|---|
| 624 | if (this.options.gestureOrientation === "both") delta = Math.abs(deltaY) > Math.abs(deltaX) ? deltaY : deltaX;
|
|---|
| 625 | else if (this.options.gestureOrientation === "horizontal") delta = deltaX;
|
|---|
| 626 | 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;
|
|---|
| 627 | if (event.cancelable) event.preventDefault();
|
|---|
| 628 | const isSyncTouch = isTouch && this.options.syncTouch;
|
|---|
| 629 | const hasTouchInertia = isTouch && event.type === "touchend";
|
|---|
| 630 | if (hasTouchInertia) delta = Math.sign(delta) * Math.abs(this.velocity) ** this.options.touchInertiaExponent;
|
|---|
| 631 | this.scrollTo(this.targetScroll + delta, {
|
|---|
| 632 | programmatic: false,
|
|---|
| 633 | ...isSyncTouch ? { lerp: hasTouchInertia ? this.options.syncTouchLerp : 1 } : {
|
|---|
| 634 | lerp: this.options.lerp,
|
|---|
| 635 | duration: this.options.duration,
|
|---|
| 636 | easing: this.options.easing
|
|---|
| 637 | }
|
|---|
| 638 | });
|
|---|
| 639 | };
|
|---|
| 640 | /**
|
|---|
| 641 | * Force lenis to recalculate the dimensions
|
|---|
| 642 | */
|
|---|
| 643 | resize() {
|
|---|
| 644 | this.dimensions.resize();
|
|---|
| 645 | this.animatedScroll = this.targetScroll = this.actualScroll;
|
|---|
| 646 | this.emit();
|
|---|
| 647 | }
|
|---|
| 648 | emit() {
|
|---|
| 649 | this.emitter.emit("scroll", this);
|
|---|
| 650 | }
|
|---|
| 651 | onNativeScroll = () => {
|
|---|
| 652 | if (this._resetVelocityTimeout !== null) {
|
|---|
| 653 | clearTimeout(this._resetVelocityTimeout);
|
|---|
| 654 | this._resetVelocityTimeout = null;
|
|---|
| 655 | }
|
|---|
| 656 | if (this._preventNextNativeScrollEvent) {
|
|---|
| 657 | this._preventNextNativeScrollEvent = false;
|
|---|
| 658 | return;
|
|---|
| 659 | }
|
|---|
| 660 | if (this.isScrolling === false || this.isScrolling === "native") {
|
|---|
| 661 | const lastScroll = this.animatedScroll;
|
|---|
| 662 | this.animatedScroll = this.targetScroll = this.actualScroll;
|
|---|
| 663 | this.lastVelocity = this.velocity;
|
|---|
| 664 | this.velocity = this.animatedScroll - lastScroll;
|
|---|
| 665 | this.direction = Math.sign(this.animatedScroll - lastScroll);
|
|---|
| 666 | if (!this.isStopped) this.isScrolling = "native";
|
|---|
| 667 | this.emit();
|
|---|
| 668 | if (this.velocity !== 0) this._resetVelocityTimeout = setTimeout(() => {
|
|---|
| 669 | this.lastVelocity = this.velocity;
|
|---|
| 670 | this.velocity = 0;
|
|---|
| 671 | this.isScrolling = false;
|
|---|
| 672 | this.emit();
|
|---|
| 673 | }, 400);
|
|---|
| 674 | }
|
|---|
| 675 | };
|
|---|
| 676 | reset() {
|
|---|
| 677 | this.isLocked = false;
|
|---|
| 678 | this.isScrolling = false;
|
|---|
| 679 | this.animatedScroll = this.targetScroll = this.actualScroll;
|
|---|
| 680 | this.lastVelocity = this.velocity = 0;
|
|---|
| 681 | this.animate.stop();
|
|---|
| 682 | }
|
|---|
| 683 | /**
|
|---|
| 684 | * Start lenis scroll after it has been stopped
|
|---|
| 685 | */
|
|---|
| 686 | start() {
|
|---|
| 687 | if (!this.isStopped) return;
|
|---|
| 688 | if (this.options.autoToggle) {
|
|---|
| 689 | this.rootElement.style.removeProperty("overflow");
|
|---|
| 690 | return;
|
|---|
| 691 | }
|
|---|
| 692 | this.internalStart();
|
|---|
| 693 | }
|
|---|
| 694 | internalStart() {
|
|---|
| 695 | if (!this.isStopped) return;
|
|---|
| 696 | this.reset();
|
|---|
| 697 | this.isStopped = false;
|
|---|
| 698 | this.emit();
|
|---|
| 699 | }
|
|---|
| 700 | /**
|
|---|
| 701 | * Stop lenis scroll
|
|---|
| 702 | */
|
|---|
| 703 | stop() {
|
|---|
| 704 | if (this.isStopped) return;
|
|---|
| 705 | if (this.options.autoToggle) {
|
|---|
| 706 | this.rootElement.style.setProperty("overflow", "clip");
|
|---|
| 707 | return;
|
|---|
| 708 | }
|
|---|
| 709 | this.internalStop();
|
|---|
| 710 | }
|
|---|
| 711 | internalStop() {
|
|---|
| 712 | if (this.isStopped) return;
|
|---|
| 713 | this.reset();
|
|---|
| 714 | this.isStopped = true;
|
|---|
| 715 | this.emit();
|
|---|
| 716 | }
|
|---|
| 717 | /**
|
|---|
| 718 | * RequestAnimationFrame for lenis
|
|---|
| 719 | *
|
|---|
| 720 | * @param time The time in ms from an external clock like `requestAnimationFrame` or Tempus
|
|---|
| 721 | */
|
|---|
| 722 | raf = (time) => {
|
|---|
| 723 | const deltaTime = time - (this.time || time);
|
|---|
| 724 | this.time = time;
|
|---|
| 725 | this.animate.advance(deltaTime * .001);
|
|---|
| 726 | if (this.options.autoRaf) this._rafId = requestAnimationFrame(this.raf);
|
|---|
| 727 | };
|
|---|
| 728 | /**
|
|---|
| 729 | * Scroll to a target value
|
|---|
| 730 | *
|
|---|
| 731 | * @param target The target value to scroll to
|
|---|
| 732 | * @param options The options for the scroll
|
|---|
| 733 | *
|
|---|
| 734 | * @example
|
|---|
| 735 | * lenis.scrollTo(100, {
|
|---|
| 736 | * offset: 100,
|
|---|
| 737 | * duration: 1,
|
|---|
| 738 | * easing: (t) => 1 - Math.cos((t * Math.PI) / 2),
|
|---|
| 739 | * lerp: 0.1,
|
|---|
| 740 | * onStart: () => {
|
|---|
| 741 | * console.log('onStart')
|
|---|
| 742 | * },
|
|---|
| 743 | * onComplete: () => {
|
|---|
| 744 | * console.log('onComplete')
|
|---|
| 745 | * },
|
|---|
| 746 | * })
|
|---|
| 747 | */
|
|---|
| 748 | 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 } = {}) {
|
|---|
| 749 | if (this.prefersReducedMotion) if (programmatic) immediate = true;
|
|---|
| 750 | else {
|
|---|
| 751 | lerp = 1;
|
|---|
| 752 | duration = void 0;
|
|---|
| 753 | easing = void 0;
|
|---|
| 754 | }
|
|---|
| 755 | if ((this.isStopped || this.isLocked) && !force) return;
|
|---|
| 756 | let target = _target;
|
|---|
| 757 | let adjustedOffset = offset;
|
|---|
| 758 | if (typeof target === "string" && [
|
|---|
| 759 | "top",
|
|---|
| 760 | "left",
|
|---|
| 761 | "start",
|
|---|
| 762 | "#"
|
|---|
| 763 | ].includes(target)) target = 0;
|
|---|
| 764 | else if (typeof target === "string" && [
|
|---|
| 765 | "bottom",
|
|---|
| 766 | "right",
|
|---|
| 767 | "end"
|
|---|
| 768 | ].includes(target)) target = this.limit;
|
|---|
| 769 | else {
|
|---|
| 770 | let node = null;
|
|---|
| 771 | if (typeof target === "string") {
|
|---|
| 772 | node = target.startsWith("#") ? document.getElementById(target.slice(1)) : document.querySelector(target);
|
|---|
| 773 | if (!node) if (target === "#top") target = 0;
|
|---|
| 774 | else console.warn("Lenis: Target not found", target);
|
|---|
| 775 | } else if (target instanceof HTMLElement && target?.nodeType) node = target;
|
|---|
| 776 | if (node) {
|
|---|
| 777 | if (this.options.wrapper !== window) {
|
|---|
| 778 | const wrapperRect = this.rootElement.getBoundingClientRect();
|
|---|
| 779 | adjustedOffset -= this.isHorizontal ? wrapperRect.left : wrapperRect.top;
|
|---|
| 780 | }
|
|---|
| 781 | const rect = node.getBoundingClientRect();
|
|---|
| 782 | const targetStyle = getComputedStyle(node);
|
|---|
| 783 | const scrollMargin = this.isHorizontal ? Number.parseFloat(targetStyle.scrollMarginLeft) : Number.parseFloat(targetStyle.scrollMarginTop);
|
|---|
| 784 | const containerStyle = getComputedStyle(this.rootElement);
|
|---|
| 785 | const scrollPadding = this.isHorizontal ? Number.parseFloat(containerStyle.scrollPaddingLeft) : Number.parseFloat(containerStyle.scrollPaddingTop);
|
|---|
| 786 | target = (this.isHorizontal ? rect.left : rect.top) + this.animatedScroll - (Number.isNaN(scrollMargin) ? 0 : scrollMargin) - (Number.isNaN(scrollPadding) ? 0 : scrollPadding);
|
|---|
| 787 | }
|
|---|
| 788 | }
|
|---|
| 789 | if (typeof target !== "number") return;
|
|---|
| 790 | target += adjustedOffset;
|
|---|
| 791 | if (this.options.infinite) {
|
|---|
| 792 | if (programmatic) {
|
|---|
| 793 | this.targetScroll = this.animatedScroll = this.scroll;
|
|---|
| 794 | const distance = target - this.animatedScroll;
|
|---|
| 795 | if (distance > this.limit / 2) target -= this.limit;
|
|---|
| 796 | else if (distance < -this.limit / 2) target += this.limit;
|
|---|
| 797 | }
|
|---|
| 798 | } else target = clamp(0, target, this.limit);
|
|---|
| 799 | if (target === this.targetScroll) {
|
|---|
| 800 | onStart?.(this);
|
|---|
| 801 | onComplete?.(this);
|
|---|
| 802 | return;
|
|---|
| 803 | }
|
|---|
| 804 | this.userData = userData ?? {};
|
|---|
| 805 | if (immediate) {
|
|---|
| 806 | this.animatedScroll = this.targetScroll = target;
|
|---|
| 807 | this.setScroll(this.scroll);
|
|---|
| 808 | this.reset();
|
|---|
| 809 | this.preventNextNativeScrollEvent();
|
|---|
| 810 | this.emit();
|
|---|
| 811 | onComplete?.(this);
|
|---|
| 812 | this.userData = {};
|
|---|
| 813 | requestAnimationFrame(() => {
|
|---|
| 814 | this.dispatchScrollendEvent();
|
|---|
| 815 | });
|
|---|
| 816 | return;
|
|---|
| 817 | }
|
|---|
| 818 | if (!programmatic) this.targetScroll = target;
|
|---|
| 819 | if (typeof duration === "number" && typeof easing !== "function") easing = defaultEasing;
|
|---|
| 820 | else if (typeof easing === "function" && typeof duration !== "number") duration = 1;
|
|---|
| 821 | this.animate.fromTo(this.animatedScroll, target, {
|
|---|
| 822 | duration,
|
|---|
| 823 | easing,
|
|---|
| 824 | lerp,
|
|---|
| 825 | onStart: () => {
|
|---|
| 826 | if (lock) this.isLocked = true;
|
|---|
| 827 | this.isScrolling = "smooth";
|
|---|
| 828 | onStart?.(this);
|
|---|
| 829 | },
|
|---|
| 830 | onUpdate: (value, completed) => {
|
|---|
| 831 | this.isScrolling = "smooth";
|
|---|
| 832 | this.lastVelocity = this.velocity;
|
|---|
| 833 | this.velocity = value - this.animatedScroll;
|
|---|
| 834 | this.direction = Math.sign(this.velocity);
|
|---|
| 835 | this.animatedScroll = value;
|
|---|
| 836 | this.setScroll(this.scroll);
|
|---|
| 837 | if (programmatic) this.targetScroll = value;
|
|---|
| 838 | if (!completed) this.emit();
|
|---|
| 839 | if (completed) {
|
|---|
| 840 | this.reset();
|
|---|
| 841 | this.emit();
|
|---|
| 842 | onComplete?.(this);
|
|---|
| 843 | this.userData = {};
|
|---|
| 844 | requestAnimationFrame(() => {
|
|---|
| 845 | this.dispatchScrollendEvent();
|
|---|
| 846 | });
|
|---|
| 847 | this.preventNextNativeScrollEvent();
|
|---|
| 848 | }
|
|---|
| 849 | }
|
|---|
| 850 | });
|
|---|
| 851 | }
|
|---|
| 852 | preventNextNativeScrollEvent() {
|
|---|
| 853 | this._preventNextNativeScrollEvent = true;
|
|---|
| 854 | requestAnimationFrame(() => {
|
|---|
| 855 | this._preventNextNativeScrollEvent = false;
|
|---|
| 856 | });
|
|---|
| 857 | }
|
|---|
| 858 | hasNestedScroll(node, { deltaX, deltaY }) {
|
|---|
| 859 | const time = Date.now();
|
|---|
| 860 | if (!node._lenis) node._lenis = {};
|
|---|
| 861 | const cache = node._lenis;
|
|---|
| 862 | let hasOverflowX;
|
|---|
| 863 | let hasOverflowY;
|
|---|
| 864 | let isScrollableX;
|
|---|
| 865 | let isScrollableY;
|
|---|
| 866 | let hasOverscrollBehaviorX;
|
|---|
| 867 | let hasOverscrollBehaviorY;
|
|---|
| 868 | let scrollWidth;
|
|---|
| 869 | let scrollHeight;
|
|---|
| 870 | let clientWidth;
|
|---|
| 871 | let clientHeight;
|
|---|
| 872 | if (time - (cache.time ?? 0) > 2e3) {
|
|---|
| 873 | cache.time = Date.now();
|
|---|
| 874 | const computedStyle = window.getComputedStyle(node);
|
|---|
| 875 | cache.computedStyle = computedStyle;
|
|---|
| 876 | hasOverflowX = [
|
|---|
| 877 | "auto",
|
|---|
| 878 | "overlay",
|
|---|
| 879 | "scroll"
|
|---|
| 880 | ].includes(computedStyle.overflowX);
|
|---|
| 881 | hasOverflowY = [
|
|---|
| 882 | "auto",
|
|---|
| 883 | "overlay",
|
|---|
| 884 | "scroll"
|
|---|
| 885 | ].includes(computedStyle.overflowY);
|
|---|
| 886 | hasOverscrollBehaviorX = ["auto"].includes(computedStyle.overscrollBehaviorX);
|
|---|
| 887 | hasOverscrollBehaviorY = ["auto"].includes(computedStyle.overscrollBehaviorY);
|
|---|
| 888 | cache.hasOverflowX = hasOverflowX;
|
|---|
| 889 | cache.hasOverflowY = hasOverflowY;
|
|---|
| 890 | if (!(hasOverflowX || hasOverflowY)) return false;
|
|---|
| 891 | scrollWidth = node.scrollWidth;
|
|---|
| 892 | scrollHeight = node.scrollHeight;
|
|---|
| 893 | clientWidth = node.clientWidth;
|
|---|
| 894 | clientHeight = node.clientHeight;
|
|---|
| 895 | isScrollableX = scrollWidth > clientWidth;
|
|---|
| 896 | isScrollableY = scrollHeight > clientHeight;
|
|---|
| 897 | cache.isScrollableX = isScrollableX;
|
|---|
| 898 | cache.isScrollableY = isScrollableY;
|
|---|
| 899 | cache.scrollWidth = scrollWidth;
|
|---|
| 900 | cache.scrollHeight = scrollHeight;
|
|---|
| 901 | cache.clientWidth = clientWidth;
|
|---|
| 902 | cache.clientHeight = clientHeight;
|
|---|
| 903 | cache.hasOverscrollBehaviorX = hasOverscrollBehaviorX;
|
|---|
| 904 | cache.hasOverscrollBehaviorY = hasOverscrollBehaviorY;
|
|---|
| 905 | } else {
|
|---|
| 906 | isScrollableX = cache.isScrollableX;
|
|---|
| 907 | isScrollableY = cache.isScrollableY;
|
|---|
| 908 | hasOverflowX = cache.hasOverflowX;
|
|---|
| 909 | hasOverflowY = cache.hasOverflowY;
|
|---|
| 910 | scrollWidth = cache.scrollWidth;
|
|---|
| 911 | scrollHeight = cache.scrollHeight;
|
|---|
| 912 | clientWidth = cache.clientWidth;
|
|---|
| 913 | clientHeight = cache.clientHeight;
|
|---|
| 914 | hasOverscrollBehaviorX = cache.hasOverscrollBehaviorX;
|
|---|
| 915 | hasOverscrollBehaviorY = cache.hasOverscrollBehaviorY;
|
|---|
| 916 | }
|
|---|
| 917 | if (!(hasOverflowX && isScrollableX || hasOverflowY && isScrollableY)) return false;
|
|---|
| 918 | const orientation = Math.abs(deltaX) >= Math.abs(deltaY) ? "horizontal" : "vertical";
|
|---|
| 919 | let scroll;
|
|---|
| 920 | let maxScroll;
|
|---|
| 921 | let delta;
|
|---|
| 922 | let hasOverflow;
|
|---|
| 923 | let isScrollable;
|
|---|
| 924 | let hasOverscrollBehavior;
|
|---|
| 925 | if (orientation === "horizontal") {
|
|---|
| 926 | scroll = Math.round(node.scrollLeft);
|
|---|
| 927 | maxScroll = scrollWidth - clientWidth;
|
|---|
| 928 | delta = deltaX;
|
|---|
| 929 | hasOverflow = hasOverflowX;
|
|---|
| 930 | isScrollable = isScrollableX;
|
|---|
| 931 | hasOverscrollBehavior = hasOverscrollBehaviorX;
|
|---|
| 932 | } else if (orientation === "vertical") {
|
|---|
| 933 | scroll = Math.round(node.scrollTop);
|
|---|
| 934 | maxScroll = scrollHeight - clientHeight;
|
|---|
| 935 | delta = deltaY;
|
|---|
| 936 | hasOverflow = hasOverflowY;
|
|---|
| 937 | isScrollable = isScrollableY;
|
|---|
| 938 | hasOverscrollBehavior = hasOverscrollBehaviorY;
|
|---|
| 939 | } else return false;
|
|---|
| 940 | if (!hasOverscrollBehavior && (scroll >= maxScroll || scroll <= 0)) return true;
|
|---|
| 941 | return (delta > 0 ? scroll < maxScroll : scroll > 0) && hasOverflow && isScrollable;
|
|---|
| 942 | }
|
|---|
| 943 | /**
|
|---|
| 944 | * The root element on which lenis is instanced
|
|---|
| 945 | */
|
|---|
| 946 | get rootElement() {
|
|---|
| 947 | return this.options.wrapper === window ? document.documentElement : this.options.wrapper;
|
|---|
| 948 | }
|
|---|
| 949 | /**
|
|---|
| 950 | * The limit which is the maximum scroll value
|
|---|
| 951 | */
|
|---|
| 952 | get limit() {
|
|---|
| 953 | if (this.options.naiveDimensions) {
|
|---|
| 954 | if (this.isHorizontal) return this.rootElement.scrollWidth - this.rootElement.clientWidth;
|
|---|
| 955 | return this.rootElement.scrollHeight - this.rootElement.clientHeight;
|
|---|
| 956 | }
|
|---|
| 957 | return this.dimensions.limit[this.isHorizontal ? "x" : "y"];
|
|---|
| 958 | }
|
|---|
| 959 | /**
|
|---|
| 960 | * Whether or not the scroll is horizontal
|
|---|
| 961 | */
|
|---|
| 962 | get isHorizontal() {
|
|---|
| 963 | return this.options.orientation === "horizontal";
|
|---|
| 964 | }
|
|---|
| 965 | /**
|
|---|
| 966 | * The actual scroll value
|
|---|
| 967 | */
|
|---|
| 968 | get actualScroll() {
|
|---|
| 969 | const wrapper = this.options.wrapper;
|
|---|
| 970 | return this.isHorizontal ? wrapper.scrollX ?? wrapper.scrollLeft : wrapper.scrollY ?? wrapper.scrollTop;
|
|---|
| 971 | }
|
|---|
| 972 | /**
|
|---|
| 973 | * The current scroll value
|
|---|
| 974 | */
|
|---|
| 975 | get scroll() {
|
|---|
| 976 | return this.options.infinite ? modulo(this.animatedScroll, this.limit) : this.animatedScroll;
|
|---|
| 977 | }
|
|---|
| 978 | /**
|
|---|
| 979 | * The progress of the scroll relative to the limit
|
|---|
| 980 | */
|
|---|
| 981 | get progress() {
|
|---|
| 982 | return this.limit === 0 ? 1 : this.scroll / this.limit;
|
|---|
| 983 | }
|
|---|
| 984 | /**
|
|---|
| 985 | * Current scroll state
|
|---|
| 986 | */
|
|---|
| 987 | get isScrolling() {
|
|---|
| 988 | return this._isScrolling;
|
|---|
| 989 | }
|
|---|
| 990 | set isScrolling(value) {
|
|---|
| 991 | if (this._isScrolling !== value) {
|
|---|
| 992 | this._isScrolling = value;
|
|---|
| 993 | this.updateClassName();
|
|---|
| 994 | }
|
|---|
| 995 | }
|
|---|
| 996 | /**
|
|---|
| 997 | * Check if lenis is stopped
|
|---|
| 998 | */
|
|---|
| 999 | get isStopped() {
|
|---|
| 1000 | return this._isStopped;
|
|---|
| 1001 | }
|
|---|
| 1002 | set isStopped(value) {
|
|---|
| 1003 | if (this._isStopped !== value) {
|
|---|
| 1004 | this._isStopped = value;
|
|---|
| 1005 | this.updateClassName();
|
|---|
| 1006 | }
|
|---|
| 1007 | }
|
|---|
| 1008 | /**
|
|---|
| 1009 | * Check if lenis is locked
|
|---|
| 1010 | */
|
|---|
| 1011 | get isLocked() {
|
|---|
| 1012 | return this._isLocked;
|
|---|
| 1013 | }
|
|---|
| 1014 | set isLocked(value) {
|
|---|
| 1015 | if (this._isLocked !== value) {
|
|---|
| 1016 | this._isLocked = value;
|
|---|
| 1017 | this.updateClassName();
|
|---|
| 1018 | }
|
|---|
| 1019 | }
|
|---|
| 1020 | /**
|
|---|
| 1021 | * Check if lenis is smooth scrolling
|
|---|
| 1022 | */
|
|---|
| 1023 | get isSmooth() {
|
|---|
| 1024 | return this.isScrolling === "smooth";
|
|---|
| 1025 | }
|
|---|
| 1026 | /**
|
|---|
| 1027 | * Whether the user prefers reduced motion and lenis is honoring it (see `respectReducedMotion` option)
|
|---|
| 1028 | */
|
|---|
| 1029 | get prefersReducedMotion() {
|
|---|
| 1030 | return this.options.respectReducedMotion && this.reducedMotionMediaQuery.matches;
|
|---|
| 1031 | }
|
|---|
| 1032 | /**
|
|---|
| 1033 | * The class name applied to the wrapper element
|
|---|
| 1034 | */
|
|---|
| 1035 | get className() {
|
|---|
| 1036 | let className = "lenis";
|
|---|
| 1037 | if (this.options.autoToggle) className += " lenis-autoToggle";
|
|---|
| 1038 | if (this.isStopped) className += " lenis-stopped";
|
|---|
| 1039 | if (this.isLocked) className += " lenis-locked";
|
|---|
| 1040 | if (this.isScrolling) className += " lenis-scrolling";
|
|---|
| 1041 | if (this.isScrolling === "smooth") className += " lenis-smooth";
|
|---|
| 1042 | return className;
|
|---|
| 1043 | }
|
|---|
| 1044 | updateClassName() {
|
|---|
| 1045 | this.cleanUpClassName();
|
|---|
| 1046 | this.className.split(" ").forEach((className) => {
|
|---|
| 1047 | this.rootElement.classList.add(className);
|
|---|
| 1048 | });
|
|---|
| 1049 | }
|
|---|
| 1050 | cleanUpClassName() {
|
|---|
| 1051 | for (const className of Array.from(this.rootElement.classList)) if (className === "lenis" || className.startsWith("lenis-")) this.rootElement.classList.remove(className);
|
|---|
| 1052 | }
|
|---|
| 1053 | };
|
|---|
| 1054 | //#endregion
|
|---|
| 1055 | export { Lenis as default };
|
|---|
| 1056 |
|
|---|
| 1057 | //# sourceMappingURL=lenis.mjs.map |
|---|