(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.Lenis = factory()); })(this, (function () { function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); } var version = "1.0.16"; // Clamp a value between a minimum and maximum value function clamp(min, input, max) { return Math.max(min, Math.min(input, max)); } // Linearly interpolate between two values using an amount (0 <= t <= 1) function lerp(x, y, t) { return (1 - t) * x + t * y; } // http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/ function damp(x, y, lambda, dt) { return lerp(x, y, 1 - Math.exp(-lambda * dt)); } // Calculate the modulo of the dividend and divisor while keeping the result within the same sign as the divisor // https://anguscroll.com/just/just-modulo function modulo(n, d) { return (n % d + d) % d; } // Animate class to handle value animations with lerping or easing var Animate = /*#__PURE__*/function () { function Animate() {} var _proto = Animate.prototype; // Advance the animation by the given delta time _proto.advance = function advance(deltaTime) { var _this$onUpdate; if (!this.isRunning) return; var completed = false; if (this.lerp) { this.value = damp(this.value, this.to, this.lerp * 60, deltaTime); if (Math.round(this.value) === this.to) { this.value = this.to; completed = true; } } else { this.currentTime += deltaTime; var linearProgress = clamp(0, this.currentTime / this.duration, 1); completed = linearProgress >= 1; var easedProgress = completed ? 1 : this.easing(linearProgress); this.value = this.from + (this.to - this.from) * easedProgress; } // Call the onUpdate callback with the current value and completed status (_this$onUpdate = this.onUpdate) == null ? void 0 : _this$onUpdate.call(this, this.value, { completed: completed }); if (completed) { this.stop(); } } // Stop the animation ; _proto.stop = function stop() { this.isRunning = false; } // Set up the animation from a starting value to an ending value // with optional parameters for lerping, duration, easing, and onUpdate callback ; _proto.fromTo = function fromTo(from, to, _ref) { var _ref$lerp = _ref.lerp, lerp = _ref$lerp === void 0 ? 0.1 : _ref$lerp, _ref$duration = _ref.duration, duration = _ref$duration === void 0 ? 1 : _ref$duration, _ref$easing = _ref.easing, easing = _ref$easing === void 0 ? function (t) { return t; } : _ref$easing, onUpdate = _ref.onUpdate; this.from = this.value = from; this.to = to; this.lerp = lerp; this.duration = duration; this.easing = easing; this.currentTime = 0; this.isRunning = true; this.onUpdate = onUpdate; }; return Animate; }(); function debounce(callback, delay) { var timer; return function () { var args = arguments; var context = this; clearTimeout(timer); timer = setTimeout(function () { callback.apply(context, args); }, delay); }; } var Dimensions = /*#__PURE__*/function () { function Dimensions(_temp) { var _this = this; var _ref = _temp === void 0 ? {} : _temp, wrapper = _ref.wrapper, content = _ref.content, _ref$autoResize = _ref.autoResize, autoResize = _ref$autoResize === void 0 ? true : _ref$autoResize; this.resize = function () { _this.onWrapperResize(); _this.onContentResize(); }; this.onWrapperResize = function () { if (_this.wrapper === window) { _this.width = window.innerWidth; _this.height = window.innerHeight; } else { _this.width = _this.wrapper.clientWidth; _this.height = _this.wrapper.clientHeight; } }; this.onContentResize = function () { _this.scrollHeight = _this.content.scrollHeight; _this.scrollWidth = _this.content.scrollWidth; }; this.wrapper = wrapper; this.content = content; if (autoResize) { var resize = debounce(this.resize, 250); if (this.wrapper !== window) { this.wrapperResizeObserver = new ResizeObserver(resize); this.wrapperResizeObserver.observe(this.wrapper); } this.contentResizeObserver = new ResizeObserver(resize); this.contentResizeObserver.observe(this.content); } this.resize(); } var _proto = Dimensions.prototype; _proto.destroy = function destroy() { var _this$wrapperResizeOb, _this$contentResizeOb; (_this$wrapperResizeOb = this.wrapperResizeObserver) == null ? void 0 : _this$wrapperResizeOb.disconnect(); (_this$contentResizeOb = this.contentResizeObserver) == null ? void 0 : _this$contentResizeOb.disconnect(); }; _createClass(Dimensions, [{ key: "limit", get: function get() { return { x: this.scrollWidth - this.width, y: this.scrollHeight - this.height }; } }]); return Dimensions; }(); var Emitter = /*#__PURE__*/function () { function Emitter() { this.events = {}; } var _proto = Emitter.prototype; _proto.emit = function emit(event) { var callbacks = this.events[event] || []; for (var i = 0, length = callbacks.length; i < length; i++) { callbacks[i].apply(callbacks, [].slice.call(arguments, 1)); } }; _proto.on = function on(event, cb) { var _this$events$event, _this = this; // Add the callback to the event's callback list, or create a new list with the callback ((_this$events$event = this.events[event]) == null ? void 0 : _this$events$event.push(cb)) || (this.events[event] = [cb]); // Return an unsubscribe function return function () { var _this$events$event2; _this.events[event] = (_this$events$event2 = _this.events[event]) == null ? void 0 : _this$events$event2.filter(function (i) { return cb !== i; }); }; }; _proto.destroy = function destroy() { this.events = {}; }; return Emitter; }(); var VirtualScroll = /*#__PURE__*/function () { function VirtualScroll(element, _ref) { var _this = this; var _ref$wheelMultiplier = _ref.wheelMultiplier, wheelMultiplier = _ref$wheelMultiplier === void 0 ? 1 : _ref$wheelMultiplier, _ref$touchMultiplier = _ref.touchMultiplier, touchMultiplier = _ref$touchMultiplier === void 0 ? 2 : _ref$touchMultiplier, _ref$normalizeWheel = _ref.normalizeWheel, normalizeWheel = _ref$normalizeWheel === void 0 ? false : _ref$normalizeWheel; // Event handler for 'touchstart' event this.onTouchStart = function (event) { var _ref2 = event.targetTouches ? event.targetTouches[0] : event, clientX = _ref2.clientX, clientY = _ref2.clientY; _this.touchStart.x = clientX; _this.touchStart.y = clientY; _this.lastDelta = { x: 0, y: 0 }; }; // Event handler for 'touchmove' event this.onTouchMove = function (event) { var _ref3 = event.targetTouches ? event.targetTouches[0] : event, clientX = _ref3.clientX, clientY = _ref3.clientY; var deltaX = -(clientX - _this.touchStart.x) * _this.touchMultiplier; var deltaY = -(clientY - _this.touchStart.y) * _this.touchMultiplier; _this.touchStart.x = clientX; _this.touchStart.y = clientY; _this.lastDelta = { x: deltaX, y: deltaY }; _this.emitter.emit('scroll', { type: 'touch', deltaX: deltaX, deltaY: deltaY, event: event }); }; this.onTouchEnd = function (event) { _this.emitter.emit('scroll', { type: 'touch', inertia: true, deltaX: _this.lastDelta.x, deltaY: _this.lastDelta.y, event: event }); }; // Event handler for 'wheel' event this.onWheel = function (event) { var deltaX = event.deltaX, deltaY = event.deltaY; if (_this.normalizeWheel) { deltaX = clamp(-100, deltaX, 100); deltaY = clamp(-100, deltaY, 100); } deltaX *= _this.wheelMultiplier; deltaY *= _this.wheelMultiplier; _this.emitter.emit('scroll', { type: 'wheel', deltaX: deltaX, deltaY: deltaY, event: event }); }; this.element = element; this.wheelMultiplier = wheelMultiplier; this.touchMultiplier = touchMultiplier; this.normalizeWheel = normalizeWheel; this.touchStart = { x: null, y: null }; this.emitter = new Emitter(); this.element.addEventListener('wheel', this.onWheel, { passive: false }); this.element.addEventListener('touchstart', this.onTouchStart, { passive: false }); this.element.addEventListener('touchmove', this.onTouchMove, { passive: false }); this.element.addEventListener('touchend', this.onTouchEnd, { passive: false }); } // Add an event listener for the given event and callback var _proto = VirtualScroll.prototype; _proto.on = function on(event, callback) { return this.emitter.on(event, callback); } // Remove all event listeners and clean up ; _proto.destroy = function destroy() { this.emitter.destroy(); this.element.removeEventListener('wheel', this.onWheel, { passive: false }); this.element.removeEventListener('touchstart', this.onTouchStart, { passive: false }); this.element.removeEventListener('touchmove', this.onTouchMove, { passive: false }); this.element.removeEventListener('touchend', this.onTouchEnd, { passive: false }); }; return VirtualScroll; }(); // Technical explaination // - listen to 'wheel' events // - prevent 'wheel' event to prevent scroll // - normalize wheel delta // - add delta to targetScroll // - animate scroll to targetScroll (smooth context) // - if animation is not running, listen to 'scroll' events (native context) var Lenis = /*#__PURE__*/function () { // isScrolling = true when scroll is animating // isStopped = true if user should not be able to scroll - enable/disable programatically // isSmooth = true if scroll should be animated // isLocked = same as isStopped but enabled/disabled when scroll reaches target /** * @typedef {(t: number) => number} EasingFunction * @typedef {'vertical' | 'horizontal'} Orientation * @typedef {'vertical' | 'horizontal' | 'both'} GestureOrientation * * @typedef LenisOptions * @property {Orientation} [direction] * @property {GestureOrientation} [gestureDirection] * @property {number} [mouseMultiplier] * @property {boolean} [smooth] * * @property {Window | HTMLElement} [wrapper] * @property {HTMLElement} [content] * @property {Window | HTMLElement} [wheelEventsTarget] * @property {boolean} [smoothWheel] * @property {boolean} [smoothTouch] * @property {boolean} [syncTouch] * @property {number} [syncTouchLerp] * @property {number} [touchInertiaMultiplier] * @property {number} [duration] * @property {EasingFunction} [easing] * @property {number} [lerp] * @property {boolean} [infinite] * @property {Orientation} [orientation] * @property {GestureOrientation} [gestureOrientation] * @property {number} [touchMultiplier] * @property {number} [wheelMultiplier] * @property {boolean} [normalizeWheel] * @property {boolean} [autoResize] * * @param {LenisOptions} */ function Lenis(_temp) { var _this = this; var _ref = _temp === void 0 ? {} : _temp, _ref$wrapper = _ref.wrapper, wrapper = _ref$wrapper === void 0 ? window : _ref$wrapper, _ref$content = _ref.content, content = _ref$content === void 0 ? document.documentElement : _ref$content, _ref$wheelEventsTarge = _ref.wheelEventsTarget, wheelEventsTarget = _ref$wheelEventsTarge === void 0 ? wrapper : _ref$wheelEventsTarge, _ref$smoothWheel = _ref.smoothWheel, smoothWheel = _ref$smoothWheel === void 0 ? true : _ref$smoothWheel, _ref$smoothTouch = _ref.smoothTouch, smoothTouch = _ref$smoothTouch === void 0 ? false : _ref$smoothTouch, _ref$syncTouch = _ref.syncTouch, _syncTouch = _ref$syncTouch === void 0 ? false : _ref$syncTouch, _ref$syncTouchLerp = _ref.syncTouchLerp, syncTouchLerp = _ref$syncTouchLerp === void 0 ? 0.1 : _ref$syncTouchLerp, _ref$__iosNoInertiaSy = _ref.__iosNoInertiaSyncTouchLerp, __iosNoInertiaSyncTouchLerp = _ref$__iosNoInertiaSy === void 0 ? 0.4 : _ref$__iosNoInertiaSy, _ref$touchInertiaMult = _ref.touchInertiaMultiplier, touchInertiaMultiplier = _ref$touchInertiaMult === void 0 ? 35 : _ref$touchInertiaMult, duration = _ref.duration, _ref$easing = _ref.easing, easing = _ref$easing === void 0 ? function (t) { return Math.min(1, 1.001 - Math.pow(2, -10 * t)); } : _ref$easing, _ref$lerp = _ref.lerp, lerp = _ref$lerp === void 0 ? duration && 0.1 : _ref$lerp, _ref$infinite = _ref.infinite, infinite = _ref$infinite === void 0 ? false : _ref$infinite, _ref$orientation = _ref.orientation, orientation = _ref$orientation === void 0 ? 'vertical' : _ref$orientation, _ref$gestureOrientati = _ref.gestureOrientation, gestureOrientation = _ref$gestureOrientati === void 0 ? 'vertical' : _ref$gestureOrientati, _ref$touchMultiplier = _ref.touchMultiplier, touchMultiplier = _ref$touchMultiplier === void 0 ? 1 : _ref$touchMultiplier, _ref$wheelMultiplier = _ref.wheelMultiplier, wheelMultiplier = _ref$wheelMultiplier === void 0 ? 1 : _ref$wheelMultiplier, _ref$normalizeWheel = _ref.normalizeWheel, normalizeWheel = _ref$normalizeWheel === void 0 ? false : _ref$normalizeWheel, _ref$autoResize = _ref.autoResize, autoResize = _ref$autoResize === void 0 ? true : _ref$autoResize; this.onVirtualScroll = function (_ref2) { var type = _ref2.type, inertia = _ref2.inertia, deltaX = _ref2.deltaX, deltaY = _ref2.deltaY, event = _ref2.event; // keep zoom feature if (event.ctrlKey) return; var isTouch = type === 'touch'; var isWheel = type === 'wheel'; if (_this.options.gestureOrientation === 'vertical' && deltaY === 0 || // trackpad previous/next page gesture _this.options.gestureOrientation === 'horizontal' && deltaX === 0 || isTouch && _this.options.gestureOrientation === 'vertical' && _this.scroll === 0 && !_this.options.infinite && deltaY <= 0 // touch pull to refresh ) return; // catch if scrolling on nested scroll elements if (!!event.composedPath().find(function (node) { return (node == null ? void 0 : node.hasAttribute == null ? void 0 : node.hasAttribute('data-lenis-prevent')) || isTouch && (node == null ? void 0 : node.hasAttribute == null ? void 0 : node.hasAttribute('data-lenis-prevent-touch')) || isWheel && (node == null ? void 0 : node.hasAttribute == null ? void 0 : node.hasAttribute('data-lenis-prevent-wheel')); })) return; if (_this.isStopped || _this.isLocked) { event.preventDefault(); return; } _this.isSmooth = (_this.options.smoothTouch || _this.options.syncTouch) && isTouch || _this.options.smoothWheel && isWheel; if (!_this.isSmooth) { _this.isScrolling = false; _this.animate.stop(); return; } event.preventDefault(); var delta = deltaY; if (_this.options.gestureOrientation === 'both') { delta = Math.abs(deltaY) > Math.abs(deltaX) ? deltaY : deltaX; } else if (_this.options.gestureOrientation === 'horizontal') { delta = deltaX; } var syncTouch = isTouch && _this.options.syncTouch; var hasTouchInertia = isTouch && inertia && Math.abs(delta) > 1; if (hasTouchInertia) { delta = _this.velocity * _this.options.touchInertiaMultiplier; } _this.scrollTo(_this.targetScroll + delta, _extends({ programmatic: false }, syncTouch && { lerp: hasTouchInertia ? _this.syncTouchLerp : _this.options.__iosNoInertiaSyncTouchLerp })); }; this.onScroll = function () { if (!_this.isScrolling) { var lastScroll = _this.animatedScroll; _this.animatedScroll = _this.targetScroll = _this.actualScroll; _this.velocity = 0; _this.direction = Math.sign(_this.animatedScroll - lastScroll); _this.emit(); } }; window.lenisVersion = version; // if wrapper is html or body, fallback to window if (wrapper === document.documentElement || wrapper === document.body) { wrapper = window; } this.options = { wrapper: wrapper, content: content, wheelEventsTarget: wheelEventsTarget, smoothWheel: smoothWheel, smoothTouch: smoothTouch, syncTouch: _syncTouch, syncTouchLerp: syncTouchLerp, __iosNoInertiaSyncTouchLerp: __iosNoInertiaSyncTouchLerp, touchInertiaMultiplier: touchInertiaMultiplier, duration: duration, easing: easing, lerp: lerp, infinite: infinite, gestureOrientation: gestureOrientation, orientation: orientation, touchMultiplier: touchMultiplier, wheelMultiplier: wheelMultiplier, normalizeWheel: normalizeWheel, autoResize: autoResize }; this.dimensions = new Dimensions({ wrapper: wrapper, content: content, autoResize: autoResize }); this.rootElement.classList.add('lenis'); this.velocity = 0; this.isStopped = false; this.isSmooth = smoothWheel || smoothTouch; this.isScrolling = false; this.targetScroll = this.animatedScroll = this.actualScroll; this.animate = new Animate(); this.emitter = new Emitter(); this.options.wrapper.addEventListener('scroll', this.onScroll, { passive: false }); this.virtualScroll = new VirtualScroll(wheelEventsTarget, { touchMultiplier: touchMultiplier, wheelMultiplier: wheelMultiplier, normalizeWheel: normalizeWheel }); this.virtualScroll.on('scroll', this.onVirtualScroll); } var _proto = Lenis.prototype; _proto.destroy = function destroy() { this.emitter.destroy(); this.options.wrapper.removeEventListener('scroll', this.onScroll, { passive: false }); this.virtualScroll.destroy(); this.dimensions.destroy(); this.rootElement.classList.remove('lenis'); this.rootElement.classList.remove('lenis-smooth'); this.rootElement.classList.remove('lenis-scrolling'); this.rootElement.classList.remove('lenis-stopped'); }; _proto.on = function on(event, callback) { return this.emitter.on(event, callback); }; _proto.off = function off(event, callback) { var _this$emitter$events$; this.emitter.events[event] = (_this$emitter$events$ = this.emitter.events[event]) == null ? void 0 : _this$emitter$events$.filter(function (i) { return callback !== i; }); }; _proto.setScroll = function setScroll(scroll) { // apply scroll value immediately if (this.isHorizontal) { this.rootElement.scrollLeft = scroll; } else { this.rootElement.scrollTop = scroll; } }; _proto.resize = function resize() { this.dimensions.resize(); }; _proto.emit = function emit() { this.emitter.emit('scroll', this); }; _proto.reset = function reset() { this.isLocked = false; this.isScrolling = false; this.velocity = 0; this.animate.stop(); }; _proto.start = function start() { this.isStopped = false; this.reset(); }; _proto.stop = function stop() { this.isStopped = true; this.animate.stop(); this.reset(); }; _proto.raf = function raf(time) { var deltaTime = time - (this.time || time); this.time = time; this.animate.advance(deltaTime * 0.001); }; _proto.scrollTo = function scrollTo(target, _temp2) { var _this2 = this; var _ref3 = _temp2 === void 0 ? {} : _temp2, _ref3$offset = _ref3.offset, offset = _ref3$offset === void 0 ? 0 : _ref3$offset, _ref3$immediate = _ref3.immediate, immediate = _ref3$immediate === void 0 ? false : _ref3$immediate, _ref3$lock = _ref3.lock, lock = _ref3$lock === void 0 ? false : _ref3$lock, _ref3$duration = _ref3.duration, duration = _ref3$duration === void 0 ? this.options.duration : _ref3$duration, _ref3$easing = _ref3.easing, easing = _ref3$easing === void 0 ? this.options.easing : _ref3$easing, _ref3$lerp = _ref3.lerp, lerp = _ref3$lerp === void 0 ? !duration && this.options.lerp : _ref3$lerp, _ref3$onComplete = _ref3.onComplete, onComplete = _ref3$onComplete === void 0 ? null : _ref3$onComplete, _ref3$force = _ref3.force, force = _ref3$force === void 0 ? false : _ref3$force, _ref3$programmatic = _ref3.programmatic, programmatic = _ref3$programmatic === void 0 ? true : _ref3$programmatic; if (this.isStopped && !force) return; // keywords if (['top', 'left', 'start'].includes(target)) { target = 0; } else if (['bottom', 'right', 'end'].includes(target)) { target = this.limit; } else { var _target; var node; if (typeof target === 'string') { // CSS selector node = document.querySelector(target); } else if ((_target = target) != null && _target.nodeType) { // Node element node = target; } if (node) { if (this.options.wrapper !== window) { // nested scroll offset correction var wrapperRect = this.options.wrapper.getBoundingClientRect(); offset -= this.isHorizontal ? wrapperRect.left : wrapperRect.top; } var rect = node.getBoundingClientRect(); target = (this.isHorizontal ? rect.left : rect.top) + this.animatedScroll; } } if (typeof target !== 'number') return; target += offset; target = Math.round(target); if (this.options.infinite) { if (programmatic) { this.targetScroll = this.animatedScroll = this.scroll; } } else { target = clamp(0, target, this.limit); } if (immediate) { this.animatedScroll = this.targetScroll = target; this.setScroll(this.scroll); this.reset(); this.emit(); onComplete == null ? void 0 : onComplete(); return; } if (!programmatic) { if (target === this.targetScroll) return; this.targetScroll = target; } this.animate.fromTo(this.animatedScroll, target, { duration: duration, easing: easing, lerp: lerp, onUpdate: function onUpdate(value, _ref4) { var completed = _ref4.completed; // started if (lock) _this2.isLocked = true; _this2.isScrolling = true; // updated _this2.velocity = value - _this2.animatedScroll; _this2.direction = Math.sign(_this2.velocity); _this2.animatedScroll = value; _this2.setScroll(_this2.scroll); if (programmatic) { // wheel during programmatic should stop it _this2.targetScroll = value; } // completed if (completed) { if (lock) _this2.isLocked = false; requestAnimationFrame(function () { //avoid double scroll event _this2.isScrolling = false; }); _this2.velocity = 0; onComplete == null ? void 0 : onComplete(); } _this2.emit(); } }); }; _createClass(Lenis, [{ key: "rootElement", get: function get() { return this.options.wrapper === window ? this.options.content : this.options.wrapper; } }, { key: "limit", get: function get() { return this.dimensions.limit[this.isHorizontal ? 'x' : 'y']; } }, { key: "isHorizontal", get: function get() { return this.options.orientation === 'horizontal'; } }, { key: "actualScroll", get: function get() { // value browser takes into account return this.isHorizontal ? this.rootElement.scrollLeft : this.rootElement.scrollTop; } }, { key: "scroll", get: function get() { return this.options.infinite ? modulo(this.animatedScroll, this.limit) : this.animatedScroll; } }, { key: "progress", get: function get() { // avoid progress to be NaN return this.limit === 0 ? 1 : this.scroll / this.limit; } }, { key: "isSmooth", get: function get() { return this.__isSmooth; }, set: function set(value) { if (this.__isSmooth !== value) { this.rootElement.classList.toggle('lenis-smooth', value); this.__isSmooth = value; } } }, { key: "isScrolling", get: function get() { return this.__isScrolling; }, set: function set(value) { if (this.__isScrolling !== value) { this.rootElement.classList.toggle('lenis-scrolling', value); this.__isScrolling = value; } } }, { key: "isStopped", get: function get() { return this.__isStopped; }, set: function set(value) { if (this.__isStopped !== value) { this.rootElement.classList.toggle('lenis-stopped', value); this.__isStopped = value; } } }]); return Lenis; }(); return Lenis; }));