lenis 1.3.4 → 1.3.6
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/LICENSE +8 -8
- package/README.md +34 -28
- package/dist/lenis-react.mjs.map +1 -1
- package/dist/lenis-snap.d.ts +8 -18
- package/dist/lenis-snap.js +16 -21
- package/dist/lenis-snap.js.map +1 -1
- package/dist/lenis-snap.min.js +1 -1
- package/dist/lenis-snap.min.js.map +1 -1
- package/dist/lenis-snap.mjs +16 -21
- package/dist/lenis-snap.mjs.map +1 -1
- package/dist/lenis-vue-nuxt.mjs +14 -1
- package/dist/lenis-vue.d.ts +10 -2
- package/dist/lenis-vue.mjs +2 -1
- package/dist/lenis-vue.mjs.map +1 -1
- package/dist/lenis.d.ts +4 -2
- package/dist/lenis.js +29 -9
- package/dist/lenis.js.map +1 -1
- package/dist/lenis.min.js +1 -1
- package/dist/lenis.min.js.map +1 -1
- package/dist/lenis.mjs +29 -9
- package/dist/lenis.mjs.map +1 -1
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
The MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 darkroom.engineering
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
-
|
|
7
|
-
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
-
|
|
1
|
+
The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 darkroom.engineering
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
9
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -40,7 +40,13 @@ Your support helps us smooth out the internet one library at a time—and lets u
|
|
|
40
40
|
|
|
41
41
|
<!-- sponsors --><a href="https://www.cachet.studio"><img src="https://github.com/cachet-studio.png" width="60px" alt="User avatar: cachet.studio" /></a><a href="https://smsunarto.com"><img src="https://github.com/smsunarto.png" width="60px" alt="User avatar: Scott Sunarto" /></a><a href="https://www.non-linear.studio/"><img src="https://github.com/mariosmaselli.png" width="60px" alt="User avatar: Mario Sanchez Maselli" /></a><a href="www.ark.studio"><img src="https://github.com/thearkis.png" width="60px" alt="User avatar: ΛRK" /></a><a href="https://federic.ooo/"><img src="https://github.com/vallafederico.png" width="60px" alt="User avatar: Federico Valla" /></a><a href="https://ironvelvet.studio"><img src="https://github.com/ironvelvet.png" width="60px" alt="User avatar: Iron Velvet" /></a><a href="http://uxby.us"><img src="https://github.com/UX-By-Us.png" width="60px" alt="User avatar: UX By Us" /></a><a href="https://github.com/MoT1998-star"><img src="https://github.com/MoT1998-star.png" width="60px" alt="User avatar: " /></a><a href="https://github.com/Alexi5000"><img src="https://github.com/Alexi5000.png" width="60px" alt="User avatar: Alex Cinovoj" /></a><a href="æ.com"><img src="https://github.com/ae-com.png" width="60px" alt="User avatar: Æ" /></a><!-- sponsors -->
|
|
42
42
|
|
|
43
|
-
<br
|
|
43
|
+
<br />
|
|
44
|
+
<br />
|
|
45
|
+
<a href="https://vercel.com/oss">
|
|
46
|
+
<img alt="Vercel OSS Program" src="https://vercel.com/oss/program-badge.svg" />
|
|
47
|
+
</a>
|
|
48
|
+
|
|
49
|
+
<br />
|
|
44
50
|
|
|
45
51
|
## Packages
|
|
46
52
|
|
|
@@ -69,7 +75,7 @@ import Lenis from 'lenis'
|
|
|
69
75
|
Using scripts:
|
|
70
76
|
|
|
71
77
|
```html
|
|
72
|
-
<script src="https://unpkg.com/lenis@1.3.
|
|
78
|
+
<script src="https://unpkg.com/lenis@1.3.6/dist/lenis.min.js"></script>
|
|
73
79
|
```
|
|
74
80
|
|
|
75
81
|
|
|
@@ -116,7 +122,7 @@ import 'lenis/dist/lenis.css'
|
|
|
116
122
|
or link the CSS file:
|
|
117
123
|
|
|
118
124
|
```html
|
|
119
|
-
<link rel="stylesheet" href="https://unpkg.com/lenis@1.3.
|
|
125
|
+
<link rel="stylesheet" href="https://unpkg.com/lenis@1.3.6/dist/lenis.css">
|
|
120
126
|
```
|
|
121
127
|
|
|
122
128
|
or add it manually:
|
|
@@ -153,31 +159,31 @@ gsap.ticker.lagSmoothing(0);
|
|
|
153
159
|
|
|
154
160
|
## Settings
|
|
155
161
|
|
|
156
|
-
| Option
|
|
157
|
-
|
|
158
|
-
| `wrapper`
|
|
159
|
-
| `content`
|
|
160
|
-
| `eventsTarget`
|
|
161
|
-
| `smoothWheel`
|
|
162
|
-
| `lerp`
|
|
163
|
-
| `duration`
|
|
164
|
-
| `easing`
|
|
165
|
-
| `orientation`
|
|
166
|
-
| `gestureOrientation`
|
|
167
|
-
| `syncTouch`
|
|
168
|
-
| `syncTouchLerp`
|
|
169
|
-
| `
|
|
170
|
-
| `wheelMultiplier`
|
|
171
|
-
| `touchMultiplier`
|
|
172
|
-
| `infinite`
|
|
173
|
-
| `autoResize`
|
|
174
|
-
| `prevent`
|
|
175
|
-
| `virtualScroll`
|
|
176
|
-
| `overscroll`
|
|
177
|
-
| `autoRaf`
|
|
178
|
-
| `anchors`
|
|
179
|
-
| `autoToggle`
|
|
180
|
-
| `allowNestedScroll`
|
|
162
|
+
| Option | Type | Default | Description |
|
|
163
|
+
|------------------------|----------------------------|----------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
164
|
+
| `wrapper` | `HTMLElement, Window` | `window` | The element that will be used as the scroll container. |
|
|
165
|
+
| `content` | `HTMLElement` | `document.documentElement` | The element that contains the content that will be scrolled, usually `wrapper`'s direct child. |
|
|
166
|
+
| `eventsTarget` | `HTMLElement, Window` | `wrapper` | The element that will listen to `wheel` and `touch` events. |
|
|
167
|
+
| `smoothWheel` | `boolean` | `true` | Smooth the scroll initiated by `wheel` events. |
|
|
168
|
+
| `lerp` | `number` | `0.1` | Linear interpolation (lerp) intensity (between 0 and 1). |
|
|
169
|
+
| `duration` | `number` | `1.2` | The duration of scroll animation (in seconds). Useless if lerp defined. |
|
|
170
|
+
| `easing` | `function` | `(t) => Math.min(1, 1.001 - Math.pow(2, -10 * t))` | The easing function to use for the scroll animation, our default is custom but you can pick one from [Easings.net](https://easings.net/en). Useless if lerp defined. |
|
|
171
|
+
| `orientation` | `string` | `vertical` | The orientation of the scrolling. Can be `vertical` or `horizontal`. |
|
|
172
|
+
| `gestureOrientation` | `string` | `vertical` | The orientation of the gestures. Can be `vertical`, `horizontal` or `both`. |
|
|
173
|
+
| `syncTouch` | `boolean` | `false` | Mimic touch device scroll while allowing scroll sync (can be unstable on iOS<16). |
|
|
174
|
+
| `syncTouchLerp` | `number` | `0.075` | Lerp applied during `syncTouch` inertia. |
|
|
175
|
+
| `touchInertiaExponent` | `number` | `1.7` | Manage the strength of syncTouch inertia. |
|
|
176
|
+
| `wheelMultiplier` | `number` | `1` | The multiplier to use for mouse wheel events. |
|
|
177
|
+
| `touchMultiplier` | `number` | `1` | The multiplier to use for touch events. |
|
|
178
|
+
| `infinite` | `boolean` | `false` | Enable infinite scrolling! `syncTouch: true` is required on touch devices ([See example](https://codepen.io/ClementRoche/pen/OJqBLod)). |
|
|
179
|
+
| `autoResize` | `boolean` | `true` | Resize instance automatically based on `ResizeObserver`. If `false` you must resize manually using `.resize()`. |
|
|
180
|
+
| `prevent` | `function` | `undefined` | Manually prevent scroll to be smoothed based on elements traversed by events. If `true` is returned, it will prevent the scroll to be smoothed. Example: `(node) => node.classList.contains('cookie-modal')`. |
|
|
181
|
+
| `virtualScroll` | `function` | `undefined` | Manually modify the events before they get consumed. If `false` is returned, the scroll will not be smoothed. Examples: `(e) => { e.deltaY /= 2 }` (to slow down vertical scroll) or `({ event }) => !event.shiftKey` (to prevent scroll to be smoothed if shift key is pressed). |
|
|
182
|
+
| `overscroll` | `boolean` | `true` | Similar to CSS overscroll-behavior (https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior). |
|
|
183
|
+
| `autoRaf` | `boolean` | `false` | Wether or not to automatically run `requestAnimationFrame` loop. |
|
|
184
|
+
| `anchors` | `boolean, ScrollToOptions` | `false` | Scroll to anchor links when clicked. If `true` is passed, it will enable anchor links with default options. If `ScrollToOptions` is passed, it will enable anchor links with the given options. |
|
|
185
|
+
| `autoToggle` | `boolean` | `false` | Automatically start or stop the lenis instance based on the wrapper's overflow property, ⚠️ this requires Lenis recommended CSS. Safari > 17.3, Chrome > 116 and Firefox > 128 ([https://caniuse.com/?search=transition-behavior](https://caniuse.com/?search=transition-behavior)). |
|
|
186
|
+
| `allowNestedScroll` | `boolean` | `false` | Allow nested scrolls. If `true` is passed, it will allow nested scrolls. If `false` is passed, it will not allow nested scrolls. ⚠️ To be used with caution since this can lead to performance issues, prefer using `prevent` or `data-lenis-prevent` instead. |
|
|
181
187
|
<br/>
|
|
182
188
|
|
|
183
189
|
<!-- `target`: goal to reach
|
package/dist/lenis-react.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../packages/react/src/provider.tsx","../packages/react/src/store.ts","../packages/react/src/use-lenis.ts"],"sourcesContent":["import Lenis, { type ScrollCallback } from 'lenis'\r\nimport {\r\n createContext,\r\n forwardRef,\r\n useCallback,\r\n useEffect,\r\n useImperativeHandle,\r\n useRef,\r\n useState,\r\n} from 'react'\r\nimport { Store } from './store'\r\nimport type { LenisContextValue, LenisProps, LenisRef } from './types'\r\n\r\nexport const LenisContext = createContext<LenisContextValue | null>(null)\r\n\r\n/**\r\n * The root store for the lenis context\r\n *\r\n * This store serves as a fallback for the context if it is not available\r\n * and allows us to use the global lenis instance above a provider\r\n */\r\nexport const rootLenisContextStore = new Store<LenisContextValue | null>(null)\r\n\r\n/**\r\n * React component to setup a Lenis instance\r\n */\r\nexport const ReactLenis = forwardRef<LenisRef, LenisProps>(\r\n (\r\n {\r\n children,\r\n root = false,\r\n options = {},\r\n className,\r\n autoRaf = true,\r\n style,\r\n props,\r\n }: LenisProps,\r\n ref\r\n ) => {\r\n const wrapperRef = useRef<HTMLDivElement | null>(null)\r\n const contentRef = useRef<HTMLDivElement | null>(null)\r\n\r\n const [lenis, setLenis] = useState<Lenis | undefined>(undefined)\r\n\r\n // Setup ref\r\n useImperativeHandle(\r\n ref,\r\n () => ({\r\n wrapper: wrapperRef.current,\r\n content: contentRef.current,\r\n lenis,\r\n }),\r\n [lenis]\r\n )\r\n\r\n // Setup lenis instance\r\n useEffect(() => {\r\n const lenis = new Lenis({\r\n ...options,\r\n ...(!root && {\r\n wrapper: wrapperRef.current!,\r\n content: contentRef.current!,\r\n }),\r\n autoRaf: options?.autoRaf ?? autoRaf, // this is to avoid breaking the autoRaf prop if it's still used (require breaking change)\r\n })\r\n\r\n setLenis(lenis)\r\n\r\n return () => {\r\n lenis.destroy()\r\n setLenis(undefined)\r\n }\r\n }, [root, JSON.stringify(options)])\r\n\r\n // Handle callbacks\r\n const callbacksRefs = useRef<\r\n {\r\n callback: ScrollCallback\r\n priority: number\r\n }[]\r\n >([])\r\n\r\n const addCallback: LenisContextValue['addCallback'] = useCallback(\r\n (callback, priority) => {\r\n callbacksRefs.current.push({ callback, priority })\r\n callbacksRefs.current.sort((a, b) => a.priority - b.priority)\r\n },\r\n []\r\n )\r\n\r\n const removeCallback: LenisContextValue['removeCallback'] = useCallback(\r\n (callback) => {\r\n callbacksRefs.current = callbacksRefs.current.filter(\r\n (cb) => cb.callback !== callback\r\n )\r\n },\r\n []\r\n )\r\n\r\n // This makes sure to set the global context if the root is true\r\n useEffect(() => {\r\n if (root && lenis) {\r\n rootLenisContextStore.set({ lenis, addCallback, removeCallback })\r\n\r\n return () => rootLenisContextStore.set(null)\r\n }\r\n }, [root, lenis, addCallback, removeCallback])\r\n\r\n // Setup callback listeners\r\n useEffect(() => {\r\n if (!lenis) return\r\n\r\n const onScroll: ScrollCallback = (data) => {\r\n for (let i = 0; i < callbacksRefs.current.length; i++) {\r\n callbacksRefs.current[i]?.callback(data)\r\n }\r\n }\r\n\r\n lenis.on('scroll', onScroll)\r\n\r\n return () => {\r\n lenis.off('scroll', onScroll)\r\n }\r\n }, [lenis])\r\n\r\n return (\r\n <LenisContext.Provider\r\n value={{ lenis: lenis!, addCallback, removeCallback }}\r\n >\r\n {root ? (\r\n children\r\n ) : (\r\n <div ref={wrapperRef} className={className} style={style} {...props}>\r\n <div ref={contentRef}>{children}</div>\r\n </div>\r\n )}\r\n </LenisContext.Provider>\r\n )\r\n }\r\n)\r\n","import { useEffect, useState } from 'react'\n\ntype Listener<S> = (state: S) => void\n\nexport class Store<S> {\n private listeners: Listener<S>[] = []\n\n constructor(private state: S) {}\n\n set(state: S) {\n this.state = state\n\n for (let listener of this.listeners) {\n listener(this.state)\n }\n }\n\n subscribe(listener: Listener<S>) {\n this.listeners = [...this.listeners, listener]\n return () => {\n this.listeners = this.listeners.filter((l) => l !== listener)\n }\n }\n\n get() {\n return this.state\n }\n}\n\nexport function useStore<S>(store: Store<S>) {\n const [state, setState] = useState(store.get())\n\n useEffect(() => {\n return store.subscribe((state) => setState(state))\n }, [store])\n\n return state\n}\n","import type { ScrollCallback } from 'lenis'\nimport { useContext, useEffect } from 'react'\nimport { LenisContext, rootLenisContextStore } from './provider'\nimport { useStore } from './store'\nimport type { LenisContextValue } from './types'\n\n// Fall back to an empty object if both context and store are not available\nconst fallbackContext: Partial<LenisContextValue> = {}\n\n/**\n * Hook to access the Lenis instance and its methods\n *\n * @example <caption>Scroll callback</caption>\n * useLenis((lenis) => {\n * if (lenis.isScrolling) {\n * console.log('Scrolling...')\n * }\n *\n * if (lenis.progress === 1) {\n * console.log('At the end!')\n * }\n * })\n *\n * @example <caption>Scroll callback with dependencies</caption>\n * useLenis((lenis) => {\n * if (lenis.isScrolling) {\n * console.log('Scrolling...', someDependency)\n * }\n * }, [someDependency])\n * @example <caption>Scroll callback with priority</caption>\n * useLenis((lenis) => {\n * if (lenis.isScrolling) {\n * console.log('Scrolling...')\n * }\n * }, [], 1)\n * @example <caption>Instance access</caption>\n * const lenis = useLenis()\n *\n * handleClick() {\n * lenis.scrollTo(100, {\n * lerp: 0.1,\n * duration: 1,\n * easing: (t) => t,\n * onComplete: () => {\n * console.log('Complete!')\n * }\n * })\n * }\n */\nexport function useLenis(\n callback?: ScrollCallback,\n deps: any[] = [],\n priority = 0\n) {\n // Try to get the lenis instance from the context first\n const localContext = useContext(LenisContext)\n // Fall back to the root store if the context is not available\n const rootContext = useStore(rootLenisContextStore)\n // Fall back to the fallback context if all else fails\n const currentContext = localContext ?? rootContext ?? fallbackContext\n\n const { lenis, addCallback, removeCallback } = currentContext\n\n useEffect(() => {\n if (!callback || !addCallback || !removeCallback || !lenis) return\n\n addCallback(callback, priority)\n callback(lenis)\n\n return () => {\n removeCallback(callback)\n }\n }, [lenis, addCallback, removeCallback, priority, ...deps])\n\n return lenis\n}\n"],"mappings":";;;AAAA,OAAO,WAAoC;AAC3C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAAC;AAAA,OACK;;;ACTP,SAAS,WAAW,gBAAgB;AAI7B,IAAM,QAAN,MAAe;AAAA,EAGpB,YAAoB,OAAU;AAAV;AAAA,EAAW;AAAA,EAFvB,YAA2B,CAAC;AAAA,EAIpC,IAAI,OAAU;AACZ,SAAK,QAAQ;AAEb,aAAS,YAAY,KAAK,WAAW;AACnC,eAAS,KAAK,KAAK;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,UAAU,UAAuB;AAC/B,SAAK,YAAY,CAAC,GAAG,KAAK,WAAW,QAAQ;AAC7C,WAAO,MAAM;AACX,WAAK,YAAY,KAAK,UAAU,OAAO,CAAC,MAAM,MAAM,QAAQ;AAAA,IAC9D;AAAA,EACF;AAAA,EAEA,MAAM;AACJ,WAAO,KAAK;AAAA,EACd;AACF;AAEO,SAAS,SAAY,OAAiB;AAC3C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,MAAM,IAAI,CAAC;AAE9C,YAAU,MAAM;AACd,WAAO,MAAM,UAAU,CAACC,WAAU,SAASA,MAAK,CAAC;AAAA,EACnD,GAAG,CAAC,KAAK,CAAC;AAEV,SAAO;AACT;;;ADgGY;AAxHL,IAAM,eAAe,cAAwC,IAAI;AAQjE,IAAM,wBAAwB,IAAI,MAAgC,IAAI;AAKtE,IAAM,aAAa;AAAA,EACxB,CACE;AAAA,IACE;AAAA,IACA,OAAO;AAAA,IACP,UAAU,CAAC;AAAA,IACX;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EACF,GACA,QACG;AACH,UAAM,aAAa,OAA8B,IAAI;AACrD,UAAM,aAAa,OAA8B,IAAI;AAErD,UAAM,CAAC,OAAO,QAAQ,IAAIC,UAA4B,MAAS;AAG/D;AAAA,MACE;AAAA,MACA,OAAO;AAAA,QACL,SAAS,WAAW;AAAA,QACpB,SAAS,WAAW;AAAA,QACpB;AAAA,MACF;AAAA,MACA,CAAC,KAAK;AAAA,IACR;AAGA,IAAAC,WAAU,MAAM;AACd,YAAMC,SAAQ,IAAI,MAAM;AAAA,QACtB,GAAG;AAAA,QACH,GAAI,CAAC,QAAQ;AAAA,UACX,SAAS,WAAW;AAAA,UACpB,SAAS,WAAW;AAAA,QACtB;AAAA,QACA,SAAS,SAAS,WAAW;AAAA;AAAA,MAC/B,CAAC;AAED,eAASA,MAAK;AAEd,aAAO,MAAM;AACX,QAAAA,OAAM,QAAQ;AACd,iBAAS,MAAS;AAAA,MACpB;AAAA,IACF,GAAG,CAAC,MAAM,KAAK,UAAU,OAAO,CAAC,CAAC;AAGlC,UAAM,gBAAgB,OAKpB,CAAC,CAAC;AAEJ,UAAM,cAAgD;AAAA,MACpD,CAAC,UAAU,aAAa;AACtB,sBAAc,QAAQ,KAAK,EAAE,UAAU,SAAS,CAAC;AACjD,sBAAc,QAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AAAA,MAC9D;AAAA,MACA,CAAC;AAAA,IACH;AAEA,UAAM,iBAAsD;AAAA,MAC1D,CAAC,aAAa;AACZ,sBAAc,UAAU,cAAc,QAAQ;AAAA,UAC5C,CAAC,OAAO,GAAG,aAAa;AAAA,QAC1B;AAAA,MACF;AAAA,MACA,CAAC;AAAA,IACH;AAGA,IAAAD,WAAU,MAAM;AACd,UAAI,QAAQ,OAAO;AACjB,8BAAsB,IAAI,EAAE,OAAO,aAAa,eAAe,CAAC;AAEhE,eAAO,MAAM,sBAAsB,IAAI,IAAI;AAAA,MAC7C;AAAA,IACF,GAAG,CAAC,MAAM,OAAO,aAAa,cAAc,CAAC;AAG7C,IAAAA,WAAU,MAAM;AACd,UAAI,CAAC,MAAO;AAEZ,YAAM,WAA2B,CAAC,SAAS;AACzC,iBAAS,IAAI,GAAG,IAAI,cAAc,QAAQ,QAAQ,KAAK;AACrD,wBAAc,QAAQ,CAAC,GAAG,SAAS,IAAI;AAAA,QACzC;AAAA,MACF;AAEA,YAAM,GAAG,UAAU,QAAQ;AAE3B,aAAO,MAAM;AACX,cAAM,IAAI,UAAU,QAAQ;AAAA,MAC9B;AAAA,IACF,GAAG,CAAC,KAAK,CAAC;AAEV,WACE;AAAA,MAAC,aAAa;AAAA,MAAb;AAAA,QACC,OAAO,EAAE,OAAe,aAAa,eAAe;AAAA,QAEnD,iBACC,WAEA,oBAAC,SAAI,KAAK,YAAY,WAAsB,OAAe,GAAG,OAC5D,8BAAC,SAAI,KAAK,YAAa,UAAS,GAClC;AAAA;AAAA,IAEJ;AAAA,EAEJ;AACF;;;AE1IA,SAAS,YAAY,aAAAE,kBAAiB;AAMtC,IAAM,kBAA8C,CAAC;AA0C9C,SAAS,SACd,UACA,OAAc,CAAC,GACf,WAAW,GACX;AAEA,QAAM,eAAe,WAAW,YAAY;AAE5C,QAAM,cAAc,SAAS,qBAAqB;AAElD,QAAM,iBAAiB,gBAAgB,eAAe;AAEtD,QAAM,EAAE,OAAO,aAAa,eAAe,IAAI;AAE/C,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,YAAY,CAAC,eAAe,CAAC,kBAAkB,CAAC,MAAO;AAE5D,gBAAY,UAAU,QAAQ;AAC9B,aAAS,KAAK;AAEd,WAAO,MAAM;AACX,qBAAe,QAAQ;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,OAAO,aAAa,gBAAgB,UAAU,GAAG,IAAI,CAAC;AAE1D,SAAO;AACT;","names":["useEffect","useState","state","useState","useEffect","lenis","useEffect","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../packages/react/src/provider.tsx","../packages/react/src/store.ts","../packages/react/src/use-lenis.ts"],"sourcesContent":["import Lenis, { type ScrollCallback } from 'lenis'\r\nimport {\r\n createContext,\r\n forwardRef,\r\n useCallback,\r\n useEffect,\r\n useImperativeHandle,\r\n useRef,\r\n useState,\r\n} from 'react'\r\nimport { Store } from './store'\r\nimport type { LenisContextValue, LenisProps, LenisRef } from './types'\r\n\r\nexport const LenisContext = createContext<LenisContextValue | null>(null)\r\n\r\n/**\r\n * The root store for the lenis context\r\n *\r\n * This store serves as a fallback for the context if it is not available\r\n * and allows us to use the global lenis instance above a provider\r\n */\r\nexport const rootLenisContextStore = new Store<LenisContextValue | null>(null)\r\n\r\n/**\r\n * React component to setup a Lenis instance\r\n */\r\nexport const ReactLenis = forwardRef<LenisRef, LenisProps>(\r\n (\r\n {\r\n children,\r\n root = false,\r\n options = {},\r\n className,\r\n autoRaf = true,\r\n style,\r\n props,\r\n }: LenisProps,\r\n ref\r\n ) => {\r\n const wrapperRef = useRef<HTMLDivElement | null>(null)\r\n const contentRef = useRef<HTMLDivElement | null>(null)\r\n\r\n const [lenis, setLenis] = useState<Lenis | undefined>(undefined)\r\n\r\n // Setup ref\r\n useImperativeHandle(\r\n ref,\r\n () => ({\r\n wrapper: wrapperRef.current,\r\n content: contentRef.current,\r\n lenis,\r\n }),\r\n [lenis]\r\n )\r\n\r\n // Setup lenis instance\r\n useEffect(() => {\r\n const lenis = new Lenis({\r\n ...options,\r\n ...(!root && {\r\n wrapper: wrapperRef.current!,\r\n content: contentRef.current!,\r\n }),\r\n autoRaf: options?.autoRaf ?? autoRaf, // this is to avoid breaking the autoRaf prop if it's still used (require breaking change)\r\n })\r\n\r\n setLenis(lenis)\r\n\r\n return () => {\r\n lenis.destroy()\r\n setLenis(undefined)\r\n }\r\n }, [root, JSON.stringify(options)])\r\n\r\n // Handle callbacks\r\n const callbacksRefs = useRef<\r\n {\r\n callback: ScrollCallback\r\n priority: number\r\n }[]\r\n >([])\r\n\r\n const addCallback: LenisContextValue['addCallback'] = useCallback(\r\n (callback, priority) => {\r\n callbacksRefs.current.push({ callback, priority })\r\n callbacksRefs.current.sort((a, b) => a.priority - b.priority)\r\n },\r\n []\r\n )\r\n\r\n const removeCallback: LenisContextValue['removeCallback'] = useCallback(\r\n (callback) => {\r\n callbacksRefs.current = callbacksRefs.current.filter(\r\n (cb) => cb.callback !== callback\r\n )\r\n },\r\n []\r\n )\r\n\r\n // This makes sure to set the global context if the root is true\r\n useEffect(() => {\r\n if (root && lenis) {\r\n rootLenisContextStore.set({ lenis, addCallback, removeCallback })\r\n\r\n return () => rootLenisContextStore.set(null)\r\n }\r\n }, [root, lenis, addCallback, removeCallback])\r\n\r\n // Setup callback listeners\r\n useEffect(() => {\r\n if (!lenis) return\r\n\r\n const onScroll: ScrollCallback = (data) => {\r\n for (let i = 0; i < callbacksRefs.current.length; i++) {\r\n callbacksRefs.current[i]?.callback(data)\r\n }\r\n }\r\n\r\n lenis.on('scroll', onScroll)\r\n\r\n return () => {\r\n lenis.off('scroll', onScroll)\r\n }\r\n }, [lenis])\r\n\r\n return (\r\n <LenisContext.Provider\r\n value={{ lenis: lenis!, addCallback, removeCallback }}\r\n >\r\n {root ? (\r\n children\r\n ) : (\r\n <div ref={wrapperRef} className={className} style={style} {...props}>\r\n <div ref={contentRef}>{children}</div>\r\n </div>\r\n )}\r\n </LenisContext.Provider>\r\n )\r\n }\r\n)\r\n","import { useEffect, useState } from 'react'\r\n\r\ntype Listener<S> = (state: S) => void\r\n\r\nexport class Store<S> {\r\n private listeners: Listener<S>[] = []\r\n\r\n constructor(private state: S) {}\r\n\r\n set(state: S) {\r\n this.state = state\r\n\r\n for (let listener of this.listeners) {\r\n listener(this.state)\r\n }\r\n }\r\n\r\n subscribe(listener: Listener<S>) {\r\n this.listeners = [...this.listeners, listener]\r\n return () => {\r\n this.listeners = this.listeners.filter((l) => l !== listener)\r\n }\r\n }\r\n\r\n get() {\r\n return this.state\r\n }\r\n}\r\n\r\nexport function useStore<S>(store: Store<S>) {\r\n const [state, setState] = useState(store.get())\r\n\r\n useEffect(() => {\r\n return store.subscribe((state) => setState(state))\r\n }, [store])\r\n\r\n return state\r\n}\r\n","import type { ScrollCallback } from 'lenis'\r\nimport { useContext, useEffect } from 'react'\r\nimport { LenisContext, rootLenisContextStore } from './provider'\r\nimport { useStore } from './store'\r\nimport type { LenisContextValue } from './types'\r\n\r\n// Fall back to an empty object if both context and store are not available\r\nconst fallbackContext: Partial<LenisContextValue> = {}\r\n\r\n/**\r\n * Hook to access the Lenis instance and its methods\r\n *\r\n * @example <caption>Scroll callback</caption>\r\n * useLenis((lenis) => {\r\n * if (lenis.isScrolling) {\r\n * console.log('Scrolling...')\r\n * }\r\n *\r\n * if (lenis.progress === 1) {\r\n * console.log('At the end!')\r\n * }\r\n * })\r\n *\r\n * @example <caption>Scroll callback with dependencies</caption>\r\n * useLenis((lenis) => {\r\n * if (lenis.isScrolling) {\r\n * console.log('Scrolling...', someDependency)\r\n * }\r\n * }, [someDependency])\r\n * @example <caption>Scroll callback with priority</caption>\r\n * useLenis((lenis) => {\r\n * if (lenis.isScrolling) {\r\n * console.log('Scrolling...')\r\n * }\r\n * }, [], 1)\r\n * @example <caption>Instance access</caption>\r\n * const lenis = useLenis()\r\n *\r\n * handleClick() {\r\n * lenis.scrollTo(100, {\r\n * lerp: 0.1,\r\n * duration: 1,\r\n * easing: (t) => t,\r\n * onComplete: () => {\r\n * console.log('Complete!')\r\n * }\r\n * })\r\n * }\r\n */\r\nexport function useLenis(\r\n callback?: ScrollCallback,\r\n deps: any[] = [],\r\n priority = 0\r\n) {\r\n // Try to get the lenis instance from the context first\r\n const localContext = useContext(LenisContext)\r\n // Fall back to the root store if the context is not available\r\n const rootContext = useStore(rootLenisContextStore)\r\n // Fall back to the fallback context if all else fails\r\n const currentContext = localContext ?? rootContext ?? fallbackContext\r\n\r\n const { lenis, addCallback, removeCallback } = currentContext\r\n\r\n useEffect(() => {\r\n if (!callback || !addCallback || !removeCallback || !lenis) return\r\n\r\n addCallback(callback, priority)\r\n callback(lenis)\r\n\r\n return () => {\r\n removeCallback(callback)\r\n }\r\n }, [lenis, addCallback, removeCallback, priority, ...deps])\r\n\r\n return lenis\r\n}\r\n"],"mappings":"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","names":["useEffect","useState","state","useState","useEffect","lenis","useEffect","useEffect"]}
|
package/dist/lenis-snap.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Lenis, { UserData, EasingFunction } from 'lenis';
|
|
2
2
|
|
|
3
3
|
type SnapElementOptions = {
|
|
4
|
-
align?: string[];
|
|
4
|
+
align?: string | string[];
|
|
5
5
|
ignoreSticky?: boolean;
|
|
6
6
|
ignoreTransform?: boolean;
|
|
7
7
|
};
|
|
@@ -59,6 +59,10 @@ type SnapOptions = {
|
|
|
59
59
|
* The duration of the snap animation (in s)
|
|
60
60
|
*/
|
|
61
61
|
duration?: number;
|
|
62
|
+
/**
|
|
63
|
+
* The distance to snap to
|
|
64
|
+
*/
|
|
65
|
+
distanceThreshold?: number | `${number}%`;
|
|
62
66
|
/**
|
|
63
67
|
* The velocity threshold to trigger a snap
|
|
64
68
|
*/
|
|
@@ -77,8 +81,6 @@ type SnapOptions = {
|
|
|
77
81
|
onSnapComplete?: OnSnapCallback;
|
|
78
82
|
};
|
|
79
83
|
|
|
80
|
-
type UID = number;
|
|
81
|
-
|
|
82
84
|
type RequiredPick<T, F extends keyof T> = Omit<T, F> & Required<Pick<T, F>>;
|
|
83
85
|
/**
|
|
84
86
|
* Snap class to handle the snap functionality
|
|
@@ -116,7 +118,7 @@ declare class Snap {
|
|
|
116
118
|
};
|
|
117
119
|
isStopped: boolean;
|
|
118
120
|
onSnapDebounced: () => void;
|
|
119
|
-
constructor(lenis: Lenis, { type, lerp, easing, duration, velocityThreshold, debounce: debounceDelay, onSnapStart, onSnapComplete, }?: SnapOptions);
|
|
121
|
+
constructor(lenis: Lenis, { type, lerp, easing, duration, distanceThreshold, velocityThreshold, debounce: debounceDelay, onSnapStart, onSnapComplete, }?: SnapOptions);
|
|
120
122
|
/**
|
|
121
123
|
* Destroy the snap instance
|
|
122
124
|
*/
|
|
@@ -136,13 +138,7 @@ declare class Snap {
|
|
|
136
138
|
* @param userData User data that will be forwarded through the snap event
|
|
137
139
|
* @returns Unsubscribe function
|
|
138
140
|
*/
|
|
139
|
-
add(value: number, userData?: UserData): () =>
|
|
140
|
-
/**
|
|
141
|
-
* Remove a snap from the snap instance
|
|
142
|
-
*
|
|
143
|
-
* @param id The snap id of the snap to remove
|
|
144
|
-
*/
|
|
145
|
-
remove(id: UID): void;
|
|
141
|
+
add(value: number, userData?: UserData): () => boolean;
|
|
146
142
|
/**
|
|
147
143
|
* Add an element to the snap instance
|
|
148
144
|
*
|
|
@@ -150,13 +146,7 @@ declare class Snap {
|
|
|
150
146
|
* @param options The options for the element
|
|
151
147
|
* @returns Unsubscribe function
|
|
152
148
|
*/
|
|
153
|
-
addElement(element: HTMLElement, options?: SnapElementOptions): () =>
|
|
154
|
-
/**
|
|
155
|
-
* Remove an element from the snap instance
|
|
156
|
-
*
|
|
157
|
-
* @param id The snap id of the snap element to remove
|
|
158
|
-
*/
|
|
159
|
-
removeElement(id: UID): void;
|
|
149
|
+
addElement(element: HTMLElement, options?: SnapElementOptions): () => boolean;
|
|
160
150
|
private onWindowResize;
|
|
161
151
|
private onScroll;
|
|
162
152
|
private onSnap;
|
package/dist/lenis-snap.js
CHANGED
|
@@ -148,8 +148,9 @@ var Snap = class {
|
|
|
148
148
|
lerp,
|
|
149
149
|
easing,
|
|
150
150
|
duration,
|
|
151
|
-
|
|
152
|
-
|
|
151
|
+
distanceThreshold = "100%",
|
|
152
|
+
velocityThreshold = 1.2,
|
|
153
|
+
debounce: debounceDelay = 50,
|
|
153
154
|
onSnapStart,
|
|
154
155
|
onSnapComplete
|
|
155
156
|
} = {}) {
|
|
@@ -159,6 +160,7 @@ var Snap = class {
|
|
|
159
160
|
lerp,
|
|
160
161
|
easing,
|
|
161
162
|
duration,
|
|
163
|
+
distanceThreshold,
|
|
162
164
|
velocityThreshold,
|
|
163
165
|
debounce: debounceDelay,
|
|
164
166
|
onSnapStart,
|
|
@@ -208,15 +210,7 @@ var Snap = class {
|
|
|
208
210
|
add(value, userData = {}) {
|
|
209
211
|
const id = uid();
|
|
210
212
|
this.snaps.set(id, { value, userData });
|
|
211
|
-
return () => this.
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Remove a snap from the snap instance
|
|
215
|
-
*
|
|
216
|
-
* @param id The snap id of the snap to remove
|
|
217
|
-
*/
|
|
218
|
-
remove(id) {
|
|
219
|
-
this.snaps.delete(id);
|
|
213
|
+
return () => this.snaps.delete(id);
|
|
220
214
|
}
|
|
221
215
|
/**
|
|
222
216
|
* Add an element to the snap instance
|
|
@@ -228,15 +222,7 @@ var Snap = class {
|
|
|
228
222
|
addElement(element, options = {}) {
|
|
229
223
|
const id = uid();
|
|
230
224
|
this.elements.set(id, new SnapElement(element, options));
|
|
231
|
-
return () => this.
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* Remove an element from the snap instance
|
|
235
|
-
*
|
|
236
|
-
* @param id The snap id of the snap element to remove
|
|
237
|
-
*/
|
|
238
|
-
removeElement(id) {
|
|
239
|
-
this.elements.delete(id);
|
|
225
|
+
return () => this.elements.delete(id);
|
|
240
226
|
}
|
|
241
227
|
onWindowResize = () => {
|
|
242
228
|
this.viewport.width = window.innerWidth;
|
|
@@ -286,7 +272,16 @@ var Snap = class {
|
|
|
286
272
|
const distanceToNextSnap = Math.abs(scroll - nextSnap.value);
|
|
287
273
|
const snap = distanceToPrevSnap < distanceToNextSnap ? prevSnap : nextSnap;
|
|
288
274
|
const distance = Math.abs(scroll - snap.value);
|
|
289
|
-
|
|
275
|
+
let distanceThreshold;
|
|
276
|
+
const axis = isHorizontal ? "width" : "height";
|
|
277
|
+
if (typeof this.options.distanceThreshold === "string" && this.options.distanceThreshold.endsWith("%")) {
|
|
278
|
+
distanceThreshold = Number(this.options.distanceThreshold.replace("%", "")) / 100 * this.viewport[axis];
|
|
279
|
+
} else if (typeof this.options.distanceThreshold === "number") {
|
|
280
|
+
distanceThreshold = this.options.distanceThreshold;
|
|
281
|
+
} else {
|
|
282
|
+
distanceThreshold = this.viewport[axis];
|
|
283
|
+
}
|
|
284
|
+
if (this.options.type === "mandatory" || this.options.type === "proximity" && distance <= distanceThreshold) {
|
|
290
285
|
this.lenis.scrollTo(snap.value, {
|
|
291
286
|
lerp: this.options.lerp,
|
|
292
287
|
easing: this.options.easing,
|
package/dist/lenis-snap.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../packages/snap/src/debounce.ts","../packages/snap/src/element.ts","../packages/snap/src/uid.ts","../packages/snap/src/snap.ts","../packages/snap/browser.ts"],"sourcesContent":["export function debounce<CB extends (...args: any[]) => void>(\n callback: CB,\n delay: number\n) {\n let timer: number | undefined\n return function <T>(this: T, ...args: Parameters<typeof callback>) {\n let context = this\n clearTimeout(timer)\n timer = setTimeout(() => {\n timer = undefined\n callback.apply(context, args)\n }, delay)\n }\n}\n","function removeParentSticky(element: HTMLElement) {\n const position = getComputedStyle(element).position\n\n const isSticky = position === 'sticky'\n\n if (isSticky) {\n element.style.setProperty('position', 'static')\n element.dataset.sticky = 'true'\n }\n\n if (element.offsetParent) {\n removeParentSticky(element.offsetParent as HTMLElement)\n }\n}\n\nfunction addParentSticky(element: HTMLElement) {\n if (element?.dataset?.sticky === 'true') {\n element.style.removeProperty('position')\n delete element.dataset.sticky\n }\n\n if (element.offsetParent) {\n addParentSticky(element.offsetParent as HTMLElement)\n }\n}\n\nfunction offsetTop(element: HTMLElement, accumulator = 0) {\n const top = accumulator + element.offsetTop\n if (element.offsetParent) {\n return offsetTop(element.offsetParent as HTMLElement, top)\n }\n return top\n}\n\nfunction offsetLeft(element: HTMLElement, accumulator = 0) {\n const left = accumulator + element.offsetLeft\n if (element.offsetParent) {\n return offsetLeft(element.offsetParent as HTMLElement, left)\n }\n return left\n}\n\nfunction scrollTop(element: HTMLElement, accumulator = 0) {\n const top = accumulator + element.scrollTop\n if (element.offsetParent) {\n return scrollTop(element.offsetParent as HTMLElement, top)\n }\n return top + window.scrollY\n}\n\nfunction scrollLeft(element: HTMLElement, accumulator = 0) {\n const left = accumulator + element.scrollLeft\n if (element.offsetParent) {\n return scrollLeft(element.offsetParent as HTMLElement, left)\n }\n return left + window.scrollX\n}\n\nexport type SnapElementOptions = {\n align?: string[]\n ignoreSticky?: boolean\n ignoreTransform?: boolean\n}\n\ntype Rect = {\n top: number\n left: number\n width: number\n height: number\n x: number\n y: number\n bottom: number\n right: number\n element: HTMLElement\n}\n\nexport class SnapElement {\n element: HTMLElement\n options: SnapElementOptions\n align: string[]\n // @ts-ignore\n rect: Rect = {}\n wrapperResizeObserver: ResizeObserver\n resizeObserver: ResizeObserver\n\n constructor(\n element: HTMLElement,\n {\n align = ['start'],\n ignoreSticky = true,\n ignoreTransform = false,\n }: SnapElementOptions = {}\n ) {\n this.element = element\n\n this.options = { align, ignoreSticky, ignoreTransform }\n\n // this.ignoreSticky = ignoreSticky\n // this.ignoreTransform = ignoreTransform\n\n this.align = [align].flat()\n\n // TODO: assing rect immediately\n\n this.wrapperResizeObserver = new ResizeObserver(this.onWrapperResize)\n this.wrapperResizeObserver.observe(document.body)\n this.onWrapperResize()\n\n this.resizeObserver = new ResizeObserver(this.onResize)\n this.resizeObserver.observe(this.element)\n this.setRect({\n width: this.element.offsetWidth,\n height: this.element.offsetHeight,\n })\n }\n\n destroy() {\n this.wrapperResizeObserver.disconnect()\n this.resizeObserver.disconnect()\n }\n\n setRect({\n top,\n left,\n width,\n height,\n element,\n }: {\n top?: number\n left?: number\n width?: number\n height?: number\n element?: HTMLElement\n } = {}) {\n top = top ?? this.rect.top\n left = left ?? this.rect.left\n width = width ?? this.rect.width\n height = height ?? this.rect.height\n element = element ?? this.rect.element\n\n if (\n top === this.rect.top &&\n left === this.rect.left &&\n width === this.rect.width &&\n height === this.rect.height &&\n element === this.rect.element\n )\n return\n\n this.rect.top = top\n this.rect.y = top\n this.rect.width = width\n this.rect.height = height\n this.rect.left = left\n this.rect.x = left\n this.rect.bottom = top + height\n this.rect.right = left + width\n }\n\n onWrapperResize = () => {\n let top, left\n\n if (this.options.ignoreSticky) removeParentSticky(this.element)\n if (this.options.ignoreTransform) {\n top = offsetTop(this.element)\n left = offsetLeft(this.element)\n } else {\n const rect = this.element.getBoundingClientRect()\n top = rect.top + scrollTop(this.element)\n left = rect.left + scrollLeft(this.element)\n }\n if (this.options.ignoreSticky) addParentSticky(this.element)\n\n this.setRect({ top, left })\n }\n\n onResize = ([entry]: ResizeObserverEntry[]) => {\n if (!entry?.borderBoxSize[0]) return\n const width = entry.borderBoxSize[0].inlineSize\n const height = entry.borderBoxSize[0].blockSize\n\n this.setRect({ width, height })\n }\n}\n","let index = 0\n\nexport type UID = number\n\nexport function uid(): UID {\n return index++\n}\n","import type Lenis from 'lenis'\nimport type { UserData } from 'lenis'\nimport { debounce } from './debounce'\nimport type { SnapElementOptions } from './element'\nimport { SnapElement } from './element'\nimport type { SnapItem, SnapOptions } from './types'\nimport type { UID } from './uid'\nimport { uid } from './uid'\n\n// TODO:\n// - horizontal\n// - fix trackpad snapping too soon due to velocity (fuck Apple)\n// - fix wheel scrolling after limits (see console scroll to)\n// - fix touch scroll, do not snap when not released\n// - arrow, spacebar\n\ntype RequiredPick<T, F extends keyof T> = Omit<T, F> & Required<Pick<T, F>>\n\n/**\n * Snap class to handle the snap functionality\n *\n * @example\n * const snap = new Snap(lenis, {\n * type: 'mandatory', // 'mandatory', 'proximity'\n * lerp: 0.1,\n * duration: 1,\n * easing: (t) => t,\n * onSnapStart: (snap) => {\n * console.log('onSnapStart', snap)\n * },\n * onSnapComplete: (snap) => {\n * console.log('onSnapComplete', snap)\n * },\n * })\n *\n * snap.add(500) // snap at 500px\n *\n * const removeSnap = snap.add(500)\n *\n * if (someCondition) {\n * removeSnap()\n * }\n */\nexport class Snap {\n options: RequiredPick<SnapOptions, 'type' | 'velocityThreshold' | 'debounce'>\n elements = new Map<UID, SnapElement>()\n snaps = new Map<UID, SnapItem>()\n viewport = {\n width: window.innerWidth,\n height: window.innerHeight,\n }\n isStopped = false\n onSnapDebounced: () => void\n\n constructor(\n private lenis: Lenis,\n {\n type = 'mandatory',\n lerp,\n easing,\n duration,\n velocityThreshold = 1,\n debounce: debounceDelay = 0,\n onSnapStart,\n onSnapComplete,\n }: SnapOptions = {}\n ) {\n this.options = {\n type,\n lerp,\n easing,\n duration,\n velocityThreshold,\n debounce: debounceDelay,\n onSnapStart,\n onSnapComplete,\n }\n\n this.onWindowResize()\n window.addEventListener('resize', this.onWindowResize, false)\n\n this.onSnapDebounced = debounce(this.onSnap, this.options.debounce)\n\n this.lenis.on('scroll', this.onScroll)\n }\n\n /**\n * Destroy the snap instance\n */\n destroy() {\n this.lenis.off('scroll', this.onScroll)\n window.removeEventListener('resize', this.onWindowResize, false)\n this.elements.forEach((element) => element.destroy())\n }\n\n /**\n * Start the snap after it has been stopped\n */\n start() {\n this.isStopped = false\n }\n\n /**\n * Stop the snap\n */\n stop() {\n this.isStopped = true\n }\n\n /**\n * Add a snap to the snap instance\n *\n * @param value The value to snap to\n * @param userData User data that will be forwarded through the snap event\n * @returns Unsubscribe function\n */\n add(value: number, userData: UserData = {}) {\n const id = uid()\n\n this.snaps.set(id, { value, userData })\n\n return () => this.remove(id)\n }\n\n /**\n * Remove a snap from the snap instance\n *\n * @param id The snap id of the snap to remove\n */\n remove(id: UID) {\n this.snaps.delete(id)\n }\n\n /**\n * Add an element to the snap instance\n *\n * @param element The element to add\n * @param options The options for the element\n * @returns Unsubscribe function\n */\n addElement(element: HTMLElement, options = {} as SnapElementOptions) {\n const id = uid()\n\n this.elements.set(id, new SnapElement(element, options))\n\n return () => this.removeElement(id)\n }\n\n /**\n * Remove an element from the snap instance\n *\n * @param id The snap id of the snap element to remove\n */\n removeElement(id: UID) {\n this.elements.delete(id)\n }\n\n private onWindowResize = () => {\n this.viewport.width = window.innerWidth\n this.viewport.height = window.innerHeight\n }\n\n private onScroll = ({\n // scroll,\n // limit,\n lastVelocity,\n velocity,\n // isScrolling,\n userData,\n }: // isHorizontal,\n Lenis) => {\n if (this.isStopped) return\n\n // return\n const isDecelerating = Math.abs(lastVelocity) > Math.abs(velocity)\n const isTurningBack =\n Math.sign(lastVelocity) !== Math.sign(velocity) && velocity !== 0\n\n if (\n Math.abs(velocity) < this.options.velocityThreshold &&\n // !isTouching &&\n isDecelerating &&\n !isTurningBack &&\n userData?.initiator !== 'snap'\n ) {\n this.onSnapDebounced()\n }\n }\n\n private onSnap = () => {\n let { scroll, isHorizontal } = this.lenis\n scroll = Math.ceil(this.lenis.scroll)\n\n let snaps = [...this.snaps.values()] as SnapItem[]\n\n this.elements.forEach(({ rect, align }) => {\n let value: number | undefined\n\n align.forEach((align) => {\n if (align === 'start') {\n value = rect.top\n } else if (align === 'center') {\n value = isHorizontal\n ? rect.left + rect.width / 2 - this.viewport.width / 2\n : rect.top + rect.height / 2 - this.viewport.height / 2\n } else if (align === 'end') {\n value = isHorizontal\n ? rect.left + rect.width - this.viewport.width\n : rect.top + rect.height - this.viewport.height\n }\n\n if (typeof value === 'number') {\n snaps.push({ value: Math.ceil(value), userData: {} })\n }\n })\n })\n\n snaps = snaps.sort((a, b) => Math.abs(a.value) - Math.abs(b.value))\n\n let prevSnap = snaps.findLast(({ value }) => value <= scroll)\n if (prevSnap === undefined) prevSnap = snaps[0]!\n const distanceToPrevSnap = Math.abs(scroll - prevSnap.value)\n\n let nextSnap = snaps.find(({ value }) => value >= scroll)\n if (nextSnap === undefined) nextSnap = snaps[snaps.length - 1]!\n const distanceToNextSnap = Math.abs(scroll - nextSnap.value)\n\n const snap = distanceToPrevSnap < distanceToNextSnap ? prevSnap : nextSnap\n\n const distance = Math.abs(scroll - snap.value)\n\n if (\n this.options.type === 'mandatory' ||\n (this.options.type === 'proximity' &&\n distance <=\n (isHorizontal\n ? this.lenis.dimensions.width\n : this.lenis.dimensions.height))\n ) {\n // this.__isScrolling = true\n // this.onSnapStart?.(snap)\n\n // console.log('scroll to')\n\n this.lenis.scrollTo(snap.value, {\n lerp: this.options.lerp,\n easing: this.options.easing,\n duration: this.options.duration,\n userData: { initiator: 'snap' },\n onStart: () => {\n this.options.onSnapStart?.(snap)\n },\n onComplete: () => {\n this.options.onSnapComplete?.(snap)\n },\n })\n }\n\n // console.timeEnd('scroll')\n }\n}\n","// This file serves as an entry point for the package\nimport { Snap } from './src/snap'\nglobalThis.Snap = Snap\n"],"mappings":"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","names":["align"]}
|
|
1
|
+
{"version":3,"sources":["../packages/snap/src/debounce.ts","../packages/snap/src/element.ts","../packages/snap/src/uid.ts","../packages/snap/src/snap.ts","../packages/snap/browser.ts"],"sourcesContent":["export function debounce<CB extends (...args: any[]) => void>(\r\n callback: CB,\r\n delay: number\r\n) {\r\n let timer: number | undefined\r\n return function <T>(this: T, ...args: Parameters<typeof callback>) {\r\n let context = this\r\n clearTimeout(timer)\r\n timer = setTimeout(() => {\r\n timer = undefined\r\n callback.apply(context, args)\r\n }, delay)\r\n }\r\n}\r\n","function removeParentSticky(element: HTMLElement) {\r\n const position = getComputedStyle(element).position\r\n\r\n const isSticky = position === 'sticky'\r\n\r\n if (isSticky) {\r\n element.style.setProperty('position', 'static')\r\n element.dataset.sticky = 'true'\r\n }\r\n\r\n if (element.offsetParent) {\r\n removeParentSticky(element.offsetParent as HTMLElement)\r\n }\r\n}\r\n\r\nfunction addParentSticky(element: HTMLElement) {\r\n if (element?.dataset?.sticky === 'true') {\r\n element.style.removeProperty('position')\r\n delete element.dataset.sticky\r\n }\r\n\r\n if (element.offsetParent) {\r\n addParentSticky(element.offsetParent as HTMLElement)\r\n }\r\n}\r\n\r\nfunction offsetTop(element: HTMLElement, accumulator = 0) {\r\n const top = accumulator + element.offsetTop\r\n if (element.offsetParent) {\r\n return offsetTop(element.offsetParent as HTMLElement, top)\r\n }\r\n return top\r\n}\r\n\r\nfunction offsetLeft(element: HTMLElement, accumulator = 0) {\r\n const left = accumulator + element.offsetLeft\r\n if (element.offsetParent) {\r\n return offsetLeft(element.offsetParent as HTMLElement, left)\r\n }\r\n return left\r\n}\r\n\r\nfunction scrollTop(element: HTMLElement, accumulator = 0) {\r\n const top = accumulator + element.scrollTop\r\n if (element.offsetParent) {\r\n return scrollTop(element.offsetParent as HTMLElement, top)\r\n }\r\n return top + window.scrollY\r\n}\r\n\r\nfunction scrollLeft(element: HTMLElement, accumulator = 0) {\r\n const left = accumulator + element.scrollLeft\r\n if (element.offsetParent) {\r\n return scrollLeft(element.offsetParent as HTMLElement, left)\r\n }\r\n return left + window.scrollX\r\n}\r\n\r\nexport type SnapElementOptions = {\r\n align?: string | string[]\r\n ignoreSticky?: boolean\r\n ignoreTransform?: boolean\r\n}\r\n\r\ntype Rect = {\r\n top: number\r\n left: number\r\n width: number\r\n height: number\r\n x: number\r\n y: number\r\n bottom: number\r\n right: number\r\n element: HTMLElement\r\n}\r\n\r\nexport class SnapElement {\r\n element: HTMLElement\r\n options: SnapElementOptions\r\n align: string[]\r\n // @ts-ignore\r\n rect: Rect = {}\r\n wrapperResizeObserver: ResizeObserver\r\n resizeObserver: ResizeObserver\r\n\r\n constructor(\r\n element: HTMLElement,\r\n {\r\n align = ['start'],\r\n ignoreSticky = true,\r\n ignoreTransform = false,\r\n }: SnapElementOptions = {}\r\n ) {\r\n this.element = element\r\n\r\n this.options = { align, ignoreSticky, ignoreTransform }\r\n\r\n // this.ignoreSticky = ignoreSticky\r\n // this.ignoreTransform = ignoreTransform\r\n\r\n this.align = [align].flat()\r\n\r\n // TODO: assing rect immediately\r\n\r\n this.wrapperResizeObserver = new ResizeObserver(this.onWrapperResize)\r\n this.wrapperResizeObserver.observe(document.body)\r\n this.onWrapperResize()\r\n\r\n this.resizeObserver = new ResizeObserver(this.onResize)\r\n this.resizeObserver.observe(this.element)\r\n this.setRect({\r\n width: this.element.offsetWidth,\r\n height: this.element.offsetHeight,\r\n })\r\n }\r\n\r\n destroy() {\r\n this.wrapperResizeObserver.disconnect()\r\n this.resizeObserver.disconnect()\r\n }\r\n\r\n setRect({\r\n top,\r\n left,\r\n width,\r\n height,\r\n element,\r\n }: {\r\n top?: number\r\n left?: number\r\n width?: number\r\n height?: number\r\n element?: HTMLElement\r\n } = {}) {\r\n top = top ?? this.rect.top\r\n left = left ?? this.rect.left\r\n width = width ?? this.rect.width\r\n height = height ?? this.rect.height\r\n element = element ?? this.rect.element\r\n\r\n if (\r\n top === this.rect.top &&\r\n left === this.rect.left &&\r\n width === this.rect.width &&\r\n height === this.rect.height &&\r\n element === this.rect.element\r\n )\r\n return\r\n\r\n this.rect.top = top\r\n this.rect.y = top\r\n this.rect.width = width\r\n this.rect.height = height\r\n this.rect.left = left\r\n this.rect.x = left\r\n this.rect.bottom = top + height\r\n this.rect.right = left + width\r\n }\r\n\r\n onWrapperResize = () => {\r\n let top, left\r\n\r\n if (this.options.ignoreSticky) removeParentSticky(this.element)\r\n if (this.options.ignoreTransform) {\r\n top = offsetTop(this.element)\r\n left = offsetLeft(this.element)\r\n } else {\r\n const rect = this.element.getBoundingClientRect()\r\n top = rect.top + scrollTop(this.element)\r\n left = rect.left + scrollLeft(this.element)\r\n }\r\n if (this.options.ignoreSticky) addParentSticky(this.element)\r\n\r\n this.setRect({ top, left })\r\n }\r\n\r\n onResize = ([entry]: ResizeObserverEntry[]) => {\r\n if (!entry?.borderBoxSize[0]) return\r\n const width = entry.borderBoxSize[0].inlineSize\r\n const height = entry.borderBoxSize[0].blockSize\r\n\r\n this.setRect({ width, height })\r\n }\r\n}\r\n","let index = 0\r\n\r\nexport type UID = number\r\n\r\nexport function uid(): UID {\r\n return index++\r\n}\r\n","import type Lenis from 'lenis'\r\nimport type { UserData } from 'lenis'\r\nimport { debounce } from './debounce'\r\nimport type { SnapElementOptions } from './element'\r\nimport { SnapElement } from './element'\r\nimport type { SnapItem, SnapOptions } from './types'\r\nimport type { UID } from './uid'\r\nimport { uid } from './uid'\r\n\r\n// TODO:\r\n// - horizontal\r\n// - fix trackpad snapping too soon due to velocity (fuck Apple)\r\n// - fix wheel scrolling after limits (see console scroll to)\r\n// - fix touch scroll, do not snap when not released\r\n// - arrow, spacebar\r\n\r\ntype RequiredPick<T, F extends keyof T> = Omit<T, F> & Required<Pick<T, F>>\r\n\r\n/**\r\n * Snap class to handle the snap functionality\r\n *\r\n * @example\r\n * const snap = new Snap(lenis, {\r\n * type: 'mandatory', // 'mandatory', 'proximity'\r\n * lerp: 0.1,\r\n * duration: 1,\r\n * easing: (t) => t,\r\n * onSnapStart: (snap) => {\r\n * console.log('onSnapStart', snap)\r\n * },\r\n * onSnapComplete: (snap) => {\r\n * console.log('onSnapComplete', snap)\r\n * },\r\n * })\r\n *\r\n * snap.add(500) // snap at 500px\r\n *\r\n * const removeSnap = snap.add(500)\r\n *\r\n * if (someCondition) {\r\n * removeSnap()\r\n * }\r\n */\r\nexport class Snap {\r\n options: RequiredPick<SnapOptions, 'type' | 'velocityThreshold' | 'debounce'>\r\n elements = new Map<UID, SnapElement>()\r\n snaps = new Map<UID, SnapItem>()\r\n viewport = {\r\n width: window.innerWidth,\r\n height: window.innerHeight,\r\n }\r\n isStopped = false\r\n onSnapDebounced: () => void\r\n\r\n constructor(\r\n private lenis: Lenis,\r\n {\r\n type = 'mandatory',\r\n lerp,\r\n easing,\r\n duration,\r\n distanceThreshold = '100%',\r\n velocityThreshold = 1.2,\r\n debounce: debounceDelay = 50,\r\n onSnapStart,\r\n onSnapComplete,\r\n }: SnapOptions = {}\r\n ) {\r\n this.options = {\r\n type,\r\n lerp,\r\n easing,\r\n duration,\r\n distanceThreshold,\r\n velocityThreshold,\r\n debounce: debounceDelay,\r\n onSnapStart,\r\n onSnapComplete,\r\n }\r\n\r\n this.onWindowResize()\r\n window.addEventListener('resize', this.onWindowResize, false)\r\n\r\n this.onSnapDebounced = debounce(this.onSnap, this.options.debounce)\r\n\r\n this.lenis.on('scroll', this.onScroll)\r\n }\r\n\r\n /**\r\n * Destroy the snap instance\r\n */\r\n destroy() {\r\n this.lenis.off('scroll', this.onScroll)\r\n window.removeEventListener('resize', this.onWindowResize, false)\r\n this.elements.forEach((element) => element.destroy())\r\n }\r\n\r\n /**\r\n * Start the snap after it has been stopped\r\n */\r\n start() {\r\n this.isStopped = false\r\n }\r\n\r\n /**\r\n * Stop the snap\r\n */\r\n stop() {\r\n this.isStopped = true\r\n }\r\n\r\n /**\r\n * Add a snap to the snap instance\r\n *\r\n * @param value The value to snap to\r\n * @param userData User data that will be forwarded through the snap event\r\n * @returns Unsubscribe function\r\n */\r\n add(value: number, userData: UserData = {}) {\r\n const id = uid()\r\n\r\n this.snaps.set(id, { value, userData })\r\n\r\n return () => this.snaps.delete(id)\r\n }\r\n\r\n /**\r\n * Add an element to the snap instance\r\n *\r\n * @param element The element to add\r\n * @param options The options for the element\r\n * @returns Unsubscribe function\r\n */\r\n addElement(element: HTMLElement, options: SnapElementOptions = {}) {\r\n const id = uid()\r\n\r\n this.elements.set(id, new SnapElement(element, options))\r\n\r\n return () => this.elements.delete(id)\r\n }\r\n\r\n private onWindowResize = () => {\r\n this.viewport.width = window.innerWidth\r\n this.viewport.height = window.innerHeight\r\n }\r\n\r\n private onScroll = ({\r\n // scroll,\r\n // limit,\r\n lastVelocity,\r\n velocity,\r\n // isScrolling,\r\n userData,\r\n }: // isHorizontal,\r\n Lenis) => {\r\n if (this.isStopped) return\r\n\r\n // return\r\n const isDecelerating = Math.abs(lastVelocity) > Math.abs(velocity)\r\n const isTurningBack =\r\n Math.sign(lastVelocity) !== Math.sign(velocity) && velocity !== 0\r\n\r\n if (\r\n Math.abs(velocity) < this.options.velocityThreshold &&\r\n // !isTouching &&\r\n isDecelerating &&\r\n !isTurningBack &&\r\n userData?.initiator !== 'snap'\r\n ) {\r\n this.onSnapDebounced()\r\n }\r\n }\r\n\r\n private onSnap = () => {\r\n let { scroll, isHorizontal } = this.lenis\r\n scroll = Math.ceil(this.lenis.scroll)\r\n\r\n let snaps = [...this.snaps.values()] as SnapItem[]\r\n\r\n this.elements.forEach(({ rect, align }) => {\r\n let value: number | undefined\r\n\r\n align.forEach((align) => {\r\n if (align === 'start') {\r\n value = rect.top\r\n } else if (align === 'center') {\r\n value = isHorizontal\r\n ? rect.left + rect.width / 2 - this.viewport.width / 2\r\n : rect.top + rect.height / 2 - this.viewport.height / 2\r\n } else if (align === 'end') {\r\n value = isHorizontal\r\n ? rect.left + rect.width - this.viewport.width\r\n : rect.top + rect.height - this.viewport.height\r\n }\r\n\r\n if (typeof value === 'number') {\r\n snaps.push({ value: Math.ceil(value), userData: {} })\r\n }\r\n })\r\n })\r\n\r\n snaps = snaps.sort((a, b) => Math.abs(a.value) - Math.abs(b.value))\r\n\r\n let prevSnap = snaps.findLast(({ value }) => value <= scroll)\r\n if (prevSnap === undefined) prevSnap = snaps[0]!\r\n const distanceToPrevSnap = Math.abs(scroll - prevSnap.value)\r\n\r\n let nextSnap = snaps.find(({ value }) => value >= scroll)\r\n if (nextSnap === undefined) nextSnap = snaps[snaps.length - 1]!\r\n const distanceToNextSnap = Math.abs(scroll - nextSnap.value)\r\n\r\n const snap = distanceToPrevSnap < distanceToNextSnap ? prevSnap : nextSnap\r\n\r\n const distance = Math.abs(scroll - snap.value)\r\n\r\n let distanceThreshold\r\n\r\n const axis = isHorizontal ? 'width' : 'height'\r\n\r\n if (\r\n typeof this.options.distanceThreshold === 'string' &&\r\n this.options.distanceThreshold.endsWith('%')\r\n ) {\r\n distanceThreshold =\r\n (Number(this.options.distanceThreshold.replace('%', '')) / 100) *\r\n this.viewport[axis]\r\n } else if (typeof this.options.distanceThreshold === 'number') {\r\n distanceThreshold = this.options.distanceThreshold\r\n } else {\r\n distanceThreshold = this.viewport[axis]\r\n }\r\n\r\n if (\r\n this.options.type === 'mandatory' ||\r\n (this.options.type === 'proximity' && distance <= distanceThreshold)\r\n ) {\r\n // this.__isScrolling = true\r\n // this.onSnapStart?.(snap)\r\n\r\n // console.log('scroll to')\r\n\r\n this.lenis.scrollTo(snap.value, {\r\n lerp: this.options.lerp,\r\n easing: this.options.easing,\r\n duration: this.options.duration,\r\n userData: { initiator: 'snap' },\r\n onStart: () => {\r\n this.options.onSnapStart?.(snap)\r\n },\r\n onComplete: () => {\r\n this.options.onSnapComplete?.(snap)\r\n },\r\n })\r\n }\r\n\r\n // console.timeEnd('scroll')\r\n }\r\n}\r\n","// This file serves as an entry point for the package\r\nimport { Snap } from './src/snap'\r\nglobalThis.Snap = Snap\r\n"],"mappings":"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","names":["align"]}
|
package/dist/lenis-snap.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function t(e){"sticky"===getComputedStyle(e).position&&(e.style.setProperty("position","static"),e.dataset.sticky="true"),e.offsetParent&&t(e.offsetParent)}function e(t){"true"===t?.dataset?.sticky&&(t.style.removeProperty("position"),delete t.dataset.sticky),t.offsetParent&&e(t.offsetParent)}function i(t,e=0){const s=e+t.offsetTop;return t.offsetParent?i(t.offsetParent,s):s}function s(t,e=0){const i=e+t.offsetLeft;return t.offsetParent?s(t.offsetParent,i):i}function o(t,e=0){const i=e+t.scrollTop;return t.offsetParent?o(t.offsetParent,i):i+window.scrollY}function n(t,e=0){const i=e+t.scrollLeft;return t.offsetParent?n(t.offsetParent,i):i+window.scrollX}var r=class{element;options;align;rect={};wrapperResizeObserver;resizeObserver;constructor(t,{align:e=["start"],ignoreSticky:i=!0,ignoreTransform:s=!1}={}){this.element=t,this.options={align:e,ignoreSticky:i,ignoreTransform:s},this.align=[e].flat(),this.wrapperResizeObserver=new ResizeObserver(this.onWrapperResize),this.wrapperResizeObserver.observe(document.body),this.onWrapperResize(),this.resizeObserver=new ResizeObserver(this.onResize),this.resizeObserver.observe(this.element),this.setRect({width:this.element.offsetWidth,height:this.element.offsetHeight})}destroy(){this.wrapperResizeObserver.disconnect(),this.resizeObserver.disconnect()}setRect({top:t,left:e,width:i,height:s,element:o}={}){t=t??this.rect.top,e=e??this.rect.left,i=i??this.rect.width,s=s??this.rect.height,o=o??this.rect.element,t===this.rect.top&&e===this.rect.left&&i===this.rect.width&&s===this.rect.height&&o===this.rect.element||(this.rect.top=t,this.rect.y=t,this.rect.width=i,this.rect.height=s,this.rect.left=e,this.rect.x=e,this.rect.bottom=t+s,this.rect.right=e+i)}onWrapperResize=()=>{let r,h;if(this.options.ignoreSticky&&t(this.element),this.options.ignoreTransform)r=i(this.element),h=s(this.element);else{const t=this.element.getBoundingClientRect();r=t.top+o(this.element),h=t.left+n(this.element)}this.options.ignoreSticky&&e(this.element),this.setRect({top:r,left:h})};onResize=([t])=>{if(!t?.borderBoxSize[0])return;const e=t.borderBoxSize[0].inlineSize,i=t.borderBoxSize[0].blockSize;this.setRect({width:e,height:i})}},h=0;function a(){return h++}globalThis.Snap=class{constructor(t,{type:e="mandatory",lerp:i,easing:s,duration:o,
|
|
1
|
+
function t(e){"sticky"===getComputedStyle(e).position&&(e.style.setProperty("position","static"),e.dataset.sticky="true"),e.offsetParent&&t(e.offsetParent)}function e(t){"true"===t?.dataset?.sticky&&(t.style.removeProperty("position"),delete t.dataset.sticky),t.offsetParent&&e(t.offsetParent)}function i(t,e=0){const s=e+t.offsetTop;return t.offsetParent?i(t.offsetParent,s):s}function s(t,e=0){const i=e+t.offsetLeft;return t.offsetParent?s(t.offsetParent,i):i}function o(t,e=0){const i=e+t.scrollTop;return t.offsetParent?o(t.offsetParent,i):i+window.scrollY}function n(t,e=0){const i=e+t.scrollLeft;return t.offsetParent?n(t.offsetParent,i):i+window.scrollX}var r=class{element;options;align;rect={};wrapperResizeObserver;resizeObserver;constructor(t,{align:e=["start"],ignoreSticky:i=!0,ignoreTransform:s=!1}={}){this.element=t,this.options={align:e,ignoreSticky:i,ignoreTransform:s},this.align=[e].flat(),this.wrapperResizeObserver=new ResizeObserver(this.onWrapperResize),this.wrapperResizeObserver.observe(document.body),this.onWrapperResize(),this.resizeObserver=new ResizeObserver(this.onResize),this.resizeObserver.observe(this.element),this.setRect({width:this.element.offsetWidth,height:this.element.offsetHeight})}destroy(){this.wrapperResizeObserver.disconnect(),this.resizeObserver.disconnect()}setRect({top:t,left:e,width:i,height:s,element:o}={}){t=t??this.rect.top,e=e??this.rect.left,i=i??this.rect.width,s=s??this.rect.height,o=o??this.rect.element,t===this.rect.top&&e===this.rect.left&&i===this.rect.width&&s===this.rect.height&&o===this.rect.element||(this.rect.top=t,this.rect.y=t,this.rect.width=i,this.rect.height=s,this.rect.left=e,this.rect.x=e,this.rect.bottom=t+s,this.rect.right=e+i)}onWrapperResize=()=>{let r,h;if(this.options.ignoreSticky&&t(this.element),this.options.ignoreTransform)r=i(this.element),h=s(this.element);else{const t=this.element.getBoundingClientRect();r=t.top+o(this.element),h=t.left+n(this.element)}this.options.ignoreSticky&&e(this.element),this.setRect({top:r,left:h})};onResize=([t])=>{if(!t?.borderBoxSize[0])return;const e=t.borderBoxSize[0].inlineSize,i=t.borderBoxSize[0].blockSize;this.setRect({width:e,height:i})}},h=0;function a(){return h++}globalThis.Snap=class{constructor(t,{type:e="mandatory",lerp:i,easing:s,duration:o,distanceThreshold:n="100%",velocityThreshold:r=1.2,debounce:h=50,onSnapStart:a,onSnapComplete:l}={}){this.lenis=t,this.options={type:e,lerp:i,easing:s,duration:o,distanceThreshold:n,velocityThreshold:r,debounce:h,onSnapStart:a,onSnapComplete:l},this.onWindowResize(),window.addEventListener("resize",this.onWindowResize,!1),this.onSnapDebounced=function(t,e){let i;return function(...s){let o=this;clearTimeout(i),i=setTimeout((()=>{i=void 0,t.apply(o,s)}),e)}}(this.onSnap,this.options.debounce),this.lenis.on("scroll",this.onScroll)}options;elements=new Map;snaps=new Map;viewport={width:window.innerWidth,height:window.innerHeight};isStopped=!1;onSnapDebounced;destroy(){this.lenis.off("scroll",this.onScroll),window.removeEventListener("resize",this.onWindowResize,!1),this.elements.forEach((t=>t.destroy()))}start(){this.isStopped=!1}stop(){this.isStopped=!0}add(t,e={}){const i=a();return this.snaps.set(i,{value:t,userData:e}),()=>this.snaps.delete(i)}addElement(t,e={}){const i=a();return this.elements.set(i,new r(t,e)),()=>this.elements.delete(i)}onWindowResize=()=>{this.viewport.width=window.innerWidth,this.viewport.height=window.innerHeight};onScroll=({lastVelocity:t,velocity:e,userData:i})=>{if(this.isStopped)return;const s=Math.abs(t)>Math.abs(e),o=Math.sign(t)!==Math.sign(e)&&0!==e;Math.abs(e)<this.options.velocityThreshold&&s&&!o&&"snap"!==i?.initiator&&this.onSnapDebounced()};onSnap=()=>{let{scroll:t,isHorizontal:e}=this.lenis;t=Math.ceil(this.lenis.scroll);let i=[...this.snaps.values()];this.elements.forEach((({rect:t,align:s})=>{let o;s.forEach((s=>{"start"===s?o=t.top:"center"===s?o=e?t.left+t.width/2-this.viewport.width/2:t.top+t.height/2-this.viewport.height/2:"end"===s&&(o=e?t.left+t.width-this.viewport.width:t.top+t.height-this.viewport.height),"number"==typeof o&&i.push({value:Math.ceil(o),userData:{}})}))})),i=i.sort(((t,e)=>Math.abs(t.value)-Math.abs(e.value)));let s=i.findLast((({value:e})=>e<=t));void 0===s&&(s=i[0]);const o=Math.abs(t-s.value);let n=i.find((({value:e})=>e>=t));void 0===n&&(n=i[i.length-1]);const r=o<Math.abs(t-n.value)?s:n,h=Math.abs(t-r.value);let a;const l=e?"width":"height";a="string"==typeof this.options.distanceThreshold&&this.options.distanceThreshold.endsWith("%")?Number(this.options.distanceThreshold.replace("%",""))/100*this.viewport[l]:"number"==typeof this.options.distanceThreshold?this.options.distanceThreshold:this.viewport[l],("mandatory"===this.options.type||"proximity"===this.options.type&&h<=a)&&this.lenis.scrollTo(r.value,{lerp:this.options.lerp,easing:this.options.easing,duration:this.options.duration,userData:{initiator:"snap"},onStart:()=>{this.options.onSnapStart?.(r)},onComplete:()=>{this.options.onSnapComplete?.(r)}})}};//# sourceMappingURL=lenis-snap.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../packages/snap/src/debounce.ts","../packages/snap/src/element.ts","../packages/snap/src/uid.ts","../packages/snap/src/snap.ts","../packages/snap/browser.ts"],"sourcesContent":["export function debounce<CB extends (...args: any[]) => void>(\n callback: CB,\n delay: number\n) {\n let timer: number | undefined\n return function <T>(this: T, ...args: Parameters<typeof callback>) {\n let context = this\n clearTimeout(timer)\n timer = setTimeout(() => {\n timer = undefined\n callback.apply(context, args)\n }, delay)\n }\n}\n","function removeParentSticky(element: HTMLElement) {\n const position = getComputedStyle(element).position\n\n const isSticky = position === 'sticky'\n\n if (isSticky) {\n element.style.setProperty('position', 'static')\n element.dataset.sticky = 'true'\n }\n\n if (element.offsetParent) {\n removeParentSticky(element.offsetParent as HTMLElement)\n }\n}\n\nfunction addParentSticky(element: HTMLElement) {\n if (element?.dataset?.sticky === 'true') {\n element.style.removeProperty('position')\n delete element.dataset.sticky\n }\n\n if (element.offsetParent) {\n addParentSticky(element.offsetParent as HTMLElement)\n }\n}\n\nfunction offsetTop(element: HTMLElement, accumulator = 0) {\n const top = accumulator + element.offsetTop\n if (element.offsetParent) {\n return offsetTop(element.offsetParent as HTMLElement, top)\n }\n return top\n}\n\nfunction offsetLeft(element: HTMLElement, accumulator = 0) {\n const left = accumulator + element.offsetLeft\n if (element.offsetParent) {\n return offsetLeft(element.offsetParent as HTMLElement, left)\n }\n return left\n}\n\nfunction scrollTop(element: HTMLElement, accumulator = 0) {\n const top = accumulator + element.scrollTop\n if (element.offsetParent) {\n return scrollTop(element.offsetParent as HTMLElement, top)\n }\n return top + window.scrollY\n}\n\nfunction scrollLeft(element: HTMLElement, accumulator = 0) {\n const left = accumulator + element.scrollLeft\n if (element.offsetParent) {\n return scrollLeft(element.offsetParent as HTMLElement, left)\n }\n return left + window.scrollX\n}\n\nexport type SnapElementOptions = {\n align?: string[]\n ignoreSticky?: boolean\n ignoreTransform?: boolean\n}\n\ntype Rect = {\n top: number\n left: number\n width: number\n height: number\n x: number\n y: number\n bottom: number\n right: number\n element: HTMLElement\n}\n\nexport class SnapElement {\n element: HTMLElement\n options: SnapElementOptions\n align: string[]\n // @ts-ignore\n rect: Rect = {}\n wrapperResizeObserver: ResizeObserver\n resizeObserver: ResizeObserver\n\n constructor(\n element: HTMLElement,\n {\n align = ['start'],\n ignoreSticky = true,\n ignoreTransform = false,\n }: SnapElementOptions = {}\n ) {\n this.element = element\n\n this.options = { align, ignoreSticky, ignoreTransform }\n\n // this.ignoreSticky = ignoreSticky\n // this.ignoreTransform = ignoreTransform\n\n this.align = [align].flat()\n\n // TODO: assing rect immediately\n\n this.wrapperResizeObserver = new ResizeObserver(this.onWrapperResize)\n this.wrapperResizeObserver.observe(document.body)\n this.onWrapperResize()\n\n this.resizeObserver = new ResizeObserver(this.onResize)\n this.resizeObserver.observe(this.element)\n this.setRect({\n width: this.element.offsetWidth,\n height: this.element.offsetHeight,\n })\n }\n\n destroy() {\n this.wrapperResizeObserver.disconnect()\n this.resizeObserver.disconnect()\n }\n\n setRect({\n top,\n left,\n width,\n height,\n element,\n }: {\n top?: number\n left?: number\n width?: number\n height?: number\n element?: HTMLElement\n } = {}) {\n top = top ?? this.rect.top\n left = left ?? this.rect.left\n width = width ?? this.rect.width\n height = height ?? this.rect.height\n element = element ?? this.rect.element\n\n if (\n top === this.rect.top &&\n left === this.rect.left &&\n width === this.rect.width &&\n height === this.rect.height &&\n element === this.rect.element\n )\n return\n\n this.rect.top = top\n this.rect.y = top\n this.rect.width = width\n this.rect.height = height\n this.rect.left = left\n this.rect.x = left\n this.rect.bottom = top + height\n this.rect.right = left + width\n }\n\n onWrapperResize = () => {\n let top, left\n\n if (this.options.ignoreSticky) removeParentSticky(this.element)\n if (this.options.ignoreTransform) {\n top = offsetTop(this.element)\n left = offsetLeft(this.element)\n } else {\n const rect = this.element.getBoundingClientRect()\n top = rect.top + scrollTop(this.element)\n left = rect.left + scrollLeft(this.element)\n }\n if (this.options.ignoreSticky) addParentSticky(this.element)\n\n this.setRect({ top, left })\n }\n\n onResize = ([entry]: ResizeObserverEntry[]) => {\n if (!entry?.borderBoxSize[0]) return\n const width = entry.borderBoxSize[0].inlineSize\n const height = entry.borderBoxSize[0].blockSize\n\n this.setRect({ width, height })\n }\n}\n","let index = 0\n\nexport type UID = number\n\nexport function uid(): UID {\n return index++\n}\n","import type Lenis from 'lenis'\nimport type { UserData } from 'lenis'\nimport { debounce } from './debounce'\nimport type { SnapElementOptions } from './element'\nimport { SnapElement } from './element'\nimport type { SnapItem, SnapOptions } from './types'\nimport type { UID } from './uid'\nimport { uid } from './uid'\n\n// TODO:\n// - horizontal\n// - fix trackpad snapping too soon due to velocity (fuck Apple)\n// - fix wheel scrolling after limits (see console scroll to)\n// - fix touch scroll, do not snap when not released\n// - arrow, spacebar\n\ntype RequiredPick<T, F extends keyof T> = Omit<T, F> & Required<Pick<T, F>>\n\n/**\n * Snap class to handle the snap functionality\n *\n * @example\n * const snap = new Snap(lenis, {\n * type: 'mandatory', // 'mandatory', 'proximity'\n * lerp: 0.1,\n * duration: 1,\n * easing: (t) => t,\n * onSnapStart: (snap) => {\n * console.log('onSnapStart', snap)\n * },\n * onSnapComplete: (snap) => {\n * console.log('onSnapComplete', snap)\n * },\n * })\n *\n * snap.add(500) // snap at 500px\n *\n * const removeSnap = snap.add(500)\n *\n * if (someCondition) {\n * removeSnap()\n * }\n */\nexport class Snap {\n options: RequiredPick<SnapOptions, 'type' | 'velocityThreshold' | 'debounce'>\n elements = new Map<UID, SnapElement>()\n snaps = new Map<UID, SnapItem>()\n viewport = {\n width: window.innerWidth,\n height: window.innerHeight,\n }\n isStopped = false\n onSnapDebounced: () => void\n\n constructor(\n private lenis: Lenis,\n {\n type = 'mandatory',\n lerp,\n easing,\n duration,\n velocityThreshold = 1,\n debounce: debounceDelay = 0,\n onSnapStart,\n onSnapComplete,\n }: SnapOptions = {}\n ) {\n this.options = {\n type,\n lerp,\n easing,\n duration,\n velocityThreshold,\n debounce: debounceDelay,\n onSnapStart,\n onSnapComplete,\n }\n\n this.onWindowResize()\n window.addEventListener('resize', this.onWindowResize, false)\n\n this.onSnapDebounced = debounce(this.onSnap, this.options.debounce)\n\n this.lenis.on('scroll', this.onScroll)\n }\n\n /**\n * Destroy the snap instance\n */\n destroy() {\n this.lenis.off('scroll', this.onScroll)\n window.removeEventListener('resize', this.onWindowResize, false)\n this.elements.forEach((element) => element.destroy())\n }\n\n /**\n * Start the snap after it has been stopped\n */\n start() {\n this.isStopped = false\n }\n\n /**\n * Stop the snap\n */\n stop() {\n this.isStopped = true\n }\n\n /**\n * Add a snap to the snap instance\n *\n * @param value The value to snap to\n * @param userData User data that will be forwarded through the snap event\n * @returns Unsubscribe function\n */\n add(value: number, userData: UserData = {}) {\n const id = uid()\n\n this.snaps.set(id, { value, userData })\n\n return () => this.remove(id)\n }\n\n /**\n * Remove a snap from the snap instance\n *\n * @param id The snap id of the snap to remove\n */\n remove(id: UID) {\n this.snaps.delete(id)\n }\n\n /**\n * Add an element to the snap instance\n *\n * @param element The element to add\n * @param options The options for the element\n * @returns Unsubscribe function\n */\n addElement(element: HTMLElement, options = {} as SnapElementOptions) {\n const id = uid()\n\n this.elements.set(id, new SnapElement(element, options))\n\n return () => this.removeElement(id)\n }\n\n /**\n * Remove an element from the snap instance\n *\n * @param id The snap id of the snap element to remove\n */\n removeElement(id: UID) {\n this.elements.delete(id)\n }\n\n private onWindowResize = () => {\n this.viewport.width = window.innerWidth\n this.viewport.height = window.innerHeight\n }\n\n private onScroll = ({\n // scroll,\n // limit,\n lastVelocity,\n velocity,\n // isScrolling,\n userData,\n }: // isHorizontal,\n Lenis) => {\n if (this.isStopped) return\n\n // return\n const isDecelerating = Math.abs(lastVelocity) > Math.abs(velocity)\n const isTurningBack =\n Math.sign(lastVelocity) !== Math.sign(velocity) && velocity !== 0\n\n if (\n Math.abs(velocity) < this.options.velocityThreshold &&\n // !isTouching &&\n isDecelerating &&\n !isTurningBack &&\n userData?.initiator !== 'snap'\n ) {\n this.onSnapDebounced()\n }\n }\n\n private onSnap = () => {\n let { scroll, isHorizontal } = this.lenis\n scroll = Math.ceil(this.lenis.scroll)\n\n let snaps = [...this.snaps.values()] as SnapItem[]\n\n this.elements.forEach(({ rect, align }) => {\n let value: number | undefined\n\n align.forEach((align) => {\n if (align === 'start') {\n value = rect.top\n } else if (align === 'center') {\n value = isHorizontal\n ? rect.left + rect.width / 2 - this.viewport.width / 2\n : rect.top + rect.height / 2 - this.viewport.height / 2\n } else if (align === 'end') {\n value = isHorizontal\n ? rect.left + rect.width - this.viewport.width\n : rect.top + rect.height - this.viewport.height\n }\n\n if (typeof value === 'number') {\n snaps.push({ value: Math.ceil(value), userData: {} })\n }\n })\n })\n\n snaps = snaps.sort((a, b) => Math.abs(a.value) - Math.abs(b.value))\n\n let prevSnap = snaps.findLast(({ value }) => value <= scroll)\n if (prevSnap === undefined) prevSnap = snaps[0]!\n const distanceToPrevSnap = Math.abs(scroll - prevSnap.value)\n\n let nextSnap = snaps.find(({ value }) => value >= scroll)\n if (nextSnap === undefined) nextSnap = snaps[snaps.length - 1]!\n const distanceToNextSnap = Math.abs(scroll - nextSnap.value)\n\n const snap = distanceToPrevSnap < distanceToNextSnap ? prevSnap : nextSnap\n\n const distance = Math.abs(scroll - snap.value)\n\n if (\n this.options.type === 'mandatory' ||\n (this.options.type === 'proximity' &&\n distance <=\n (isHorizontal\n ? this.lenis.dimensions.width\n : this.lenis.dimensions.height))\n ) {\n // this.__isScrolling = true\n // this.onSnapStart?.(snap)\n\n // console.log('scroll to')\n\n this.lenis.scrollTo(snap.value, {\n lerp: this.options.lerp,\n easing: this.options.easing,\n duration: this.options.duration,\n userData: { initiator: 'snap' },\n onStart: () => {\n this.options.onSnapStart?.(snap)\n },\n onComplete: () => {\n this.options.onSnapComplete?.(snap)\n },\n })\n }\n\n // console.timeEnd('scroll')\n }\n}\n","// This file serves as an entry point for the package\nimport { Snap } from './src/snap'\nglobalThis.Snap = Snap\n"],"mappings":"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","names":["align"]}
|
|
1
|
+
{"version":3,"sources":["../packages/snap/src/debounce.ts","../packages/snap/src/element.ts","../packages/snap/src/uid.ts","../packages/snap/src/snap.ts","../packages/snap/browser.ts"],"sourcesContent":["export function debounce<CB extends (...args: any[]) => void>(\r\n callback: CB,\r\n delay: number\r\n) {\r\n let timer: number | undefined\r\n return function <T>(this: T, ...args: Parameters<typeof callback>) {\r\n let context = this\r\n clearTimeout(timer)\r\n timer = setTimeout(() => {\r\n timer = undefined\r\n callback.apply(context, args)\r\n }, delay)\r\n }\r\n}\r\n","function removeParentSticky(element: HTMLElement) {\r\n const position = getComputedStyle(element).position\r\n\r\n const isSticky = position === 'sticky'\r\n\r\n if (isSticky) {\r\n element.style.setProperty('position', 'static')\r\n element.dataset.sticky = 'true'\r\n }\r\n\r\n if (element.offsetParent) {\r\n removeParentSticky(element.offsetParent as HTMLElement)\r\n }\r\n}\r\n\r\nfunction addParentSticky(element: HTMLElement) {\r\n if (element?.dataset?.sticky === 'true') {\r\n element.style.removeProperty('position')\r\n delete element.dataset.sticky\r\n }\r\n\r\n if (element.offsetParent) {\r\n addParentSticky(element.offsetParent as HTMLElement)\r\n }\r\n}\r\n\r\nfunction offsetTop(element: HTMLElement, accumulator = 0) {\r\n const top = accumulator + element.offsetTop\r\n if (element.offsetParent) {\r\n return offsetTop(element.offsetParent as HTMLElement, top)\r\n }\r\n return top\r\n}\r\n\r\nfunction offsetLeft(element: HTMLElement, accumulator = 0) {\r\n const left = accumulator + element.offsetLeft\r\n if (element.offsetParent) {\r\n return offsetLeft(element.offsetParent as HTMLElement, left)\r\n }\r\n return left\r\n}\r\n\r\nfunction scrollTop(element: HTMLElement, accumulator = 0) {\r\n const top = accumulator + element.scrollTop\r\n if (element.offsetParent) {\r\n return scrollTop(element.offsetParent as HTMLElement, top)\r\n }\r\n return top + window.scrollY\r\n}\r\n\r\nfunction scrollLeft(element: HTMLElement, accumulator = 0) {\r\n const left = accumulator + element.scrollLeft\r\n if (element.offsetParent) {\r\n return scrollLeft(element.offsetParent as HTMLElement, left)\r\n }\r\n return left + window.scrollX\r\n}\r\n\r\nexport type SnapElementOptions = {\r\n align?: string | string[]\r\n ignoreSticky?: boolean\r\n ignoreTransform?: boolean\r\n}\r\n\r\ntype Rect = {\r\n top: number\r\n left: number\r\n width: number\r\n height: number\r\n x: number\r\n y: number\r\n bottom: number\r\n right: number\r\n element: HTMLElement\r\n}\r\n\r\nexport class SnapElement {\r\n element: HTMLElement\r\n options: SnapElementOptions\r\n align: string[]\r\n // @ts-ignore\r\n rect: Rect = {}\r\n wrapperResizeObserver: ResizeObserver\r\n resizeObserver: ResizeObserver\r\n\r\n constructor(\r\n element: HTMLElement,\r\n {\r\n align = ['start'],\r\n ignoreSticky = true,\r\n ignoreTransform = false,\r\n }: SnapElementOptions = {}\r\n ) {\r\n this.element = element\r\n\r\n this.options = { align, ignoreSticky, ignoreTransform }\r\n\r\n // this.ignoreSticky = ignoreSticky\r\n // this.ignoreTransform = ignoreTransform\r\n\r\n this.align = [align].flat()\r\n\r\n // TODO: assing rect immediately\r\n\r\n this.wrapperResizeObserver = new ResizeObserver(this.onWrapperResize)\r\n this.wrapperResizeObserver.observe(document.body)\r\n this.onWrapperResize()\r\n\r\n this.resizeObserver = new ResizeObserver(this.onResize)\r\n this.resizeObserver.observe(this.element)\r\n this.setRect({\r\n width: this.element.offsetWidth,\r\n height: this.element.offsetHeight,\r\n })\r\n }\r\n\r\n destroy() {\r\n this.wrapperResizeObserver.disconnect()\r\n this.resizeObserver.disconnect()\r\n }\r\n\r\n setRect({\r\n top,\r\n left,\r\n width,\r\n height,\r\n element,\r\n }: {\r\n top?: number\r\n left?: number\r\n width?: number\r\n height?: number\r\n element?: HTMLElement\r\n } = {}) {\r\n top = top ?? this.rect.top\r\n left = left ?? this.rect.left\r\n width = width ?? this.rect.width\r\n height = height ?? this.rect.height\r\n element = element ?? this.rect.element\r\n\r\n if (\r\n top === this.rect.top &&\r\n left === this.rect.left &&\r\n width === this.rect.width &&\r\n height === this.rect.height &&\r\n element === this.rect.element\r\n )\r\n return\r\n\r\n this.rect.top = top\r\n this.rect.y = top\r\n this.rect.width = width\r\n this.rect.height = height\r\n this.rect.left = left\r\n this.rect.x = left\r\n this.rect.bottom = top + height\r\n this.rect.right = left + width\r\n }\r\n\r\n onWrapperResize = () => {\r\n let top, left\r\n\r\n if (this.options.ignoreSticky) removeParentSticky(this.element)\r\n if (this.options.ignoreTransform) {\r\n top = offsetTop(this.element)\r\n left = offsetLeft(this.element)\r\n } else {\r\n const rect = this.element.getBoundingClientRect()\r\n top = rect.top + scrollTop(this.element)\r\n left = rect.left + scrollLeft(this.element)\r\n }\r\n if (this.options.ignoreSticky) addParentSticky(this.element)\r\n\r\n this.setRect({ top, left })\r\n }\r\n\r\n onResize = ([entry]: ResizeObserverEntry[]) => {\r\n if (!entry?.borderBoxSize[0]) return\r\n const width = entry.borderBoxSize[0].inlineSize\r\n const height = entry.borderBoxSize[0].blockSize\r\n\r\n this.setRect({ width, height })\r\n }\r\n}\r\n","let index = 0\r\n\r\nexport type UID = number\r\n\r\nexport function uid(): UID {\r\n return index++\r\n}\r\n","import type Lenis from 'lenis'\r\nimport type { UserData } from 'lenis'\r\nimport { debounce } from './debounce'\r\nimport type { SnapElementOptions } from './element'\r\nimport { SnapElement } from './element'\r\nimport type { SnapItem, SnapOptions } from './types'\r\nimport type { UID } from './uid'\r\nimport { uid } from './uid'\r\n\r\n// TODO:\r\n// - horizontal\r\n// - fix trackpad snapping too soon due to velocity (fuck Apple)\r\n// - fix wheel scrolling after limits (see console scroll to)\r\n// - fix touch scroll, do not snap when not released\r\n// - arrow, spacebar\r\n\r\ntype RequiredPick<T, F extends keyof T> = Omit<T, F> & Required<Pick<T, F>>\r\n\r\n/**\r\n * Snap class to handle the snap functionality\r\n *\r\n * @example\r\n * const snap = new Snap(lenis, {\r\n * type: 'mandatory', // 'mandatory', 'proximity'\r\n * lerp: 0.1,\r\n * duration: 1,\r\n * easing: (t) => t,\r\n * onSnapStart: (snap) => {\r\n * console.log('onSnapStart', snap)\r\n * },\r\n * onSnapComplete: (snap) => {\r\n * console.log('onSnapComplete', snap)\r\n * },\r\n * })\r\n *\r\n * snap.add(500) // snap at 500px\r\n *\r\n * const removeSnap = snap.add(500)\r\n *\r\n * if (someCondition) {\r\n * removeSnap()\r\n * }\r\n */\r\nexport class Snap {\r\n options: RequiredPick<SnapOptions, 'type' | 'velocityThreshold' | 'debounce'>\r\n elements = new Map<UID, SnapElement>()\r\n snaps = new Map<UID, SnapItem>()\r\n viewport = {\r\n width: window.innerWidth,\r\n height: window.innerHeight,\r\n }\r\n isStopped = false\r\n onSnapDebounced: () => void\r\n\r\n constructor(\r\n private lenis: Lenis,\r\n {\r\n type = 'mandatory',\r\n lerp,\r\n easing,\r\n duration,\r\n distanceThreshold = '100%',\r\n velocityThreshold = 1.2,\r\n debounce: debounceDelay = 50,\r\n onSnapStart,\r\n onSnapComplete,\r\n }: SnapOptions = {}\r\n ) {\r\n this.options = {\r\n type,\r\n lerp,\r\n easing,\r\n duration,\r\n distanceThreshold,\r\n velocityThreshold,\r\n debounce: debounceDelay,\r\n onSnapStart,\r\n onSnapComplete,\r\n }\r\n\r\n this.onWindowResize()\r\n window.addEventListener('resize', this.onWindowResize, false)\r\n\r\n this.onSnapDebounced = debounce(this.onSnap, this.options.debounce)\r\n\r\n this.lenis.on('scroll', this.onScroll)\r\n }\r\n\r\n /**\r\n * Destroy the snap instance\r\n */\r\n destroy() {\r\n this.lenis.off('scroll', this.onScroll)\r\n window.removeEventListener('resize', this.onWindowResize, false)\r\n this.elements.forEach((element) => element.destroy())\r\n }\r\n\r\n /**\r\n * Start the snap after it has been stopped\r\n */\r\n start() {\r\n this.isStopped = false\r\n }\r\n\r\n /**\r\n * Stop the snap\r\n */\r\n stop() {\r\n this.isStopped = true\r\n }\r\n\r\n /**\r\n * Add a snap to the snap instance\r\n *\r\n * @param value The value to snap to\r\n * @param userData User data that will be forwarded through the snap event\r\n * @returns Unsubscribe function\r\n */\r\n add(value: number, userData: UserData = {}) {\r\n const id = uid()\r\n\r\n this.snaps.set(id, { value, userData })\r\n\r\n return () => this.snaps.delete(id)\r\n }\r\n\r\n /**\r\n * Add an element to the snap instance\r\n *\r\n * @param element The element to add\r\n * @param options The options for the element\r\n * @returns Unsubscribe function\r\n */\r\n addElement(element: HTMLElement, options: SnapElementOptions = {}) {\r\n const id = uid()\r\n\r\n this.elements.set(id, new SnapElement(element, options))\r\n\r\n return () => this.elements.delete(id)\r\n }\r\n\r\n private onWindowResize = () => {\r\n this.viewport.width = window.innerWidth\r\n this.viewport.height = window.innerHeight\r\n }\r\n\r\n private onScroll = ({\r\n // scroll,\r\n // limit,\r\n lastVelocity,\r\n velocity,\r\n // isScrolling,\r\n userData,\r\n }: // isHorizontal,\r\n Lenis) => {\r\n if (this.isStopped) return\r\n\r\n // return\r\n const isDecelerating = Math.abs(lastVelocity) > Math.abs(velocity)\r\n const isTurningBack =\r\n Math.sign(lastVelocity) !== Math.sign(velocity) && velocity !== 0\r\n\r\n if (\r\n Math.abs(velocity) < this.options.velocityThreshold &&\r\n // !isTouching &&\r\n isDecelerating &&\r\n !isTurningBack &&\r\n userData?.initiator !== 'snap'\r\n ) {\r\n this.onSnapDebounced()\r\n }\r\n }\r\n\r\n private onSnap = () => {\r\n let { scroll, isHorizontal } = this.lenis\r\n scroll = Math.ceil(this.lenis.scroll)\r\n\r\n let snaps = [...this.snaps.values()] as SnapItem[]\r\n\r\n this.elements.forEach(({ rect, align }) => {\r\n let value: number | undefined\r\n\r\n align.forEach((align) => {\r\n if (align === 'start') {\r\n value = rect.top\r\n } else if (align === 'center') {\r\n value = isHorizontal\r\n ? rect.left + rect.width / 2 - this.viewport.width / 2\r\n : rect.top + rect.height / 2 - this.viewport.height / 2\r\n } else if (align === 'end') {\r\n value = isHorizontal\r\n ? rect.left + rect.width - this.viewport.width\r\n : rect.top + rect.height - this.viewport.height\r\n }\r\n\r\n if (typeof value === 'number') {\r\n snaps.push({ value: Math.ceil(value), userData: {} })\r\n }\r\n })\r\n })\r\n\r\n snaps = snaps.sort((a, b) => Math.abs(a.value) - Math.abs(b.value))\r\n\r\n let prevSnap = snaps.findLast(({ value }) => value <= scroll)\r\n if (prevSnap === undefined) prevSnap = snaps[0]!\r\n const distanceToPrevSnap = Math.abs(scroll - prevSnap.value)\r\n\r\n let nextSnap = snaps.find(({ value }) => value >= scroll)\r\n if (nextSnap === undefined) nextSnap = snaps[snaps.length - 1]!\r\n const distanceToNextSnap = Math.abs(scroll - nextSnap.value)\r\n\r\n const snap = distanceToPrevSnap < distanceToNextSnap ? prevSnap : nextSnap\r\n\r\n const distance = Math.abs(scroll - snap.value)\r\n\r\n let distanceThreshold\r\n\r\n const axis = isHorizontal ? 'width' : 'height'\r\n\r\n if (\r\n typeof this.options.distanceThreshold === 'string' &&\r\n this.options.distanceThreshold.endsWith('%')\r\n ) {\r\n distanceThreshold =\r\n (Number(this.options.distanceThreshold.replace('%', '')) / 100) *\r\n this.viewport[axis]\r\n } else if (typeof this.options.distanceThreshold === 'number') {\r\n distanceThreshold = this.options.distanceThreshold\r\n } else {\r\n distanceThreshold = this.viewport[axis]\r\n }\r\n\r\n if (\r\n this.options.type === 'mandatory' ||\r\n (this.options.type === 'proximity' && distance <= distanceThreshold)\r\n ) {\r\n // this.__isScrolling = true\r\n // this.onSnapStart?.(snap)\r\n\r\n // console.log('scroll to')\r\n\r\n this.lenis.scrollTo(snap.value, {\r\n lerp: this.options.lerp,\r\n easing: this.options.easing,\r\n duration: this.options.duration,\r\n userData: { initiator: 'snap' },\r\n onStart: () => {\r\n this.options.onSnapStart?.(snap)\r\n },\r\n onComplete: () => {\r\n this.options.onSnapComplete?.(snap)\r\n },\r\n })\r\n }\r\n\r\n // console.timeEnd('scroll')\r\n }\r\n}\r\n","// This file serves as an entry point for the package\r\nimport { Snap } from './src/snap'\r\nglobalThis.Snap = Snap\r\n"],"mappings":"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","names":["align"]}
|