├── .editorconfig ├── .gitignore ├── .npmignore ├── .npmrc ├── README.md ├── build.js ├── eslint.config.js ├── example ├── build.js ├── package-lock.json ├── package.json ├── src │ ├── index.jsx │ └── styles │ │ ├── example.css │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 └── yarn.lock ├── package-lock.json ├── package.json ├── src ├── index.ts ├── modules │ ├── mouse.without.window.ts │ └── scroll-to.ts ├── scroll.tsx ├── styles │ └── index.ts ├── types │ ├── props.ts │ ├── state.ts │ └── styles.ts └── utils │ ├── clear-selection.ts │ ├── events.ts │ ├── generate-id.ts │ ├── generate-style.ts │ ├── get-scroll-width.ts │ └── is.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_size = 2 3 | indent_style = space 4 | max_line_length = 120 5 | tab_width = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | .DS_STORE 4 | static 5 | 6 | .module-cache 7 | *.log 8 | *.map 9 | 10 | *.orig 11 | .DS_Store 12 | Thumbs.db 13 | logs 14 | reports 15 | documentation 16 | webpack.customConfig.js 17 | 18 | dist 19 | lib 20 | coverage 21 | test-reports 22 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | .DS_STORE 4 | static 5 | 6 | .module-cache 7 | *.log 8 | *.map 9 | 10 | *.orig 11 | .DS_Store 12 | Thumbs.db 13 | logs 14 | reports 15 | documentation 16 | webpack.customConfig.js 17 | example 18 | examples 19 | src 20 | coverage 21 | test-reports 22 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save = true 2 | save-exact = true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React CustomScroll 2 | 3 | **React-customscroll** is a tiny React component for scroll bar customization, without dependencies but with a lot of features. 4 | 5 | [Demo](http://natrube.net/custom-scroll/index.html) 6 | 7 | ## Features: 8 | - React 19 support 9 | - TypeScript support 10 | - Tiny size (11kb) 11 | - Without dependencies 12 | - Easy customization, simple api 13 | - Native OS scroll behavior 14 | - Cross browser 15 | - Animate scrollTo feature 16 | - RTL support 17 | - Server Side Rendering support 18 | - Scroll snap support 19 | 20 | ## Usage 21 | 22 | ### Step 1: 23 | 24 | ``` 25 | npm install react-customscroll -save 26 | ``` 27 | 28 | ### Step 2: 29 | 30 | **React-customscroll** works like native browser scroll. 31 | 32 | You should paste the component inside the block with scrollable data. 33 | 34 | For instance: 35 | 36 | ```jsx 37 | import CustomScroll from 'react-customscroll'; 38 | ``` 39 | 40 | ```jsx 41 |
42 | 43 | ...long_data_here... 44 | 45 |
46 | ``` 47 | 48 | If block with a native browser scroll works well it will work with **React-customscroll** 49 | 50 | --- 51 | 52 | [Examples](https://github.com/AlexSergey/react-customscroll/tree/master/example) 53 | 54 | ## License 55 | 56 | MIT 57 | -------------------------------------------------------------------------------- /build.js: -------------------------------------------------------------------------------- 1 | const { libraryCompiler } = require('@rockpack/compiler'); 2 | 3 | libraryCompiler({ 4 | name: 'CustomScroll', 5 | cjs: { 6 | src: './src', 7 | dist: './lib/cjs' 8 | }, 9 | esm: { 10 | src: './src', 11 | dist: './lib/esm' 12 | }, 13 | externals: [ 14 | 'react' 15 | ] 16 | }); 17 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | const {makeConfig} = require('@rockpack/codestyle'); 2 | 3 | const config = makeConfig(); 4 | 5 | config.push({ 6 | rules: { 7 | '@typescript-eslint/no-unsafe-function-type': 'off', 8 | }, 9 | }); 10 | 11 | module.exports = config; 12 | -------------------------------------------------------------------------------- /example/build.js: -------------------------------------------------------------------------------- 1 | const { frontendCompiler } = require('@rockpack/compiler'); 2 | const path = require('path'); 3 | 4 | frontendCompiler({}, finalConfig => { 5 | finalConfig.output.publicPath = '/'; 6 | 7 | Object.assign(finalConfig.resolve, { 8 | alias: { 9 | 'react-dom': path.resolve(__dirname, '../node_modules/react-dom'), 10 | 'prop-types': path.resolve(__dirname, '../node_modules/prop-types'), 11 | react: path.resolve(__dirname, '../node_modules/react') 12 | } 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /example/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-customscroll-example", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "react-customscroll-example", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "react": "19.1.0", 13 | "react-container-dimensions": "1.4.1", 14 | "react-dom": "19.1.0", 15 | "react-measure": "2.5.2", 16 | "react-sortable-tree": "2.8.0", 17 | "react-virtualized": "9.22.6" 18 | } 19 | }, 20 | "node_modules/@babel/runtime": { 21 | "version": "7.12.5", 22 | "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", 23 | "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", 24 | "license": "MIT", 25 | "dependencies": { 26 | "regenerator-runtime": "^0.13.4" 27 | } 28 | }, 29 | "node_modules/@react-dnd/asap": { 30 | "version": "4.0.0", 31 | "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.0.tgz", 32 | "integrity": "sha512-0XhqJSc6pPoNnf8DhdsPHtUhRzZALVzYMTzRwV4VI6DJNJ/5xxfL9OQUwb8IH5/2x7lSf7nAZrnzUD+16VyOVQ==", 33 | "license": "MIT" 34 | }, 35 | "node_modules/@react-dnd/invariant": { 36 | "version": "2.0.0", 37 | "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-2.0.0.tgz", 38 | "integrity": "sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw==", 39 | "license": "MIT" 40 | }, 41 | "node_modules/@react-dnd/shallowequal": { 42 | "version": "2.0.0", 43 | "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz", 44 | "integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==", 45 | "license": "MIT" 46 | }, 47 | "node_modules/@types/hoist-non-react-statics": { 48 | "version": "3.3.1", 49 | "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", 50 | "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", 51 | "license": "MIT", 52 | "dependencies": { 53 | "@types/react": "*", 54 | "hoist-non-react-statics": "^3.3.0" 55 | } 56 | }, 57 | "node_modules/@types/prop-types": { 58 | "version": "15.7.3", 59 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", 60 | "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==", 61 | "license": "MIT" 62 | }, 63 | "node_modules/@types/react": { 64 | "version": "16.9.4", 65 | "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.4.tgz", 66 | "integrity": "sha512-ItGNmJvQ0IvWt8rbk5PLdpdQhvBVxAaXI9hDlx7UMd8Ie1iMIuwMNiKeTfmVN517CdplpyXvA22X4zm4jGGZnw==", 67 | "license": "MIT", 68 | "dependencies": { 69 | "@types/prop-types": "*", 70 | "csstype": "^2.2.0" 71 | } 72 | }, 73 | "node_modules/batch-processor": { 74 | "version": "1.0.0", 75 | "resolved": "https://registry.npmjs.org/batch-processor/-/batch-processor-1.0.0.tgz", 76 | "integrity": "sha1-dclcMrdI4IUNEMKxaPa9vpiRrOg= sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA==", 77 | "license": "MIT" 78 | }, 79 | "node_modules/clsx": { 80 | "version": "1.1.1", 81 | "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", 82 | "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", 83 | "license": "MIT", 84 | "engines": { 85 | "node": ">=6" 86 | } 87 | }, 88 | "node_modules/csstype": { 89 | "version": "2.6.6", 90 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.6.tgz", 91 | "integrity": "sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg==", 92 | "license": "MIT" 93 | }, 94 | "node_modules/dnd-core": { 95 | "version": "11.1.3", 96 | "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-11.1.3.tgz", 97 | "integrity": "sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA==", 98 | "license": "MIT", 99 | "dependencies": { 100 | "@react-dnd/asap": "^4.0.0", 101 | "@react-dnd/invariant": "^2.0.0", 102 | "redux": "^4.0.4" 103 | } 104 | }, 105 | "node_modules/dom-helpers": { 106 | "version": "5.2.0", 107 | "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz", 108 | "integrity": "sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==", 109 | "license": "MIT", 110 | "dependencies": { 111 | "@babel/runtime": "^7.8.7", 112 | "csstype": "^3.0.2" 113 | } 114 | }, 115 | "node_modules/dom-helpers/node_modules/csstype": { 116 | "version": "3.0.5", 117 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz", 118 | "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==", 119 | "license": "MIT" 120 | }, 121 | "node_modules/element-resize-detector": { 122 | "version": "1.1.15", 123 | "resolved": "https://registry.npmjs.org/element-resize-detector/-/element-resize-detector-1.1.15.tgz", 124 | "integrity": "sha512-16/5avDegXlUxytGgaumhjyQoM6hpp5j3+L79sYq5hlXfTNRy5WMMuTVWkZU3egp/CokCmTmvf18P3KeB57Iog==", 125 | "license": "MIT", 126 | "dependencies": { 127 | "batch-processor": "^1.0.0" 128 | } 129 | }, 130 | "node_modules/frontend-collective-react-dnd-scrollzone": { 131 | "version": "1.0.2", 132 | "resolved": "https://registry.npmjs.org/frontend-collective-react-dnd-scrollzone/-/frontend-collective-react-dnd-scrollzone-1.0.2.tgz", 133 | "integrity": "sha512-me/D9PZJq9j/sjEjs/OPmm6V6nbaHbhgeQiwrWu0t35lhwAOKWc+QBzzKKcZQeboYTkgE8UvCD9el+5ANp+g5Q==", 134 | "license": "MIT", 135 | "dependencies": { 136 | "hoist-non-react-statics": "^3.1.0", 137 | "lodash.throttle": "^4.0.1", 138 | "prop-types": "^15.5.9", 139 | "raf": "^3.2.0", 140 | "react": "^16.3.0", 141 | "react-display-name": "^0.2.0", 142 | "react-dom": "^16.3.0" 143 | }, 144 | "peerDependencies": { 145 | "react-dnd": "^7.3.0" 146 | } 147 | }, 148 | "node_modules/frontend-collective-react-dnd-scrollzone/node_modules/react": { 149 | "version": "16.10.1", 150 | "resolved": "https://registry.npmjs.org/react/-/react-16.10.1.tgz", 151 | "integrity": "sha512-2bisHwMhxQ3XQz4LiJJwG3360pY965pTl/MRrZYxIBKVj4fOHoDs5aZAkYXGxDRO1Li+SyjTAilQEbOmtQJHzA==", 152 | "license": "MIT", 153 | "dependencies": { 154 | "loose-envify": "^1.1.0", 155 | "object-assign": "^4.1.1", 156 | "prop-types": "^15.6.2" 157 | }, 158 | "engines": { 159 | "node": ">=0.10.0" 160 | } 161 | }, 162 | "node_modules/frontend-collective-react-dnd-scrollzone/node_modules/react-dom": { 163 | "version": "16.10.1", 164 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.10.1.tgz", 165 | "integrity": "sha512-SmM4ZW0uug0rn95U8uqr52I7UdNf6wdGLeXDmNLfg3y5q5H9eAbdjF5ubQc3bjDyRrvdAB2IKG7X0GzSpnn5Mg==", 166 | "license": "MIT", 167 | "dependencies": { 168 | "loose-envify": "^1.1.0", 169 | "object-assign": "^4.1.1", 170 | "prop-types": "^15.6.2", 171 | "scheduler": "^0.16.1" 172 | }, 173 | "peerDependencies": { 174 | "react": "^16.0.0" 175 | } 176 | }, 177 | "node_modules/frontend-collective-react-dnd-scrollzone/node_modules/scheduler": { 178 | "version": "0.16.1", 179 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.16.1.tgz", 180 | "integrity": "sha512-MIuie7SgsqMYOdCXVFZa8SKoNorJZUWHW8dPgto7uEHn1lX3fg2Gu0TzgK8USj76uxV7vB5eRMnZs/cdEHg+cg==", 181 | "license": "MIT", 182 | "dependencies": { 183 | "loose-envify": "^1.1.0", 184 | "object-assign": "^4.1.1" 185 | } 186 | }, 187 | "node_modules/get-node-dimensions": { 188 | "version": "1.2.1", 189 | "resolved": "https://registry.npmjs.org/get-node-dimensions/-/get-node-dimensions-1.2.1.tgz", 190 | "integrity": "sha512-2MSPMu7S1iOTL+BOa6K1S62hB2zUAYNF/lV0gSVlOaacd087lc6nR1H1r0e3B1CerTo+RceOmi1iJW+vp21xcQ==", 191 | "license": "MIT" 192 | }, 193 | "node_modules/hoist-non-react-statics": { 194 | "version": "3.3.0", 195 | "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", 196 | "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", 197 | "license": "BSD-3-Clause", 198 | "dependencies": { 199 | "react-is": "^16.7.0" 200 | } 201 | }, 202 | "node_modules/invariant": { 203 | "version": "2.2.4", 204 | "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", 205 | "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", 206 | "license": "MIT", 207 | "dependencies": { 208 | "loose-envify": "^1.0.0" 209 | } 210 | }, 211 | "node_modules/js-tokens": { 212 | "version": "4.0.0", 213 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 214 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 215 | "license": "MIT" 216 | }, 217 | "node_modules/lodash.isequal": { 218 | "version": "4.5.0", 219 | "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", 220 | "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA= sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", 221 | "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", 222 | "license": "MIT" 223 | }, 224 | "node_modules/lodash.throttle": { 225 | "version": "4.1.1", 226 | "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", 227 | "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", 228 | "license": "MIT" 229 | }, 230 | "node_modules/loose-envify": { 231 | "version": "1.4.0", 232 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 233 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 234 | "license": "MIT", 235 | "dependencies": { 236 | "js-tokens": "^3.0.0 || ^4.0.0" 237 | }, 238 | "bin": { 239 | "loose-envify": "cli.js" 240 | } 241 | }, 242 | "node_modules/object-assign": { 243 | "version": "4.1.1", 244 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 245 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 246 | "license": "MIT", 247 | "engines": { 248 | "node": ">=0.10.0" 249 | } 250 | }, 251 | "node_modules/performance-now": { 252 | "version": "2.1.0", 253 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 254 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", 255 | "license": "MIT" 256 | }, 257 | "node_modules/prop-types": { 258 | "version": "15.7.2", 259 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", 260 | "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", 261 | "license": "MIT", 262 | "dependencies": { 263 | "loose-envify": "^1.4.0", 264 | "object-assign": "^4.1.1", 265 | "react-is": "^16.8.1" 266 | } 267 | }, 268 | "node_modules/raf": { 269 | "version": "3.4.1", 270 | "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", 271 | "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", 272 | "license": "MIT", 273 | "dependencies": { 274 | "performance-now": "^2.1.0" 275 | } 276 | }, 277 | "node_modules/react": { 278 | "version": "19.1.0", 279 | "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", 280 | "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", 281 | "license": "MIT", 282 | "engines": { 283 | "node": ">=0.10.0" 284 | } 285 | }, 286 | "node_modules/react-container-dimensions": { 287 | "version": "1.4.1", 288 | "resolved": "https://registry.npmjs.org/react-container-dimensions/-/react-container-dimensions-1.4.1.tgz", 289 | "integrity": "sha512-f5WXYpsL0vAuT9hD6bSTqRYOJXFbU9JzjSZfscNT77PbdCUReLv2/aSvYBgkXyALtOBVdS/nwO5RVZgBxxWDnw==", 290 | "license": "MIT", 291 | "dependencies": { 292 | "element-resize-detector": "^1.1.10", 293 | "invariant": "^2.2.2", 294 | "prop-types": "^15.5.8" 295 | }, 296 | "peerDependencies": { 297 | "react": "^0.14.0 || ^15.0.0 || 16.x", 298 | "react-dom": "^0.14.0 || ^15.0.0 || 16.x" 299 | } 300 | }, 301 | "node_modules/react-display-name": { 302 | "version": "0.2.4", 303 | "resolved": "https://registry.npmjs.org/react-display-name/-/react-display-name-0.2.4.tgz", 304 | "integrity": "sha512-zvU6iouW+SWwHTyThwxGICjJYCMZFk/6r/+jmOdC7ntQoPlS/Pqb81MkxaMf2bHTSq9TN3K3zX2/ayMW/jCtyA==", 305 | "license": "MIT" 306 | }, 307 | "node_modules/react-dnd": { 308 | "version": "11.1.3", 309 | "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-11.1.3.tgz", 310 | "integrity": "sha512-8rtzzT8iwHgdSC89VktwhqdKKtfXaAyC4wiqp0SywpHG12TTLvfOoL6xNEIUWXwIEWu+CFfDn4GZJyynCEuHIQ==", 311 | "license": "MIT", 312 | "dependencies": { 313 | "@react-dnd/shallowequal": "^2.0.0", 314 | "@types/hoist-non-react-statics": "^3.3.1", 315 | "dnd-core": "^11.1.3", 316 | "hoist-non-react-statics": "^3.3.0" 317 | }, 318 | "peerDependencies": { 319 | "react": ">= 16.9.0", 320 | "react-dom": ">= 16.9.0" 321 | } 322 | }, 323 | "node_modules/react-dnd-html5-backend": { 324 | "version": "11.1.3", 325 | "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-11.1.3.tgz", 326 | "integrity": "sha512-/1FjNlJbW/ivkUxlxQd7o3trA5DE33QiRZgxent3zKme8DwF4Nbw3OFVhTRFGaYhHFNL1rZt6Rdj1D78BjnNLw==", 327 | "license": "MIT", 328 | "dependencies": { 329 | "dnd-core": "^11.1.3" 330 | } 331 | }, 332 | "node_modules/react-dom": { 333 | "version": "19.1.0", 334 | "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", 335 | "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", 336 | "license": "MIT", 337 | "dependencies": { 338 | "scheduler": "^0.26.0" 339 | }, 340 | "peerDependencies": { 341 | "react": "^19.1.0" 342 | } 343 | }, 344 | "node_modules/react-is": { 345 | "version": "16.10.1", 346 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.10.1.tgz", 347 | "integrity": "sha512-BXUMf9sIOPXXZWqr7+c5SeOKJykyVr2u0UDzEf4LNGc6taGkQe1A9DFD07umCIXz45RLr9oAAwZbAJ0Pkknfaw==", 348 | "license": "MIT" 349 | }, 350 | "node_modules/react-lifecycles-compat": { 351 | "version": "3.0.4", 352 | "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", 353 | "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", 354 | "license": "MIT" 355 | }, 356 | "node_modules/react-measure": { 357 | "version": "2.5.2", 358 | "resolved": "https://registry.npmjs.org/react-measure/-/react-measure-2.5.2.tgz", 359 | "integrity": "sha512-M+rpbTLWJ3FD6FXvYV6YEGvQ5tMayQ3fGrZhRPHrE9bVlBYfDCLuDcgNttYfk8IqfOI03jz6cbpqMRTUclQnaA==", 360 | "license": "MIT", 361 | "dependencies": { 362 | "@babel/runtime": "^7.2.0", 363 | "get-node-dimensions": "^1.2.1", 364 | "prop-types": "^15.6.2", 365 | "resize-observer-polyfill": "^1.5.0" 366 | }, 367 | "peerDependencies": { 368 | "react": ">0.13.0", 369 | "react-dom": ">0.13.0" 370 | } 371 | }, 372 | "node_modules/react-sortable-tree": { 373 | "version": "2.8.0", 374 | "resolved": "https://registry.npmjs.org/react-sortable-tree/-/react-sortable-tree-2.8.0.tgz", 375 | "integrity": "sha512-gTjwxRNt7z0FC76KeNTnGqx1qUSlV3N78mMPRushBpSUXzZYhiFNsWHUIruyPnaAbw4SA7LgpItV7VieAuwDpw==", 376 | "license": "MIT", 377 | "dependencies": { 378 | "frontend-collective-react-dnd-scrollzone": "^1.0.2", 379 | "lodash.isequal": "^4.5.0", 380 | "prop-types": "^15.6.1", 381 | "react-dnd": "^11.1.3", 382 | "react-dnd-html5-backend": "^11.1.3", 383 | "react-lifecycles-compat": "^3.0.4", 384 | "react-virtualized": "^9.21.2" 385 | }, 386 | "peerDependencies": { 387 | "react": "^16.3.0", 388 | "react-dnd": "^7.3.0", 389 | "react-dom": "^16.3.0" 390 | } 391 | }, 392 | "node_modules/react-virtualized": { 393 | "version": "9.22.6", 394 | "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.6.tgz", 395 | "integrity": "sha512-U5j7KuUQt3AaMatlMJ0UJddqSiX+Km0YJxSqbAzIiGw5EmNz0khMyqP2hzgu4+QUtm+QPIrxzUX4raJxmVJnHg==", 396 | "license": "MIT", 397 | "dependencies": { 398 | "@babel/runtime": "^7.7.2", 399 | "clsx": "^1.0.4", 400 | "dom-helpers": "^5.1.3", 401 | "loose-envify": "^1.4.0", 402 | "prop-types": "^15.7.2", 403 | "react-lifecycles-compat": "^3.0.4" 404 | }, 405 | "peerDependencies": { 406 | "react": "^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", 407 | "react-dom": "^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" 408 | } 409 | }, 410 | "node_modules/redux": { 411 | "version": "4.0.5", 412 | "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz", 413 | "integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==", 414 | "license": "MIT", 415 | "dependencies": { 416 | "loose-envify": "^1.4.0", 417 | "symbol-observable": "^1.2.0" 418 | } 419 | }, 420 | "node_modules/regenerator-runtime": { 421 | "version": "0.13.5", 422 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", 423 | "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", 424 | "license": "MIT" 425 | }, 426 | "node_modules/resize-observer-polyfill": { 427 | "version": "1.5.1", 428 | "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", 429 | "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", 430 | "license": "MIT" 431 | }, 432 | "node_modules/scheduler": { 433 | "version": "0.26.0", 434 | "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", 435 | "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", 436 | "license": "MIT" 437 | }, 438 | "node_modules/symbol-observable": { 439 | "version": "1.2.0", 440 | "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", 441 | "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", 442 | "license": "MIT", 443 | "engines": { 444 | "node": ">=0.10.0" 445 | } 446 | } 447 | } 448 | } 449 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-customscroll-example", 3 | "version": "1.0.0", 4 | "description": "This is little component for custom scroll in React", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=development node build", 8 | "build": "cross-env NODE_ENV=production node build" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/AlexSergey/react-customscroll" 13 | }, 14 | "author": "Aleksandrov Sergey (https://github.com/AlexSergey/react-customscroll)", 15 | "license": "MIT", 16 | "dependencies": { 17 | "react": "19.1.0", 18 | "react-dom": "19.1.0", 19 | "react-measure": "2.5.2", 20 | "react-container-dimensions": "1.4.1", 21 | "react-virtualized": "9.22.6" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, createRef, StrictMode } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | import CustomScroll from '../../src'; 4 | import './styles/example.css'; 5 | 6 | class Layout extends Component { 7 | constructor(prop) { 8 | super(prop); 9 | this.sticky = createRef(); 10 | 11 | this.anchors = {}; 12 | 13 | this.state = { 14 | data: [ 15 | 'Click add please! ' 16 | ], 17 | mount: true, 18 | scrollTop: 0 19 | }; 20 | } 21 | 22 | setScrollTo = (e) => { 23 | e.preventDefault(); 24 | const anchorOffset = this.anchors[e.currentTarget.dataset.anc].offsetTop; 25 | 26 | this.scrollWithAnchor.setY(anchorOffset); 27 | } 28 | 29 | add = () => { 30 | this.setState(state => ( 31 | Object.assign(state, { 32 | data: [...state.data, 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad, animi aspernatur cum debitis deleniti deserunt distinctio eius esse expedita facilis harum illo iure iusto molestiae, perferendis quam, quidem quos tenetur.'] 33 | }) 34 | )); 35 | } 36 | 37 | render() { 38 | return ( 39 | 40 |
41 |
42 |

React CustomScroll

43 |

44 | This is a tiny React component for scroll bar customization, without dependencies but with a lot of 45 | features. 46 |

47 |

Features

48 |
    49 |
  • - React 19 support
  • 50 |
  • - TypeScript support
  • 51 |
  • - Extremely small size (11kb)
  • 52 |
  • - Without dependencies
  • 53 |
  • - Easy customization, simple api
  • 54 |
  • - Native OS scroll behavior
  • 55 |
  • - Cross browser
  • 56 |
  • - Animate scrollTo feature
  • 57 |
  • - RTL support
  • 58 |
  • - Server Side Rendering support
  • 59 |
  • - Scroll snap support
  • 60 |
61 |

License MIT

62 |

63 | Github 64 |

65 |
66 |
67 |
68 |

Scroll by anchor:

69 | 75 |
76 | this.scrollWithAnchor = c}> 77 |

this.anchors.anc1 = c}>Anchor 1

78 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 79 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 80 | eos necessitatibus saepe voluptatem?

81 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 82 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 83 | eos necessitatibus saepe voluptatem?

84 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 85 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 86 | eos necessitatibus saepe voluptatem?

87 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 88 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 89 | eos necessitatibus saepe voluptatem?

90 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 91 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 92 | eos necessitatibus saepe voluptatem?

93 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 94 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 95 | eos necessitatibus saepe voluptatem?

96 |

this.anchors.anc2 = c}>Anchor 2

97 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 98 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 99 | eos necessitatibus saepe voluptatem?

100 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 101 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 102 | eos necessitatibus saepe voluptatem?

103 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 104 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 105 | eos necessitatibus saepe voluptatem?

106 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 107 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 108 | eos necessitatibus saepe voluptatem?

109 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 110 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 111 | eos necessitatibus saepe voluptatem?

112 |

this.anchors.anc3 = c}>Anchor 3

113 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 114 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 115 | eos necessitatibus saepe voluptatem?

116 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 117 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 118 | eos necessitatibus saepe voluptatem?

119 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 120 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 121 | eos necessitatibus saepe voluptatem?

122 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 123 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 124 | eos necessitatibus saepe voluptatem?

125 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 126 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 127 | eos necessitatibus saepe voluptatem?

128 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 129 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 130 | eos necessitatibus saepe voluptatem?

131 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 132 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 133 | eos necessitatibus saepe voluptatem?

134 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 135 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 136 | eos necessitatibus saepe voluptatem?

137 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 138 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 139 | eos necessitatibus saepe voluptatem?

140 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 141 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 142 | eos necessitatibus saepe voluptatem?

143 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 144 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 145 | eos necessitatibus saepe voluptatem?

146 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 147 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 148 | eos necessitatibus saepe voluptatem?

149 |

this.anchors.anc4 = c}>Anchor 4

150 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 151 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 152 | eos necessitatibus saepe voluptatem?

153 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 154 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 155 | eos necessitatibus saepe voluptatem?

156 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 157 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 158 | eos necessitatibus saepe voluptatem?

159 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 160 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 161 | eos necessitatibus saepe voluptatem?

162 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 163 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 164 | eos necessitatibus saepe voluptatem?

165 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 166 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 167 | eos necessitatibus saepe voluptatem?

168 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 169 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 170 | eos necessitatibus saepe voluptatem?

171 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 172 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 173 | eos necessitatibus saepe voluptatem?

174 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 175 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 176 | eos necessitatibus saepe voluptatem?

177 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 178 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 179 | eos necessitatibus saepe voluptatem?

180 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 181 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 182 | eos necessitatibus saepe voluptatem?

183 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 184 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 185 | eos necessitatibus saepe voluptatem?

186 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 187 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 188 | eos necessitatibus saepe voluptatem?

189 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 190 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 191 | eos necessitatibus saepe voluptatem?

192 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 193 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 194 | eos necessitatibus saepe voluptatem?

195 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 196 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 197 | eos necessitatibus saepe voluptatem?

198 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 199 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 200 | eos necessitatibus saepe voluptatem?

201 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 202 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 203 | eos necessitatibus saepe voluptatem?

204 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 205 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 206 | eos necessitatibus saepe voluptatem?

207 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 208 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 209 | eos necessitatibus saepe voluptatem?

210 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 211 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 212 | eos necessitatibus saepe voluptatem?

213 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 214 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 215 | eos necessitatibus saepe voluptatem?

216 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 217 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 218 | eos necessitatibus saepe voluptatem?

219 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 220 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 221 | eos necessitatibus saepe voluptatem?

222 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 223 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 224 | eos necessitatibus saepe voluptatem?

225 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 226 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 227 | eos necessitatibus saepe voluptatem?

228 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 229 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 230 | eos necessitatibus saepe voluptatem?

231 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 232 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 233 | eos necessitatibus saepe voluptatem?

234 |

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 235 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 236 | eos necessitatibus saepe voluptatem?

237 |

The end

238 |
239 |
240 |
241 |
242 |

Auto reinit:

243 |
244 | 245 | {this.state.data.map((p, k) =>

{p}

)} 246 |
247 |
248 | 249 |
250 |
251 |
252 |

Textarea scroll:

253 |
254 | 255 |
256 | I look like a textarea 257 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 258 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 259 | eos necessitatibus saepe voluptatem? 260 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 261 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 262 | eos necessitatibus saepe voluptatem? 263 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 264 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 265 | eos necessitatibus saepe voluptatem? 266 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 267 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 268 | eos necessitatibus saepe voluptatem? 269 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 270 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 271 | eos necessitatibus saepe voluptatem? 272 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 273 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 274 | eos necessitatibus saepe voluptatem? 275 |
276 |
277 |
278 |
279 |
280 |

Customization scroll

281 |
282 | 283 |

284 | I look like a textarea 285 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 286 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae eos 287 | necessitatibus saepe voluptatem? 288 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 289 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae eos 290 | necessitatibus saepe voluptatem? 291 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 292 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae eos 293 | necessitatibus saepe voluptatem? 294 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 295 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae eos 296 | necessitatibus saepe voluptatem? 297 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 298 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae eos 299 | necessitatibus saepe voluptatem? 300 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 301 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae eos 302 | necessitatibus saepe voluptatem? 303 |

304 |
305 |
306 |
307 |

Sticky block

308 | 309 | {scroll => { 310 | let offset = scroll; 311 | if (this.sticky.current) { 312 | if (scroll >= this.sticky.current.offsetHeight - 60) { 313 | offset = this.sticky.current.offsetHeight - 60; 314 | } 315 | } 316 | return ( 317 | <> 318 |
327 | I am sticky 328 |
329 |

330 | I look like a textarea 331 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 332 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 333 | eos necessitatibus saepe voluptatem? 334 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 335 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 336 | eos necessitatibus saepe voluptatem? 337 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 338 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 339 | eos necessitatibus saepe voluptatem? 340 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 341 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 342 | eos necessitatibus saepe voluptatem? 343 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 344 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 345 | eos necessitatibus saepe voluptatem? 346 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 347 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae 348 | eos necessitatibus saepe voluptatem? 349 |

350 | 351 | ); 352 | }} 353 |
354 |
355 |
356 |

Unmount scroll

357 | 360 |
361 | {this.state.mount && 362 | 363 |

364 | I look like a textarea 365 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 366 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae eos 367 | necessitatibus saepe voluptatem? 368 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 369 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae eos 370 | necessitatibus saepe voluptatem? 371 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 372 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae eos 373 | necessitatibus saepe voluptatem? 374 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 375 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae eos 376 | necessitatibus saepe voluptatem? 377 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 378 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae eos 379 | necessitatibus saepe voluptatem? 380 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquid dignissimos distinctio, eaque eos 381 | libero maxime, modi natus necessitatibus nesciunt quis reiciendis rem repellat rerum. Aliquam beatae eos 382 | necessitatibus saepe voluptatem? 383 |

384 |
385 | } 386 |
387 |
388 |

RTL example:

389 |
390 |
391 | 392 |

?سوف أعطي مثالا على ذلك. لا تمانع

393 |

?سوف أعطي مثالا على ذلك. لا تمانع

394 |

?سوف أعطي مثالا على ذلك. لا تمانع

395 |

?سوف أعطي مثالا على ذلك. لا تمانع

396 |

?سوف أعطي مثالا على ذلك. لا تمانع

397 |

?سوف أعطي مثالا على ذلك. لا تمانع

398 |

?سوف أعطي مثالا على ذلك. لا تمانع

399 |

?سوف أعطي مثالا على ذلك. لا تمانع

400 |

?سوف أعطي مثالا على ذلك. لا تمانع

401 |

?سوف أعطي مثالا على ذلك. لا تمانع

402 |

?سوف أعطي مثالا على ذلك. لا تمانع

403 |

?سوف أعطي مثالا على ذلك. لا تمانع

404 |

?سوف أعطي مثالا على ذلك. لا تمانع

405 |

?سوف أعطي مثالا على ذلك. لا تمانع

406 |

?سوف أعطي مثالا على ذلك. لا تمانع

407 |
408 |
409 |
410 |
411 |
412 |

Scroll Snap

413 |
414 | 415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 | 424 |
425 |
426 |
427 | 428 | ) 429 | } 430 | } 431 | 432 | const root = createRoot(document.getElementById('root')); 433 | 434 | root.render( 435 | 436 | 437 | 438 | ); 439 | -------------------------------------------------------------------------------- /example/src/styles/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexSergey/react-customscroll/accc1199598603d6a7efebec1320551b886839a7/example/src/styles/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /example/src/styles/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | -------------------------------------------------------------------------------- /example/src/styles/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexSergey/react-customscroll/accc1199598603d6a7efebec1320551b886839a7/example/src/styles/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /example/src/styles/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexSergey/react-customscroll/accc1199598603d6a7efebec1320551b886839a7/example/src/styles/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /example/src/styles/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexSergey/react-customscroll/accc1199598603d6a7efebec1320551b886839a7/example/src/styles/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /example/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@babel/runtime@^7.2.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7": 6 | version "7.12.5" 7 | resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz" 8 | integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== 9 | dependencies: 10 | regenerator-runtime "^0.13.4" 11 | 12 | "@react-dnd/asap@^4.0.0": 13 | version "4.0.0" 14 | resolved "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.0.tgz" 15 | integrity sha512-0XhqJSc6pPoNnf8DhdsPHtUhRzZALVzYMTzRwV4VI6DJNJ/5xxfL9OQUwb8IH5/2x7lSf7nAZrnzUD+16VyOVQ== 16 | 17 | "@react-dnd/invariant@^2.0.0": 18 | version "2.0.0" 19 | resolved "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-2.0.0.tgz" 20 | integrity sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw== 21 | 22 | "@react-dnd/shallowequal@^2.0.0": 23 | version "2.0.0" 24 | resolved "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz" 25 | integrity sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg== 26 | 27 | "@types/hoist-non-react-statics@^3.3.1": 28 | version "3.3.1" 29 | resolved "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz" 30 | integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== 31 | dependencies: 32 | "@types/react" "*" 33 | hoist-non-react-statics "^3.3.0" 34 | 35 | "@types/prop-types@*": 36 | version "15.7.3" 37 | resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz" 38 | integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== 39 | 40 | "@types/react@*": 41 | version "16.9.4" 42 | resolved "https://registry.npmjs.org/@types/react/-/react-16.9.4.tgz" 43 | integrity sha512-ItGNmJvQ0IvWt8rbk5PLdpdQhvBVxAaXI9hDlx7UMd8Ie1iMIuwMNiKeTfmVN517CdplpyXvA22X4zm4jGGZnw== 44 | dependencies: 45 | "@types/prop-types" "*" 46 | csstype "^2.2.0" 47 | 48 | batch-processor@^1.0.0: 49 | version "1.0.0" 50 | resolved "https://registry.npmjs.org/batch-processor/-/batch-processor-1.0.0.tgz" 51 | integrity sha1-dclcMrdI4IUNEMKxaPa9vpiRrOg= sha512-xoLQD8gmmR32MeuBHgH0Tzd5PuSZx71ZsbhVxOCRbgktZEPe4SQy7s9Z50uPp0F/f7iw2XmkHN2xkgbMfckMDA== 52 | 53 | clsx@^1.0.4: 54 | version "1.1.1" 55 | resolved "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz" 56 | integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== 57 | 58 | csstype@^2.2.0: 59 | version "2.6.6" 60 | resolved "https://registry.npmjs.org/csstype/-/csstype-2.6.6.tgz" 61 | integrity sha512-RpFbQGUE74iyPgvr46U9t1xoQBM8T4BL8SxrN66Le2xYAPSaDJJKeztV3awugusb3g3G9iL8StmkBBXhcbbXhg== 62 | 63 | csstype@^3.0.2: 64 | version "3.0.5" 65 | resolved "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz" 66 | integrity sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ== 67 | 68 | dnd-core@^11.1.3: 69 | version "11.1.3" 70 | resolved "https://registry.npmjs.org/dnd-core/-/dnd-core-11.1.3.tgz" 71 | integrity sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA== 72 | dependencies: 73 | "@react-dnd/asap" "^4.0.0" 74 | "@react-dnd/invariant" "^2.0.0" 75 | redux "^4.0.4" 76 | 77 | dom-helpers@^5.1.3: 78 | version "5.2.0" 79 | resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz" 80 | integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ== 81 | dependencies: 82 | "@babel/runtime" "^7.8.7" 83 | csstype "^3.0.2" 84 | 85 | element-resize-detector@^1.1.10: 86 | version "1.1.15" 87 | resolved "https://registry.npmjs.org/element-resize-detector/-/element-resize-detector-1.1.15.tgz" 88 | integrity sha512-16/5avDegXlUxytGgaumhjyQoM6hpp5j3+L79sYq5hlXfTNRy5WMMuTVWkZU3egp/CokCmTmvf18P3KeB57Iog== 89 | dependencies: 90 | batch-processor "^1.0.0" 91 | 92 | frontend-collective-react-dnd-scrollzone@^1.0.2: 93 | version "1.0.2" 94 | resolved "https://registry.npmjs.org/frontend-collective-react-dnd-scrollzone/-/frontend-collective-react-dnd-scrollzone-1.0.2.tgz" 95 | integrity sha512-me/D9PZJq9j/sjEjs/OPmm6V6nbaHbhgeQiwrWu0t35lhwAOKWc+QBzzKKcZQeboYTkgE8UvCD9el+5ANp+g5Q== 96 | dependencies: 97 | hoist-non-react-statics "^3.1.0" 98 | lodash.throttle "^4.0.1" 99 | prop-types "^15.5.9" 100 | raf "^3.2.0" 101 | react "^16.3.0" 102 | react-display-name "^0.2.0" 103 | react-dom "^16.3.0" 104 | 105 | get-node-dimensions@^1.2.1: 106 | version "1.2.1" 107 | resolved "https://registry.npmjs.org/get-node-dimensions/-/get-node-dimensions-1.2.1.tgz" 108 | integrity sha512-2MSPMu7S1iOTL+BOa6K1S62hB2zUAYNF/lV0gSVlOaacd087lc6nR1H1r0e3B1CerTo+RceOmi1iJW+vp21xcQ== 109 | 110 | hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0: 111 | version "3.3.0" 112 | resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz" 113 | integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA== 114 | dependencies: 115 | react-is "^16.7.0" 116 | 117 | invariant@^2.2.2: 118 | version "2.2.4" 119 | resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" 120 | integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== 121 | dependencies: 122 | loose-envify "^1.0.0" 123 | 124 | "js-tokens@^3.0.0 || ^4.0.0": 125 | version "4.0.0" 126 | resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" 127 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 128 | 129 | lodash.isequal@^4.5.0: 130 | version "4.5.0" 131 | resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz" 132 | integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== 133 | 134 | lodash.throttle@^4.0.1: 135 | version "4.1.1" 136 | resolved "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz" 137 | integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ== 138 | 139 | loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: 140 | version "1.4.0" 141 | resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" 142 | integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== 143 | dependencies: 144 | js-tokens "^3.0.0 || ^4.0.0" 145 | 146 | object-assign@^4.1.1: 147 | version "4.1.1" 148 | resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" 149 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== 150 | 151 | performance-now@^2.1.0: 152 | version "2.1.0" 153 | resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" 154 | integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== 155 | 156 | prop-types@^15.5.8, prop-types@^15.5.9, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: 157 | version "15.7.2" 158 | resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz" 159 | integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== 160 | dependencies: 161 | loose-envify "^1.4.0" 162 | object-assign "^4.1.1" 163 | react-is "^16.8.1" 164 | 165 | raf@^3.2.0: 166 | version "3.4.1" 167 | resolved "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz" 168 | integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== 169 | dependencies: 170 | performance-now "^2.1.0" 171 | 172 | react-container-dimensions@1.4.1: 173 | version "1.4.1" 174 | resolved "https://registry.npmjs.org/react-container-dimensions/-/react-container-dimensions-1.4.1.tgz" 175 | integrity sha512-f5WXYpsL0vAuT9hD6bSTqRYOJXFbU9JzjSZfscNT77PbdCUReLv2/aSvYBgkXyALtOBVdS/nwO5RVZgBxxWDnw== 176 | dependencies: 177 | element-resize-detector "^1.1.10" 178 | invariant "^2.2.2" 179 | prop-types "^15.5.8" 180 | 181 | react-display-name@^0.2.0: 182 | version "0.2.4" 183 | resolved "https://registry.npmjs.org/react-display-name/-/react-display-name-0.2.4.tgz" 184 | integrity sha512-zvU6iouW+SWwHTyThwxGICjJYCMZFk/6r/+jmOdC7ntQoPlS/Pqb81MkxaMf2bHTSq9TN3K3zX2/ayMW/jCtyA== 185 | 186 | react-dnd-html5-backend@^11.1.3: 187 | version "11.1.3" 188 | resolved "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-11.1.3.tgz" 189 | integrity sha512-/1FjNlJbW/ivkUxlxQd7o3trA5DE33QiRZgxent3zKme8DwF4Nbw3OFVhTRFGaYhHFNL1rZt6Rdj1D78BjnNLw== 190 | dependencies: 191 | dnd-core "^11.1.3" 192 | 193 | react-dnd@^11.1.3: 194 | version "11.1.3" 195 | resolved "https://registry.npmjs.org/react-dnd/-/react-dnd-11.1.3.tgz" 196 | integrity sha512-8rtzzT8iwHgdSC89VktwhqdKKtfXaAyC4wiqp0SywpHG12TTLvfOoL6xNEIUWXwIEWu+CFfDn4GZJyynCEuHIQ== 197 | dependencies: 198 | "@react-dnd/shallowequal" "^2.0.0" 199 | "@types/hoist-non-react-statics" "^3.3.1" 200 | dnd-core "^11.1.3" 201 | hoist-non-react-statics "^3.3.0" 202 | 203 | react-dom@^16.3.0: 204 | version "16.10.1" 205 | resolved "https://registry.npmjs.org/react-dom/-/react-dom-16.10.1.tgz" 206 | integrity sha512-SmM4ZW0uug0rn95U8uqr52I7UdNf6wdGLeXDmNLfg3y5q5H9eAbdjF5ubQc3bjDyRrvdAB2IKG7X0GzSpnn5Mg== 207 | dependencies: 208 | loose-envify "^1.1.0" 209 | object-assign "^4.1.1" 210 | prop-types "^15.6.2" 211 | scheduler "^0.16.1" 212 | 213 | react-dom@19.1.0: 214 | version "19.1.0" 215 | resolved "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz" 216 | integrity sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g== 217 | dependencies: 218 | scheduler "^0.26.0" 219 | 220 | react-is@^16.7.0, react-is@^16.8.1: 221 | version "16.10.1" 222 | resolved "https://registry.npmjs.org/react-is/-/react-is-16.10.1.tgz" 223 | integrity sha512-BXUMf9sIOPXXZWqr7+c5SeOKJykyVr2u0UDzEf4LNGc6taGkQe1A9DFD07umCIXz45RLr9oAAwZbAJ0Pkknfaw== 224 | 225 | react-lifecycles-compat@^3.0.4: 226 | version "3.0.4" 227 | resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz" 228 | integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== 229 | 230 | react-measure@2.5.2: 231 | version "2.5.2" 232 | resolved "https://registry.npmjs.org/react-measure/-/react-measure-2.5.2.tgz" 233 | integrity sha512-M+rpbTLWJ3FD6FXvYV6YEGvQ5tMayQ3fGrZhRPHrE9bVlBYfDCLuDcgNttYfk8IqfOI03jz6cbpqMRTUclQnaA== 234 | dependencies: 235 | "@babel/runtime" "^7.2.0" 236 | get-node-dimensions "^1.2.1" 237 | prop-types "^15.6.2" 238 | resize-observer-polyfill "^1.5.0" 239 | 240 | react-sortable-tree@2.8.0: 241 | version "2.8.0" 242 | resolved "https://registry.npmjs.org/react-sortable-tree/-/react-sortable-tree-2.8.0.tgz" 243 | integrity sha512-gTjwxRNt7z0FC76KeNTnGqx1qUSlV3N78mMPRushBpSUXzZYhiFNsWHUIruyPnaAbw4SA7LgpItV7VieAuwDpw== 244 | dependencies: 245 | frontend-collective-react-dnd-scrollzone "^1.0.2" 246 | lodash.isequal "^4.5.0" 247 | prop-types "^15.6.1" 248 | react-dnd "^11.1.3" 249 | react-dnd-html5-backend "^11.1.3" 250 | react-lifecycles-compat "^3.0.4" 251 | react-virtualized "^9.21.2" 252 | 253 | react-virtualized@^9.21.2, react-virtualized@9.22.6: 254 | version "9.22.6" 255 | resolved "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.6.tgz" 256 | integrity sha512-U5j7KuUQt3AaMatlMJ0UJddqSiX+Km0YJxSqbAzIiGw5EmNz0khMyqP2hzgu4+QUtm+QPIrxzUX4raJxmVJnHg== 257 | dependencies: 258 | "@babel/runtime" "^7.7.2" 259 | clsx "^1.0.4" 260 | dom-helpers "^5.1.3" 261 | loose-envify "^1.4.0" 262 | prop-types "^15.7.2" 263 | react-lifecycles-compat "^3.0.4" 264 | 265 | react@^16.3.0: 266 | version "16.10.1" 267 | resolved "https://registry.npmjs.org/react/-/react-16.10.1.tgz" 268 | integrity sha512-2bisHwMhxQ3XQz4LiJJwG3360pY965pTl/MRrZYxIBKVj4fOHoDs5aZAkYXGxDRO1Li+SyjTAilQEbOmtQJHzA== 269 | dependencies: 270 | loose-envify "^1.1.0" 271 | object-assign "^4.1.1" 272 | prop-types "^15.6.2" 273 | 274 | react@19.1.0: 275 | version "19.1.0" 276 | resolved "https://registry.npmjs.org/react/-/react-19.1.0.tgz" 277 | integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg== 278 | 279 | redux@^4.0.4: 280 | version "4.0.5" 281 | resolved "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz" 282 | integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w== 283 | dependencies: 284 | loose-envify "^1.4.0" 285 | symbol-observable "^1.2.0" 286 | 287 | regenerator-runtime@^0.13.4: 288 | version "0.13.5" 289 | resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz" 290 | integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== 291 | 292 | resize-observer-polyfill@^1.5.0: 293 | version "1.5.1" 294 | resolved "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz" 295 | integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== 296 | 297 | scheduler@^0.16.1: 298 | version "0.16.1" 299 | resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.16.1.tgz" 300 | integrity sha512-MIuie7SgsqMYOdCXVFZa8SKoNorJZUWHW8dPgto7uEHn1lX3fg2Gu0TzgK8USj76uxV7vB5eRMnZs/cdEHg+cg== 301 | dependencies: 302 | loose-envify "^1.1.0" 303 | object-assign "^4.1.1" 304 | 305 | scheduler@^0.26.0: 306 | version "0.26.0" 307 | resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz" 308 | integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA== 309 | 310 | symbol-observable@^1.2.0: 311 | version "1.2.0" 312 | resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz" 313 | integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== 314 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-customscroll", 3 | "version": "5.3.0", 4 | "description": "This is a tiny React component for scroll bar customization, without dependencies but with a lot of features.", 5 | "main": "dist/index.js", 6 | "types": "dist/types/index.d.ts", 7 | "scripts": { 8 | "start": "cross-env NODE_ENV=development node build", 9 | "build": "cross-env NODE_ENV=production node build", 10 | "typing": "cross-env NODE_ENV=production tsc -p . --noEmit", 11 | "lint": "cross-env NODE_ENV=production eslint \"src/**\"", 12 | "format": "cross-env NODE_ENV=production eslint \"src/**\" --fix", 13 | "production": "npm run typing && npm run lint && npm run build && npm publish" 14 | }, 15 | "husky": { 16 | "hooks": { 17 | "pre-push": "npm run lint && npm run typing" 18 | } 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/AlexSergey/react-customscroll" 23 | }, 24 | "author": "Aleksandrov Sergey (https://github.com/AlexSergey/react-customscroll)", 25 | "license": "MIT", 26 | "homepage": "https://github.com/AlexSergey/react-customscroll", 27 | "bugs": { 28 | "url": "https://github.com/AlexSergey/react-customscroll/issues" 29 | }, 30 | "keywords": [ 31 | "react", 32 | "react-component", 33 | "scroll", 34 | "scrollbar", 35 | "customscroll", 36 | "ui" 37 | ], 38 | "devDependencies": { 39 | "@rockpack/codestyle": "6.0.0", 40 | "@rockpack/compiler": "6.0.0", 41 | "@rockpack/tsconfig": "6.0.0", 42 | "@types/react": "19.1.6", 43 | "@types/react-dom": "19.1.5", 44 | "husky": "9.1.7", 45 | "react": "19.1.0", 46 | "react-dom": "19.1.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { CustomScroll, getDefaultScrollWidth } from "./scroll"; 2 | 3 | export default CustomScroll; 4 | 5 | export { getDefaultScrollWidth }; 6 | -------------------------------------------------------------------------------- /src/modules/mouse.without.window.ts: -------------------------------------------------------------------------------- 1 | import { isClient } from "../utils/is"; 2 | 3 | const fire = (): void => { 4 | const event = document.createEvent("Event"); 5 | event.initEvent("mouseWithoutWindow", true, true); 6 | document.dispatchEvent(event); 7 | }; 8 | 9 | const MouseWithoutWindow = ((): { getInstance: () => void } => { 10 | let instance; 11 | 12 | const createInstance = (): boolean => { 13 | document.addEventListener("mouseup", (e) => { 14 | const w = window; 15 | const d = document; 16 | const el = d.documentElement; 17 | const body = d.getElementsByTagName("body")[0]; 18 | const width = w.innerWidth || el.clientWidth || body.clientWidth; 19 | const height = w.innerHeight || el.clientHeight || body.clientHeight; 20 | 21 | if (e.clientX >= width || e.clientX < 0 || e.clientY >= height || e.clientY < 0) { 22 | fire(); 23 | } 24 | }); 25 | 26 | window.addEventListener("blur", () => { 27 | fire(); 28 | }); 29 | 30 | return true; 31 | }; 32 | 33 | return { 34 | getInstance: (): void => { 35 | if (instance) { 36 | return instance; 37 | } 38 | instance = createInstance(); 39 | }, 40 | }; 41 | })(); 42 | 43 | export const mouseWithoutWindow = (): void => { 44 | if (isClient()) { 45 | MouseWithoutWindow.getInstance(); 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /src/modules/scroll-to.ts: -------------------------------------------------------------------------------- 1 | const _scrollTo = (element: HTMLDivElement, to: number, duration: number): void => { 2 | if (duration <= 0) { 3 | return; 4 | } 5 | const difference = to - element.scrollTop; 6 | const perTick = (difference / duration) * 10; 7 | 8 | setTimeout(() => { 9 | element.scrollTop += perTick; 10 | if (element.scrollTop === to) { 11 | return; 12 | } 13 | _scrollTo(element, to, duration - 10); 14 | }, 10); 15 | }; 16 | 17 | export const scrollTo = (domEl: HTMLDivElement, offsetY: number, animate?: boolean): void => { 18 | if (animate) { 19 | _scrollTo(domEl, offsetY, 500); 20 | } else { 21 | domEl.scrollTop = offsetY; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/scroll.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component, createRef, CSSProperties, ReactNode } from "react"; 2 | 3 | import { mouseWithoutWindow } from "./modules/mouse.without.window"; 4 | import { scrollTo } from "./modules/scroll-to"; 5 | import { stylesFactory } from "./styles"; 6 | import { Props } from "./types/props"; 7 | import { State } from "./types/state"; 8 | import { clearSelection } from "./utils/clear-selection"; 9 | import { off, on } from "./utils/events"; 10 | import { generateId } from "./utils/generate-id"; 11 | import { generateStyle } from "./utils/generate-style"; 12 | import { getScrollWidth } from "./utils/get-scroll-width"; 13 | import { isClient, isDefined, isFunction, isObject } from "./utils/is"; 14 | 15 | mouseWithoutWindow(); 16 | 17 | /** 18 | * This is min height for Scroll Bar. 19 | * If children content will be very big 20 | * Scroll Bar stay 20 pixels 21 | * */ 22 | const minHeightScrollBar = 20; 23 | const defaultScrollWidth = 17; 24 | const REINIT_MS = 250; 25 | const SCROLL_WIDTH = getScrollWidth(); 26 | // If this is Safari / iPhone / iPad or other browser / device with scrollWidth === 0 27 | const isZero = SCROLL_WIDTH === 0; 28 | 29 | const getDefaultScrollWidth = (): number => (typeof SCROLL_WIDTH === "number" ? SCROLL_WIDTH : 0); 30 | 31 | class CustomScroll extends Component { 32 | private customScroll: HTMLDivElement; 33 | 34 | private customScrollFrameRef: { 35 | current: HTMLDivElement; 36 | }; 37 | 38 | private customScrollHolder: HTMLDivElement; 39 | 40 | private customScrollHolderRef: { 41 | current: HTMLDivElement; 42 | }; 43 | 44 | private customScrollRef: { 45 | current: HTMLDivElement; 46 | }; 47 | 48 | private endScroll: () => void; 49 | 50 | private interval; 51 | 52 | private readonly isVirtualized: boolean; 53 | 54 | private nextHolderHeight = 0; 55 | 56 | private nextWrapperHeight = 0; 57 | 58 | private scrollBarRef: { 59 | current: HTMLDivElement; 60 | }; 61 | 62 | private scrollBlock: HTMLDivElement; 63 | 64 | private scrollID: string = generateId(); 65 | 66 | private scrollRun: (e) => void; 67 | 68 | constructor(props) { 69 | super(props); 70 | 71 | [ 72 | "scroll-area", 73 | "scroll-area-holder", 74 | "scrollBar", 75 | "customScroll", 76 | "customScrollHolder", 77 | "customScrollFrame", 78 | ].forEach((r) => { 79 | this[`${r}Ref`] = createRef(); 80 | }); 81 | let scrollWidth = getDefaultScrollWidth(); 82 | 83 | this.isVirtualized = isObject(props.virtualized); 84 | 85 | if (isZero) { 86 | scrollWidth = defaultScrollWidth; 87 | } 88 | 89 | const className = isDefined(props.className) ? props.className : "react-customscroll"; 90 | 91 | this.state = { 92 | animate: props.animate || true, 93 | classes: { 94 | area: `${className}-scrollbar-area`, 95 | "area-holder": `${className}-scrollbar-holder`, 96 | base: className, 97 | frame: `${className}-frame`, 98 | holder: `${className}-holder`, 99 | "scroll-bar": `${className}-scrollbar`, 100 | }, 101 | scrollAreaShow: false, 102 | scrollTop: 0, 103 | selection: true, 104 | styles: { 105 | ctmScroll: {}, 106 | ctmScrollActive: {}, 107 | ctmScrollFrame: {}, 108 | ctmScrollHolder: {}, 109 | noselect: {}, 110 | scrollArea: {}, 111 | scrollAreaFrame: {}, 112 | scrollBar: {}, 113 | }, 114 | virtualState: this.isVirtualized ? this.getScrollBarStyles(props.scrollTo || 0) : null, 115 | width: `calc(100% + ${scrollWidth}px)`, 116 | }; 117 | 118 | if (isClient() && !document.getElementById(this.scrollID)) { 119 | generateStyle( 120 | `#${this.scrollID}::-webkit-scrollbar { opacity: 0 } 121 | #${this.scrollID}::-webkit-scrollbar-track-piece { background-color: transparent }`, 122 | this.scrollID, 123 | ); 124 | } 125 | } 126 | 127 | applyStyles(): void { 128 | const scrollWidth = getDefaultScrollWidth(); 129 | 130 | this.setState((state) => 131 | Object.assign(state, { 132 | styles: stylesFactory( 133 | { 134 | isZero, 135 | originalScrollWidth: scrollWidth, 136 | scrollAreaColor: isDefined(this.props.scrollAreaColor) ? this.props.scrollAreaColor : "#494949", 137 | scrollBarColor: isDefined(this.props.scrollBarColor) ? this.props.scrollBarColor : "#aeaeae", 138 | scrollBarRadius: isDefined(this.props.scrollBarRadius) ? this.props.scrollBarRadius : "6px", 139 | scrollWidth: isDefined(this.props.scrollWidth) ? this.props.scrollWidth : "6px", 140 | virtualized: this.isVirtualized, 141 | }, 142 | !!this.props.rtl, 143 | ), 144 | }), 145 | ); 146 | } 147 | 148 | blockSelection(state): void { 149 | if (!state) { 150 | clearSelection(); 151 | } 152 | this.setState({ selection: !!state }); 153 | } 154 | 155 | override componentDidMount(): void { 156 | /** 157 | * If mouse cursor gone outside window 158 | * Will trigger event 'mouseWithoutWindow' 159 | * And all listeners will remove 160 | * Content in scroll block will be selectable 161 | * */ 162 | on(document, ["mouseWithoutWindow"], this.reset); 163 | on(window, ["resize"], this.restScrollAfterResize); 164 | 165 | this.scrollBlock = this.customScrollHolderRef.current; 166 | this.customScroll = this.customScrollRef.current; 167 | this.customScrollHolder = this.customScrollFrameRef.current; 168 | 169 | this.applyStyles(); 170 | 171 | /** 172 | * Reinitialize scroll bar every 250 ms 173 | * */ 174 | this.interval = setInterval(this.reinit, REINIT_MS); 175 | } 176 | 177 | override componentDidUpdate(prevProps): void { 178 | let offsetY = this.props.scrollTo; 179 | 180 | if (isDefined(offsetY) && !isNaN(offsetY)) { 181 | if (prevProps.scrollTo !== offsetY) { 182 | if (this.isVirtualized) { 183 | offsetY = offsetY || 0; 184 | 185 | setTimeout(() => { 186 | this.setState({ 187 | virtualState: this.getScrollBarStyles(offsetY), 188 | }); 189 | }); 190 | } else { 191 | scrollTo(this.scrollBlock, offsetY, this.state.animate); 192 | } 193 | } 194 | } 195 | } 196 | 197 | override componentWillUnmount(): void { 198 | if (isClient()) { 199 | const el = document.getElementById(this.scrollID); 200 | if (el) { 201 | el.parentNode.removeChild(el); 202 | } 203 | } 204 | clearInterval(this.interval); 205 | this.removeListeners(); 206 | } 207 | 208 | getParams(): { height: number; holderHeight: number; percentDiff: number; wrapperHeight: number } { 209 | let wrapperHeight = 0; 210 | let holderHeight = 0; 211 | let percentDiff = 0; 212 | let height = 0; 213 | 214 | if (!isClient()) { 215 | return { 216 | height, 217 | holderHeight, 218 | percentDiff, 219 | wrapperHeight, 220 | }; 221 | } 222 | 223 | const scrollArea = this["scroll-areaRef"].current; 224 | const paddings = 225 | window && scrollArea 226 | ? parseFloat(window.getComputedStyle(scrollArea, null).getPropertyValue("padding-top")) + 227 | parseFloat(window.getComputedStyle(scrollArea, null).getPropertyValue("padding-bottom")) 228 | : 0; 229 | 230 | if (this.isVirtualized) { 231 | wrapperHeight = this.props.virtualized.height || 0; 232 | holderHeight = this.props.virtualized.scrollHeight || 0; 233 | } else { 234 | wrapperHeight = this.customScroll && this.customScroll.offsetHeight; 235 | holderHeight = this.customScroll && this.customScrollHolder.offsetHeight; 236 | } 237 | if (holderHeight === 0) { 238 | height = 0; 239 | percentDiff = 0; 240 | } else { 241 | percentDiff = (wrapperHeight - paddings) / holderHeight; 242 | height = wrapperHeight * percentDiff; 243 | } 244 | 245 | return { 246 | height, 247 | holderHeight, 248 | percentDiff, 249 | wrapperHeight: Math.ceil(wrapperHeight), 250 | }; 251 | } 252 | 253 | getScrollBarStyles(offsetY = 0): { height: number; top: number } { 254 | const { height, holderHeight, percentDiff } = this.getParams(); 255 | 256 | if (holderHeight === 0 && percentDiff === 0 && height === 0) { 257 | return { 258 | height: 0, 259 | top: 0, 260 | }; 261 | } 262 | 263 | const scrollTop = this.isVirtualized ? offsetY : this.state.scrollTop || this.scrollBlock.scrollTop; 264 | 265 | const newPercentDiff = 266 | height < minHeightScrollBar ? percentDiff - (minHeightScrollBar - height) / holderHeight : percentDiff; 267 | 268 | const scrollBarHeight = height < minHeightScrollBar ? minHeightScrollBar : height; 269 | 270 | return { 271 | height: scrollBarHeight, 272 | top: scrollTop * newPercentDiff, 273 | }; 274 | } 275 | 276 | jump = (e): void => { 277 | const y = e.touches ? e.touches[0].pageY : e.pageY; 278 | let scrollBar = this.scrollBarRef.current as { offsetHeight: number; offsetTop: number }; 279 | let scrollPosition = this.scrollBlock.scrollTop; 280 | const { wrapperHeight } = this.getParams(); 281 | const topOffset = this.scrollBlock.getBoundingClientRect().top; 282 | 283 | if (this.isVirtualized) { 284 | scrollPosition = this.props.scrollTo || 0; 285 | scrollBar = { 286 | offsetHeight: this.state.virtualState.height, 287 | offsetTop: this.state.virtualState.top, 288 | }; 289 | } 290 | if (y < topOffset + scrollBar.offsetTop || y > topOffset + scrollBar.offsetTop + scrollBar.offsetHeight) { 291 | const offset = topOffset + scrollBar.offsetTop <= y ? 1 : -1; 292 | const scrollY = scrollPosition + wrapperHeight * offset; 293 | if (this.isVirtualized) { 294 | if (isFunction(this.props.scrollSync)) { 295 | this.props.scrollSync(scrollY); 296 | } 297 | } else { 298 | scrollTo(this.scrollBlock, scrollY); 299 | } 300 | } 301 | }; 302 | 303 | onClick = (evt): void => { 304 | evt.stopPropagation(); 305 | evt.preventDefault(); 306 | /** 307 | * If we clicked right mouse button we must skip this event 308 | * */ 309 | let isRightMB; 310 | if ("which" in evt) { 311 | isRightMB = evt.which === 3; 312 | } else if ("button" in evt) { 313 | isRightMB = evt.button === 2; 314 | } 315 | if (isRightMB) { 316 | setTimeout(this.reset); 317 | 318 | return; 319 | } 320 | 321 | const elem = this.scrollBlock; 322 | const startPoint = evt.touches ? evt.touches[0].pageY : evt.pageY; 323 | 324 | const scrollTopOffset = this.isVirtualized ? this.props.scrollTo || 0 : elem.scrollTop; 325 | this.blockSelection(false); 326 | 327 | this.scrollRun = (e): void => { 328 | e.stopPropagation(); 329 | e.preventDefault(); 330 | const { holderHeight, wrapperHeight } = this.getParams(); 331 | const diff = holderHeight / wrapperHeight; 332 | const pageY = e.touches ? e.touches[0].pageY : e.pageY; 333 | if (this.isVirtualized) { 334 | let scrollTop = (pageY - startPoint) * diff + scrollTopOffset; 335 | scrollTop = holderHeight - wrapperHeight <= scrollTop ? holderHeight - wrapperHeight : scrollTop; 336 | if (isFunction(this.props.scrollSync)) { 337 | this.props.scrollSync(scrollTop); 338 | } 339 | } else { 340 | scrollTo(elem, (pageY - startPoint) * diff + scrollTopOffset); 341 | } 342 | }; 343 | 344 | this.endScroll = (): void => { 345 | this.reset(); 346 | }; 347 | 348 | on(document, ["mouseup", "touchend"], this.endScroll); 349 | on(document, ["mousemove", "touchmove"], this.scrollRun); 350 | }; 351 | 352 | reinit = (): void => { 353 | const { holderHeight, wrapperHeight } = this.getParams(); 354 | 355 | if (wrapperHeight !== this.nextWrapperHeight || holderHeight !== this.nextHolderHeight) { 356 | if (this.isVirtualized) { 357 | const scrollPosition = this.props.scrollTo || 0; 358 | const virtualState = this.getScrollBarStyles(scrollPosition); 359 | this.setState({ 360 | scrollAreaShow: holderHeight > wrapperHeight, 361 | virtualState, 362 | }); 363 | } else { 364 | this.setState({ 365 | scrollAreaShow: holderHeight > wrapperHeight, 366 | }); 367 | } 368 | } 369 | 370 | this.nextWrapperHeight = wrapperHeight; 371 | this.nextHolderHeight = holderHeight; 372 | }; 373 | 374 | removeListeners(): void { 375 | off(document, ["mouseWithoutWindow"], this.reset); 376 | off(window, ["resize"], this.restScrollAfterResize); 377 | off(document, ["mouseup", "touchend"], this.endScroll); 378 | off(document, ["mousemove", "touchmove"], this.scrollRun); 379 | } 380 | 381 | override render(): ReactNode { 382 | const ctmScroll = !this.state.selection 383 | ? Object.assign({}, this.state.styles.ctmScroll, this.state.styles.noselect) 384 | : this.state.styles.ctmScroll; 385 | const ctmScrollFrame = this.state.scrollAreaShow 386 | ? Object.assign({}, this.state.styles.ctmScrollFrame, this.state.styles.ctmScrollActive) 387 | : this.state.styles.ctmScrollFrame; 388 | 389 | return ( 390 |
396 |
410 |
422 | {isFunction(this.props.children) 423 | ? this.props.children(this.scrollBlock && this.scrollBlock.scrollTop ? this.scrollBlock.scrollTop : 0) 424 | : this.props.children} 425 |
426 | {this.state.scrollAreaShow ? ( 427 |
433 |
438 |
449 |
450 |
451 | ) : null} 452 |
453 |
454 | ); 455 | } 456 | 457 | reset = (): void => { 458 | this.removeListeners(); 459 | this.blockSelection(true); 460 | }; 461 | 462 | restScrollAfterResize = (): void => { 463 | this.nextWrapperHeight = 0; 464 | this.nextHolderHeight = 0; 465 | }; 466 | 467 | scroll = (): void => { 468 | this.setState({ 469 | scrollTop: this.scrollBlock.scrollTop, 470 | }); 471 | }; 472 | 473 | setY(value: number): void { 474 | scrollTo(this.scrollBlock, value, this.state.animate); 475 | } 476 | } 477 | 478 | export { CustomScroll, getDefaultScrollWidth }; 479 | -------------------------------------------------------------------------------- /src/styles/index.ts: -------------------------------------------------------------------------------- 1 | import { Styles } from "../types/styles"; 2 | 3 | interface Props { 4 | isZero: boolean; 5 | originalScrollWidth: number; 6 | scrollAreaColor: string; 7 | scrollBarColor: string; 8 | scrollBarRadius: string; 9 | scrollWidth: string; 10 | virtualized: boolean; 11 | } 12 | 13 | export const stylesFactory = (props: Props, rtl?: boolean): Styles => ({ 14 | ctmScroll: { 15 | height: "100%", 16 | overflow: "hidden", 17 | position: "relative", 18 | }, 19 | ctmScrollActive: Object.assign( 20 | {}, 21 | rtl 22 | ? { 23 | paddingLeft: props.scrollWidth, 24 | } 25 | : { 26 | paddingRight: props.scrollWidth, 27 | }, 28 | ), 29 | ctmScrollFrame: Object.assign( 30 | {}, 31 | props.virtualized ? { height: "100%", width: "100%" } : {}, 32 | props.isZero 33 | ? { 34 | width: `calc(100% - ${props.originalScrollWidth}px)`, 35 | } 36 | : {}, 37 | ), 38 | ctmScrollHolder: { 39 | height: "100%", 40 | overflowY: "scroll", 41 | }, 42 | noselect: { 43 | MozUserSelect: "none", 44 | msUserSelect: "none", 45 | userSelect: "none", 46 | WebkitTouchCallout: "none", 47 | WebkitUserSelect: "none", 48 | }, 49 | scrollArea: Object.assign( 50 | { 51 | background: props.scrollAreaColor, 52 | height: "100%", 53 | padding: "1px", 54 | position: "absolute", 55 | width: props.scrollWidth, 56 | zIndex: "10", 57 | }, 58 | rtl 59 | ? { 60 | left: "0", 61 | top: "0", 62 | } 63 | : { 64 | right: "0", 65 | top: "0", 66 | }, 67 | ), 68 | scrollAreaFrame: { 69 | height: "100%", 70 | position: "relative", 71 | }, 72 | scrollBar: Object.assign({ 73 | background: props.scrollBarColor, 74 | borderRadius: props.scrollBarRadius, 75 | cursor: "pointer", 76 | position: "absolute", 77 | top: "0", 78 | width: "100%", 79 | }), 80 | }); 81 | -------------------------------------------------------------------------------- /src/types/props.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | export interface Props { 4 | children: (offset: number) => ReactNode; 5 | rtl?: boolean; 6 | scrollAreaColor?: string; 7 | scrollBarColor?: string; 8 | scrollBarRadius?: string; 9 | scrollSync?: (offset: number) => unknown; 10 | scrollTo?: number; 11 | scrollWidth?: string; 12 | virtualized?: { 13 | height: number; 14 | scrollHeight: number; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /src/types/state.ts: -------------------------------------------------------------------------------- 1 | import { Styles } from "./styles"; 2 | 3 | export interface State { 4 | animate: boolean; 5 | classes: { 6 | area: string; 7 | "area-holder": string; 8 | base: string; 9 | frame: string; 10 | holder: string; 11 | "scroll-bar": string; 12 | }; 13 | scrollAreaShow: boolean; 14 | scrollTop: number; 15 | selection: boolean; 16 | styles: Styles; 17 | virtualState: null | { height: number; top: number }; 18 | width: string; 19 | } 20 | -------------------------------------------------------------------------------- /src/types/styles.ts: -------------------------------------------------------------------------------- 1 | export interface Styles { 2 | ctmScroll: Record; 3 | ctmScrollActive: Record; 4 | ctmScrollFrame: Record; 5 | ctmScrollHolder: Record; 6 | noselect: Record; 7 | scrollArea: Record; 8 | scrollAreaFrame: Record; 9 | scrollBar: Record; 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/clear-selection.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Clear selection range on content in the scroll block 3 | * */ 4 | import { isClient } from "./is"; 5 | 6 | export const clearSelection = (): void => { 7 | if (isClient()) { 8 | (window.getSelection() as { removeAllRanges: () => void }).removeAllRanges(); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/utils/events.ts: -------------------------------------------------------------------------------- 1 | export const on = (el: Document | HTMLElement | Window, events: string[], cb: Function): void => { 2 | events.forEach((event) => el.addEventListener(event, cb as EventListener)); 3 | }; 4 | 5 | export const off = (el: Document | HTMLElement | Window, events: string[], cb: Function): void => { 6 | events.forEach((event) => el.removeEventListener(event, cb as EventListener)); 7 | }; 8 | -------------------------------------------------------------------------------- /src/utils/generate-id.ts: -------------------------------------------------------------------------------- 1 | export const generateId = (): string => `_${Math.random().toString(36).substr(2, 9)}`; 2 | -------------------------------------------------------------------------------- /src/utils/generate-style.ts: -------------------------------------------------------------------------------- 1 | export const generateStyle = (css: string, id: string): void => { 2 | const head = document.head || document.getElementsByTagName("head")[0]; 3 | const style = document.createElement("style"); 4 | 5 | style.setAttribute("id", id); 6 | 7 | style.appendChild(document.createTextNode(css)); 8 | 9 | head.appendChild(style); 10 | }; 11 | -------------------------------------------------------------------------------- /src/utils/get-scroll-width.ts: -------------------------------------------------------------------------------- 1 | import { isClient } from "./is"; 2 | 3 | export const getScrollWidth = (): number => { 4 | if (!isClient()) { 5 | return 0; 6 | } 7 | const inner = document.createElement("p"); 8 | inner.style.width = "100%"; 9 | inner.style.height = "200px"; 10 | 11 | const outer = document.createElement("div"); 12 | outer.style.position = "absolute"; 13 | outer.style.top = "0px"; 14 | outer.style.left = "0px"; 15 | outer.style.visibility = "hidden"; 16 | outer.style.width = "200px"; 17 | outer.style.height = "150px"; 18 | outer.style.overflow = "hidden"; 19 | outer.appendChild(inner); 20 | 21 | document.body.appendChild(outer); 22 | const w1 = inner.offsetWidth; 23 | outer.style.overflow = "scroll"; 24 | let w2 = inner.offsetWidth; 25 | 26 | if (w1 === w2) { 27 | w2 = outer.clientWidth; 28 | } 29 | 30 | document.body.removeChild(outer); 31 | 32 | return w1 - w2; 33 | }; 34 | -------------------------------------------------------------------------------- /src/utils/is.ts: -------------------------------------------------------------------------------- 1 | const isFunction = (fn: unknown): fn is Function => typeof fn === "function"; 2 | 3 | const isDefined = (value: unknown): boolean => typeof value !== "undefined"; 4 | 5 | const isClient = (): boolean => typeof window !== "undefined"; 6 | 7 | const isObject = (value: unknown): boolean => typeof value === "object"; 8 | 9 | export { isClient, isDefined, isFunction, isObject }; 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@rockpack/tsconfig", 3 | "compilerOptions": { 4 | "strict": false, 5 | "exactOptionalPropertyTypes": false, 6 | "strictNullChecks": false 7 | }, 8 | "exclude": [ 9 | "./node_modules/**/*" 10 | ], 11 | "include": [ 12 | "./src/**/*" 13 | ] 14 | } 15 | --------------------------------------------------------------------------------