├── .editorconfig ├── .env.sample ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── .release-it.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── commitlint.config.js ├── husky.sh ├── lib ├── @types │ ├── custom.d.ts │ └── locomotive-scroll.d.ts ├── LocomotiveScroll.context.tsx ├── index.tsx └── useLocomotiveScroll.hook.tsx ├── package.json ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = false 9 | insert_final_newline = false 10 | 11 | [*.{php,conf}] 12 | indent_style = space 13 | indent_size = 4 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = false 17 | insert_final_newline = false 18 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | HUSKY_SKIP_HOOKS=1 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules/* 2 | **/dist/* -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": ["@typescript-eslint"], 4 | "extends": [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "prettier", 8 | "prettier/@typescript-eslint" 9 | ], 10 | "env": { 11 | "es6": true, 12 | "jest": true, 13 | "node": true 14 | }, 15 | "rules": { 16 | "@typescript-eslint/triple-slash-reference": 0, 17 | "@typescript-eslint/explicit-function-return-type": 0, 18 | "@typescript-eslint/explicit-module-boundary-types": 0, 19 | "@typescript-eslint/explicit-member-accessibility": 0, 20 | "@typescript-eslint/indent": 0, 21 | "@typescript-eslint/member-delimiter-style": 0, 22 | "@typescript-eslint/no-non-null-assertion": 0, 23 | "@typescript-eslint/no-explicit-any": 1, 24 | "@typescript-eslint/no-var-requires": 1, 25 | "@typescript-eslint/no-use-before-define": 0, 26 | "@typescript-eslint/ban-ts-comment": 0, 27 | "@typescript-eslint/no-empty-function": [ 28 | 2, 29 | { 30 | "allow": ["arrowFunctions"] 31 | } 32 | ], 33 | "@typescript-eslint/no-unused-vars": [ 34 | 2, 35 | { 36 | "argsIgnorePattern": "^_" 37 | } 38 | ], 39 | "no-console": [ 40 | 2, 41 | { 42 | "allow": ["warn", "error"] 43 | } 44 | ] 45 | }, 46 | "overrides": [{ 47 | "files": ["*.js"], 48 | "rules": { 49 | "@typescript-eslint/no-var-requires": "off" 50 | } 51 | }] 52 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | .DS_Store 107 | 108 | module/ 109 | .husky 110 | .DS_Store -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/node_modules/* 2 | **/module/* -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "typescript", 3 | "printWidth": 100, 4 | "tabWidth": 2, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "arrowParens": "avoid" 11 | } 12 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "requireBranch": "main", 4 | "requireCommits": true, 5 | "requireCleanWorkingDir": false, 6 | "addFiles": ["package.json", "CHANGELOG.md"] 7 | }, 8 | "npm": { 9 | "publish": true 10 | }, 11 | "github": { 12 | "release": true 13 | }, 14 | "plugins": { 15 | "@release-it/conventional-changelog": { 16 | "preset": "angular", 17 | "infile": "CHANGELOG.md" 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## [0.2.2](https://github.com/toinelin/react-locomotive-scroll/compare/0.2.1...0.2.2) (2022-07-22) 4 | 5 | 6 | ### Bug Fixes 7 | 8 | * pre-release command ([a12b653](https://github.com/toinelin/react-locomotive-scroll/commit/a12b653b2f24b6a01f81ef4b25038c25cc6ba6d7)) 9 | 10 | ## [0.2.1](https://github.com/toinelin/react-locomotive-scroll/compare/0.2.0...0.2.1) (2022-07-22) 11 | 12 | # [0.2.0](https://github.com/toinelin/react-locomotive-scroll/compare/0.1.8...0.2.0) (2021-09-13) 13 | 14 | 15 | ### Bug Fixes 16 | 17 | * **update-react-version:** update React version from 17.0.1 to ^17.0.2 ([bd77ddd](https://github.com/toinelin/react-locomotive-scroll/commit/bd77ddd3540563f5f548019014314566fda3a671)), closes [#14](https://github.com/toinelin/react-locomotive-scroll/issues/14) 18 | 19 | 20 | ### Features 21 | 22 | * **provider:** add onUpdate, location and onLocationChange props ([83ffcfd](https://github.com/toinelin/react-locomotive-scroll/commit/83ffcfd529edaeac27b03fbb6825bcf29d5a64a2)), closes [#9](https://github.com/toinelin/react-locomotive-scroll/issues/9) 23 | 24 | 25 | ### Reverts 26 | 27 | * **package-verison:** removes package version bump ([773087f](https://github.com/toinelin/react-locomotive-scroll/commit/773087ff872d7df2d10107355f870b88aa59e880)) 28 | 29 | ## [0.1.8](https://github.com/toinelin/react-locomotive-scroll/compare/0.1.7...0.1.8) (2021-04-30) 30 | 31 | ## [0.1.7](https://github.com/toinelin/react-locomotive-scroll/compare/0.1.6...0.1.7) (2021-02-19) 32 | 33 | 34 | ### Reverts 35 | 36 | * **build:** add tslib to paths + re-use target es2015 ([4eeb853](https://github.com/toinelin/react-locomotive-scroll/commit/4eeb853efabed59fe923aaba710bca124b4633b9)) 37 | 38 | ## [0.1.6](https://github.com/toinelin/react-locomotive-scroll/compare/0.1.5...0.1.6) (2021-02-19) 39 | 40 | 41 | ### Reverts 42 | 43 | * **tsconfig:** revert module to commonjs ([f2f83f6](https://github.com/toinelin/react-locomotive-scroll/commit/f2f83f60b5622804d862e4522114da2bf96c1de4)) 44 | 45 | ## [0.1.5](https://github.com/toinelin/react-locomotive-scroll/compare/0.1.4...0.1.5) (2021-02-19) 46 | 47 | ## [0.1.4](https://github.com/toinelin/react-locomotive-scroll/compare/0.1.3...0.1.4) (2021-02-19) 48 | 49 | ## [0.1.3](https://github.com/toinelin/react-locomotive-scroll/compare/0.1.2...0.1.3) (2021-02-19) 50 | 51 | ## [0.1.2](https://github.com/toinelin/react-locomotive-scroll/compare/0.1.1...0.1.2) (2021-02-09) 52 | 53 | # 0.1.1 (2021-02-09) 54 | 55 | 56 | ### Reverts 57 | 58 | * **use-resize-observer:** finaly force the use of observer to avoid weird behaviours ([3ac0e7e](https://github.com/toinelin/react-locomotive-scroll/commit/3ac0e7eee2d28a0613fa958fdba80f254d8f9c30)) 59 | 60 | # 0.1.0 (2021-02-09) 61 | 62 | 63 | ### Bug Fixes 64 | 65 | * **deps:** move use-resize-observer to devDeps ([0826448](https://github.com/toinelin/react-locomotive-scroll/commit/0826448c608b1fb96ea701d3f470ed3b5cc048b7)) 66 | 67 | 68 | ### Features 69 | 70 | * **context + hook:** add context, hook and release deps ([d01e09a](https://github.com/toinelin/react-locomotive-scroll/commit/d01e09a4b03a02a8165e788a6d70cf9e3da5f4f5)) 71 | 72 | 73 | ### Reverts 74 | 75 | * **use-resize-observer:** finaly force the use of observer to avoid weird behaviours ([3ac0e7e](https://github.com/toinelin/react-locomotive-scroll/commit/3ac0e7eee2d28a0613fa958fdba80f254d8f9c30)) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Antoine LIN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | 5 | Logo 6 | Logo 7 | 8 | 9 |

React Locomotive Scroll

10 | 11 |

12 | A locomotive-scroll React wrapper 13 |
14 | Explore Locomotive Scroll docs » 15 |
16 |
17 | 19 | Report Bug 20 | · 21 | Request Feature 22 |

23 |

24 | 25 | 26 | 27 | 28 |

Table of Contents

29 |
    30 |
  1. 31 | Getting Started 32 | 36 |
  2. 37 |
  3. Usage
  4. 38 |
  5. Specific cases
  6. 39 |
  7. Contributing
  8. 40 |
  9. License
  10. 41 |
  11. Contact
  12. 42 |
  13. Acknowledgements
  14. 43 |
44 | 45 | ## Getting Started 46 | 47 | To get a local copy up and running follow these simple steps. 48 | 49 | ### Installation 50 | 51 | ```sh 52 | $ npm install locomotive-scroll react-locomotive-scroll 53 | ``` 54 | 55 | or using [Yarn](https://yarnpkg.com/) 56 | 57 | ```sh 58 | $ yarn add locomotive-scroll react-locomotive-scroll 59 | ``` 60 | 61 | 62 | 63 | 64 | ## Usage 65 | 66 | ### 1. Import the provider 67 | ```js 68 | import { LocomotiveScrollProvider } from 'react-locomotive-scroll' 69 | ``` 70 | 71 | ### 2. Wrap your application using the provider 72 | ```js 73 | const containerRef = useRef(null) 74 | 75 | 91 |
92 | {/* ...your app */} 93 |
94 |
95 | ``` 96 | 97 | ### 3. Wrap your pages using `data-scroll-section` to prevent weird behaviours 98 | 99 | ```js 100 | export function Page() { 101 | return ( 102 |
103 | {/* ...your page */} 104 |
105 | ) 106 | } 107 | ``` 108 | 109 | From the Locomotive Scroll doc : `Defines a scrollable section. Splitting your page into sections may improve performance.` 110 | You may want to use `data-scroll-section` on each page which may be wrapped by `LocomotiveScrollProvider` 111 | 112 | ### 4. Add the base styles to your CSS file. 113 | 114 | [`locomotive-scroll.css`](https://github.com/locomotivemtl/locomotive-scroll/blob/master/dist/locomotive-scroll.css) 115 | 116 | ### 5. Get the scroll instance through all your components 117 | ```js 118 | import { useLocomotiveScroll } from 'react-locomotive-scroll' 119 | 120 | export function Component() { 121 | const { scroll } = useLocomotiveScroll() 122 | 123 | // ... your component 124 | } 125 | ``` 126 | 127 | At this time you should be able to do whatever your want using the scroll object. 128 | 129 | For more examples and to use Locomotive Scroll, please refer to their [Documentation](https://github.com/locomotivemtl/locomotive-scroll) 130 | 131 | ## Specific cases 132 | 133 | ### 1. Apply code to the location update only 134 | 135 | If you want to write some code applied only when the location change but not when the rest of your dependencies added to the `watch` list change, here the thing: 136 | 137 | First, remove the location props from the `watch` dependencies list and add it to the `location` props. 138 | 139 | > `react-locomotive-scroll` will update the scroll instance as it should, but in a different `useEffect` than the one used to update watched dependencies 140 | 141 | ```js 142 | const { pathname } = useLocation() // With react-router 143 | const { asPath } = useRouter() // With next/router 144 | 145 | scroll.scrollTo(0, { duration: 0, disableLerp: true })} // If you want to reset the scroll position to 0 for example 160 | onUpdate={() => console.log('Updated, but not on location change!')} // Will trigger on 161 | > 162 |
163 | {/* ...your app */} 164 |
165 |
166 | ``` 167 | 168 | 169 | ## Troubleshooting 170 | 171 | - [Flickering on Google Chrome v94](https://github.com/locomotivemtl/locomotive-scroll/issues/353) 172 | 173 | 174 | ## Contributing 175 | 176 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. 177 | 178 | 1. Fork the Project 179 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 180 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 181 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 182 | 5. Open a Pull Request 183 | 184 | 185 | 186 | ## License 187 | 188 | Distributed under the MIT License. See `LICENSE` for more information. 189 | 190 | 191 | 192 | 193 | ## Contact 194 | 195 | Antoine Lin - [@vahilloff](https://twitter.com/vahilloff) - contact@antoinelin.com 196 | 197 | Project Link: [https://github.com/toinelin/react-locomotive-scroll](https://github.com/toinelin/react-locomotive-scroll) 198 | 199 | 200 | 201 | 202 | ## Acknowledgements 203 | 204 | * []() 205 | * []() 206 | * []() 207 | 208 | _Please feel free to open a pull request to add your project to the list!_ 209 | 210 | 211 | 212 | 213 | 214 | 215 | [contributors-shield]: https://img.shields.io/github/contributors/toinelin/repo.svg?style=for-the-badge 216 | [contributors-url]: https://github.com/toinelin/repo/graphs/contributors 217 | [forks-shield]: https://img.shields.io/github/forks/toinelin/repo.svg?style=for-the-badge 218 | [forks-url]: https://github.com/toinelin/repo/network/members 219 | [stars-shield]: https://img.shields.io/github/stars/toinelin/repo.svg?style=for-the-badge 220 | [stars-url]: https://github.com/toinelin/repo/stargazers 221 | [issues-shield]: https://img.shields.io/github/issues/toinelin/repo.svg?style=for-the-badge 222 | [issues-url]: https://github.com/toinelin/repo/issues 223 | [license-shield]: https://img.shields.io/github/license/toinelin/repo.svg?style=for-the-badge 224 | [license-url]: https://github.com/toinelin/repo/blob/master/LICENSE.txt 225 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 226 | [linkedin-url]: https://linkedin.com/in/toinelin 227 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | } 4 | -------------------------------------------------------------------------------- /husky.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | npx husky add .husky/pre-commit "yarn pre-commit" 3 | npx husky add .husky/prepare-commit-msg "yarn prepare-commit-msg" -------------------------------------------------------------------------------- /lib/@types/custom.d.ts: -------------------------------------------------------------------------------- 1 | type WithChildren> = T & { children?: React.ReactNode } 2 | -------------------------------------------------------------------------------- /lib/@types/locomotive-scroll.d.ts: -------------------------------------------------------------------------------- 1 | // Credits: https://github.com/locomotivemtl/locomotive-scroll/pull/38/commits/8a112a4ec21fa7262372123262e15a82ddf053b7 2 | 3 | declare module 'locomotive-scroll' { 4 | export function getParents(elem: Element): Element[] 5 | export function queryClosestParent(elem: Element, selector: string): Element | null 6 | export function transform(el: Element, transformValue: string): void 7 | export function getTranslate(el: Element): Vector2 8 | export function lerp(start: number, end: number, amt: number) 9 | 10 | export type Vector2 = { 11 | x: number 12 | y: number 13 | } 14 | 15 | export type LocomotiveElementAttributes = { 16 | el: HTMLElement 17 | class: string 18 | top: number 19 | bottom: number 20 | offset: number 21 | repeat: boolean 22 | inView: boolean 23 | call: string 24 | } 25 | 26 | export type ScrollInstance = { 27 | scroll: Vector2 28 | limit: number 29 | } 30 | 31 | export class Core implements LocomotiveScrollOptions { 32 | el?: Element 33 | elMobile?: Element 34 | name?: string 35 | offset?: number 36 | repeat?: boolean 37 | smooth?: boolean 38 | smoothMobile?: boolean 39 | direction?: string 40 | inertia?: number 41 | class?: string 42 | scrollbarClass?: string 43 | scrollingClass?: string 44 | draggingClass?: string 45 | smoothClass?: string 46 | initClass?: string 47 | getSpeed?: boolean 48 | getDirection?: boolean 49 | 50 | namespace: string 51 | html: Element 52 | windowHeight: number 53 | windowMiddle: number 54 | els: Element[] 55 | hasScrollTicking: boolean 56 | hasCallEventSet: boolean 57 | instance: ScrollInstance 58 | 59 | constructor(options?: LocomotiveScrollOptions) 60 | 61 | init(): void 62 | checkScroll(): void 63 | checkResize(): void 64 | initEvents(): void 65 | setScrollTo(event: Event): void 66 | addElements(): void 67 | detectElements(hasCallEventSet: boolean): void 68 | setInView(current: LocomotiveElementAttributes, i: number): void 69 | setOutOfView(current: LocomotiveElementAttributes, i: number): void 70 | dispatchCall(current: LocomotiveElementAttributes, way: string): void 71 | dispatchScroll(): void 72 | setEvents(event: string, func: string | string[]): void 73 | startScroll(): void 74 | stopScroll(): void 75 | setScroll(x: number, y: number): void 76 | destroy(): void 77 | } 78 | 79 | export class Native extends Core { 80 | constructor(options?: LocomotiveScrollOptions) 81 | 82 | init(): void 83 | checkScroll(): void 84 | checkResize(): void 85 | addElements(): void 86 | updateElements(): void 87 | scrollTo(targetOption: string | Event, offsetOption: number): void 88 | update(): void 89 | destroy(): void 90 | } 91 | 92 | export class Smooth extends Core { 93 | isScrolling: boolean 94 | isDraggingScrollbar: boolean 95 | isTicking: boolean 96 | parallaxElements: Element[] 97 | inertiaRatio: number 98 | stop: boolean 99 | 100 | constructor(options?: LocomotiveScrollOptions) 101 | 102 | init(): void 103 | setScrollLimit(): void 104 | startScrolling(): void 105 | stopScrolling(): void 106 | checkKey(e: KeyboardEvent): void 107 | checkScroll(): void 108 | checkResize(): void 109 | updateDelta(e: WheelEvent): void 110 | updateScroll(e: Event): void 111 | addDirection(): void 112 | addSpeed(): void 113 | initScrollBar(): void 114 | reinitScrollBar(): void 115 | destroyScrollBar(): void 116 | getScrollBar(e: Event): void 117 | releaseScrollBar(e: Event): void 118 | moveScrollBar(e: MouseEvent): void 119 | addElements(): void 120 | addSections(): void 121 | transform(element: Element, x: number, y: number, delay: number): void 122 | transformElement(isForced: boolean): void 123 | scrollTo(targetOption: string | Event, offsetOption: number): void 124 | update(): void 125 | startScroll(): void 126 | stopScroll(): void 127 | setScroll(x: number, y: number): void 128 | destroy(): void 129 | } 130 | 131 | export interface LocomotiveScrollOptions { 132 | el?: Element 133 | elMobile?: Element 134 | name?: string 135 | offset?: number 136 | repeat?: boolean 137 | smooth?: boolean 138 | smoothMobile?: boolean 139 | direction?: string 140 | inertia?: number 141 | class?: string 142 | scrollbarClass?: string 143 | scrollingClass?: string 144 | draggingClass?: string 145 | smoothClass?: string 146 | initClass?: string 147 | getSpeed?: boolean 148 | getDirection?: boolean 149 | } 150 | 151 | export default class LocomotiveScroll implements LocomotiveScrollOptions { 152 | el?: Element 153 | elMobile?: Element 154 | name?: string 155 | offset?: number 156 | repeat?: boolean 157 | smooth?: boolean 158 | smoothMobile?: boolean 159 | direction?: string 160 | inertia?: number 161 | class?: string 162 | scrollbarClass?: string 163 | scrollingClass?: string 164 | draggingClass?: string 165 | smoothClass?: string 166 | initClass?: string 167 | getSpeed?: boolean 168 | getDirection?: boolean 169 | 170 | isMobile: boolean 171 | options: LocomotiveScrollOptions 172 | scroll: Smooth | Native 173 | 174 | constructor(options?: LocomotiveScrollOptions) 175 | 176 | init(): void 177 | update(): void 178 | start(): void 179 | stop(): void 180 | scrollTo( 181 | target: Node | string | number, 182 | options: { 183 | offset?: number 184 | callback?: () => void 185 | duration?: number 186 | easing?: [number, number, number, number] 187 | disableLerp?: boolean 188 | } 189 | ): void 190 | setScroll(x: number, y: number): void 191 | on(event: 'call' | 'scroll', func: (data: string | string[]) => void): void 192 | destroy(): void 193 | } 194 | 195 | export { LocomotiveScroll as Scroll } 196 | } 197 | -------------------------------------------------------------------------------- /lib/LocomotiveScroll.context.tsx: -------------------------------------------------------------------------------- 1 | import { LocomotiveScrollOptions, Scroll } from 'locomotive-scroll' 2 | import { createContext, DependencyList, MutableRefObject, useEffect, useRef, useState } from 'react' 3 | import { useDebounce } from 'use-debounce' 4 | import useResizeObserver from 'use-resize-observer' 5 | 6 | export interface LocomotiveScrollContextValue { 7 | scroll: Scroll | null 8 | isReady: boolean 9 | } 10 | 11 | export const LocomotiveScrollContext = createContext({ 12 | scroll: null, 13 | isReady: false, 14 | }) 15 | 16 | export interface LocomotiveScrollProviderProps { 17 | options: LocomotiveScrollOptions 18 | containerRef: MutableRefObject 19 | watch: DependencyList | undefined 20 | onUpdate?: (scroll: Scroll) => void 21 | location?: string 22 | onLocationChange?: (scroll: Scroll) => void 23 | } 24 | 25 | export function LocomotiveScrollProvider({ 26 | children, 27 | options, 28 | containerRef, 29 | watch, 30 | onUpdate, 31 | location, 32 | onLocationChange, 33 | }: WithChildren) { 34 | const { height: containerHeight } = useResizeObserver({ ref: containerRef }) 35 | const [isReady, setIsReady] = useState(false) 36 | const LocomotiveScrollRef = useRef(null) 37 | const [height] = useDebounce(containerHeight, 100) 38 | 39 | if (!watch) { 40 | console.warn( 41 | 'react-locomotive-scroll: you did not add any props to watch. Scroll may have weird behaviours if the instance is not updated when the route changes' 42 | ) 43 | } 44 | 45 | useEffect(() => { 46 | ;(async () => { 47 | try { 48 | const LocomotiveScroll = (await import('locomotive-scroll')).default 49 | 50 | const dataScrollContainer = document.querySelector('[data-scroll-container]') 51 | 52 | if (!dataScrollContainer) { 53 | console.warn( 54 | `react-locomotive-scroll: [data-scroll-container] dataset was not found. You likely forgot to add it which will prevent Locomotive Scroll to work.` 55 | ) 56 | } 57 | 58 | LocomotiveScrollRef.current = new LocomotiveScroll({ 59 | el: dataScrollContainer ?? undefined, 60 | ...options, 61 | }) 62 | 63 | setIsReady(true) // Re-render the context 64 | } catch (error) { 65 | throw Error(`react-locomotive-scroll: ${error}`) 66 | } 67 | })() 68 | 69 | return () => { 70 | LocomotiveScrollRef.current?.destroy() 71 | setIsReady(false) 72 | } 73 | }, []) 74 | 75 | useEffect( 76 | () => { 77 | if (!LocomotiveScrollRef.current) { 78 | return 79 | } 80 | 81 | LocomotiveScrollRef.current.update() 82 | 83 | if (onUpdate) { 84 | onUpdate(LocomotiveScrollRef.current) 85 | } 86 | }, 87 | watch ? [...watch, height] : [height] 88 | ) 89 | 90 | useEffect(() => { 91 | if (!LocomotiveScrollRef.current || !location) { 92 | return 93 | } 94 | 95 | LocomotiveScrollRef.current.update() 96 | 97 | if (onLocationChange) { 98 | onLocationChange(LocomotiveScrollRef.current) 99 | } 100 | }, [location]) 101 | 102 | return ( 103 | 104 | {children} 105 | 106 | ) 107 | } 108 | 109 | LocomotiveScrollContext.displayName = 'LocomotiveScrollContext' 110 | LocomotiveScrollProvider.displayName = 'LocomotiveScrollProvider' 111 | -------------------------------------------------------------------------------- /lib/index.tsx: -------------------------------------------------------------------------------- 1 | export type { LocomotiveScrollOptions, Scroll, ScrollInstance } from 'locomotive-scroll' 2 | export { LocomotiveScrollContext, LocomotiveScrollProvider } from './LocomotiveScroll.context' 3 | export type { 4 | LocomotiveScrollContextValue, 5 | LocomotiveScrollProviderProps, 6 | } from './LocomotiveScroll.context' 7 | export { useLocomotiveScroll } from './useLocomotiveScroll.hook' 8 | -------------------------------------------------------------------------------- /lib/useLocomotiveScroll.hook.tsx: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect } from 'react' 2 | import { LocomotiveScrollContext, LocomotiveScrollContextValue } from './LocomotiveScroll.context' 3 | 4 | export function useLocomotiveScroll(): LocomotiveScrollContextValue { 5 | const context = useContext(LocomotiveScrollContext) 6 | 7 | if (context === undefined) { 8 | console.warn( 9 | 'react-locomotive-scroll: the context is missing. You may be using the hook without registering LocomotiveScrollProvider, or you may be using the hook in a component which is not wrapped by LocomotiveScrollProvider.' 10 | ) 11 | } 12 | 13 | return context 14 | } 15 | 16 | useLocomotiveScroll.displayName = 'useLocomotiveScroll' 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-locomotive-scroll", 3 | "version": "0.2.2", 4 | "description": "A locomotive-scroll React wrapper", 5 | "main": "module/index.js", 6 | "module": "module/index.js", 7 | "typings": "module/index.d.ts", 8 | "repository": "git@github.com:toinelin/react-locomotive-scroll.git", 9 | "author": "Antoine ", 10 | "license": "MIT", 11 | "keywords": [ 12 | "react", 13 | "locomotive-scroll", 14 | "smooth-scroll", 15 | "typescript" 16 | ], 17 | "files": [ 18 | "lib", 19 | "module" 20 | ], 21 | "scripts": { 22 | "build": "rm -rf ./module && tsc", 23 | "lint": "eslint --quiet", 24 | "format": "prettier --debug-check lib/**/*.{ts,tsx}", 25 | "prepare:husky": "husky install && sh ./husky.sh", 26 | "prepare": "yarn prepare:husky && yarn lint && yarn format && yarn build", 27 | "pre-commit": "yarn lint && yarn format && lint-staged", 28 | "prepare-commit-msg": "exec < /dev/tty && git cz --hook || true", 29 | "release": "dotenv release-it --", 30 | "pre-release": "dotenv release-it major --preRelease=beta --" 31 | }, 32 | "config": { 33 | "commitizen": { 34 | "path": "cz-conventional-changelog" 35 | } 36 | }, 37 | "lint-staged": { 38 | "lib/**/*.{ts,tsx,js,jsx,md}": [ 39 | "eslint --quiet", 40 | "prettier --write" 41 | ] 42 | }, 43 | "dependencies": { 44 | "use-debounce": "6.0.1", 45 | "use-resize-observer": "6.1.0" 46 | }, 47 | "devDependencies": { 48 | "@release-it/conventional-changelog": "5.0.0", 49 | "@types/react": "17.0.1", 50 | "@typescript-eslint/eslint-plugin": "4.14.2", 51 | "@typescript-eslint/parser": "4.14.2", 52 | "commitizen": "4.2.3", 53 | "dotenv-cli": "6.0.0", 54 | "eslint": "7.19.0", 55 | "eslint-config-prettier": "7.2.0", 56 | "husky": "8.0.1", 57 | "lint-staged": "13.0.3", 58 | "prettier": "2.2.1", 59 | "prettier-plugin-organize-imports": "1.1.1", 60 | "release-it": "15.1.3", 61 | "typescript": "4.7.4" 62 | }, 63 | "peerDependencies": { 64 | "locomotive-scroll": "^4.1.4", 65 | "react": "^18.0.2" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "compilerOptions": { 4 | "outDir": "./module", 5 | "sourceMap": false, 6 | "declaration": true, 7 | "strict": true, 8 | "noUnusedLocals": false, 9 | "skipLibCheck": true, 10 | "allowJs": true, 11 | "allowSyntheticDefaultImports": true, 12 | "removeComments": false, 13 | "esModuleInterop": true, 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "module": "CommonJS", 17 | "moduleResolution": "Node", 18 | "target": "ES2015", 19 | "importHelpers": true, 20 | "jsx": "react-jsx", 21 | "lib": [ 22 | "DOM", 23 | "ESNext" 24 | ], 25 | "typeRoots" : [ 26 | "./node_modules/@types", 27 | "./lib/@types" 28 | ], 29 | "baseUrl": ".", 30 | "paths": { 31 | "*": ["*"], 32 | "tslib": ["node_modules/tslib"] 33 | } 34 | }, 35 | "include": [ 36 | "./lib/**/*" 37 | , "@types" ], 38 | "exclude": [ 39 | "./module", 40 | "node_modules" 41 | ] 42 | } --------------------------------------------------------------------------------