scroll-to-future 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENCE +5 -0
- package/README.md +970 -0
- package/dist/index.css +78 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +63 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.js +1796 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1770 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,970 @@
|
|
|
1
|
+
# Scroll To Future
|
|
2
|
+
|
|
3
|
+
A customizable React scrollbar component with vertical and horizontal scrolling, draggable thumbs, automatic size calculation, mobile fallback, native scrollbar hiding, and built-in themes.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
* Vertical and horizontal scrollbars
|
|
8
|
+
* Automatic thumb sizing
|
|
9
|
+
* Drag-to-scroll support
|
|
10
|
+
* Clickable scrollbar track
|
|
11
|
+
* Support for regular elements and page scrolling
|
|
12
|
+
* Automatic target detection
|
|
13
|
+
* Automatic content and container observation
|
|
14
|
+
* Native scrollbar hiding
|
|
15
|
+
* Native scrolling fallback on mobile devices
|
|
16
|
+
* Overlay and reserved-space positioning
|
|
17
|
+
* Built-in theme presets
|
|
18
|
+
* Fully customizable scrollbar and thumb styles
|
|
19
|
+
* TypeScript support
|
|
20
|
+
* React and Next.js compatible
|
|
21
|
+
|
|
22
|
+
# 20+ Themes in box
|
|
23
|
+
* "primary"
|
|
24
|
+
* "midnight"
|
|
25
|
+
* "neonCyan"
|
|
26
|
+
* "ocean"
|
|
27
|
+
* "deepSea"
|
|
28
|
+
* "forest"
|
|
29
|
+
* "moss"
|
|
30
|
+
* "lava"
|
|
31
|
+
* "ember"
|
|
32
|
+
* "gold"
|
|
33
|
+
* "roseQuartz"
|
|
34
|
+
* "violet"
|
|
35
|
+
* "royal"
|
|
36
|
+
* "arctic"
|
|
37
|
+
* "glass"
|
|
38
|
+
* "graphite"
|
|
39
|
+
* "terminal"
|
|
40
|
+
* "toxic"
|
|
41
|
+
* "candy"
|
|
42
|
+
* "sand"
|
|
43
|
+
* "monoLight"
|
|
44
|
+
* "monoDark";
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install scroll-to-future
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
yarn add scroll-to-future
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pnpm add scroll-to-future
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
## Basic usage
|
|
62
|
+
|
|
63
|
+
### Improt styles
|
|
64
|
+
```bash
|
|
65
|
+
import 'scroll-to-future/style.css';
|
|
66
|
+
|
|
67
|
+
The scroll container must have a constrained size and an `overflow` value such as `auto` or `scroll`.
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { ScrollToFuture } from "scroll-to-future";
|
|
71
|
+
|
|
72
|
+
export const Example = () => {
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
style={{
|
|
76
|
+
position: "relative",
|
|
77
|
+
width: "400px",
|
|
78
|
+
height: "300px",
|
|
79
|
+
overflow: "auto",
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<ScrollToFuture />
|
|
83
|
+
|
|
84
|
+
<div style={{ minHeight: "1000px" }}>
|
|
85
|
+
Scrollable content
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
When `target` is not provided, `ScrollToFuture` automatically uses its parent element as the scroll target.
|
|
93
|
+
|
|
94
|
+
For automatic target detection, place the component directly inside the scrollable container.
|
|
95
|
+
|
|
96
|
+
## Usage with a target ref
|
|
97
|
+
|
|
98
|
+
Use the `target` property when the scrollbar is rendered outside the scrollable element or when explicit target control is required.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { useRef } from "react";
|
|
102
|
+
import { ScrollToFuture } from "scroll-to-future";
|
|
103
|
+
|
|
104
|
+
export const Example = () => {
|
|
105
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div style={{ position: "relative" }}>
|
|
109
|
+
<div
|
|
110
|
+
ref={scrollRef}
|
|
111
|
+
style={{
|
|
112
|
+
width: "400px",
|
|
113
|
+
height: "300px",
|
|
114
|
+
overflow: "auto",
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
<div style={{ minHeight: "1000px" }}>
|
|
118
|
+
Scrollable content
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<ScrollToFuture target={scrollRef} />
|
|
123
|
+
</div>
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Vertical scrollbar
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
<ScrollToFuture
|
|
132
|
+
scrollBar={{
|
|
133
|
+
mode: "vertical",
|
|
134
|
+
}}
|
|
135
|
+
/>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Horizontal scrollbar
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
<div
|
|
142
|
+
style={{
|
|
143
|
+
position: "relative",
|
|
144
|
+
width: "600px",
|
|
145
|
+
overflowX: "auto",
|
|
146
|
+
overflowY: "hidden",
|
|
147
|
+
}}
|
|
148
|
+
>
|
|
149
|
+
<ScrollToFuture
|
|
150
|
+
scrollBar={{
|
|
151
|
+
mode: "horizontal",
|
|
152
|
+
}}
|
|
153
|
+
/>
|
|
154
|
+
|
|
155
|
+
<div style={{ width: "1600px", whiteSpace: "nowrap" }}>
|
|
156
|
+
Horizontally scrollable content
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Vertical and horizontal scrollbars
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
<ScrollToFuture
|
|
165
|
+
scrollBar={{
|
|
166
|
+
mode: "both",
|
|
167
|
+
}}
|
|
168
|
+
/>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Configuration
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
import type { ScrollToFutureConfig } from "scroll-to-future";
|
|
175
|
+
import "scroll-to-future/styles.css";
|
|
176
|
+
|
|
177
|
+
const config: ScrollToFutureConfig = {
|
|
178
|
+
scrollBar: {
|
|
179
|
+
mode: "both",
|
|
180
|
+
positionMode: "after",
|
|
181
|
+
superimposition: "after",
|
|
182
|
+
boundaryOffset: "4px",
|
|
183
|
+
widthTrack: "8px",
|
|
184
|
+
heightTrack: "98%",
|
|
185
|
+
hideNativeScrollbar: "always",
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
thumb: {
|
|
189
|
+
boundaryOffset: "1px 1px",
|
|
190
|
+
heightTrack: "auto",
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
nativeOnMobile: true,
|
|
194
|
+
selectTheme: "primary",
|
|
195
|
+
optionsTheme: {},
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
<ScrollToFuture {...config} />
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Component properties
|
|
204
|
+
|
|
205
|
+
| Property | Type | Default | Description |
|
|
206
|
+
| ---------------- | -------------------------------------- | -----------------------: | ------------------------------------------------- |
|
|
207
|
+
| `target` | `React.RefObject<HTMLElement \| null>` | Parent element | Element whose scrolling is controlled |
|
|
208
|
+
| `scrollBar` | `ScrollToFutureScrollBar` | Default scrollbar config | Track and positioning configuration |
|
|
209
|
+
| `thumb` | `ScrollToFutureThumb` | Default thumb config | Thumb size and offset configuration |
|
|
210
|
+
| `selectTheme` | `PresetsThemeType` | `"primary"` | Built-in theme name |
|
|
211
|
+
| `optionsTheme` | `ScrollToFutureThemeProps` | `{}` | Custom theme overrides |
|
|
212
|
+
| `nativeOnMobile` | `boolean` | `true` | Use native scrolling on mobile-only input devices |
|
|
213
|
+
|
|
214
|
+
## Scrollbar configuration
|
|
215
|
+
|
|
216
|
+
### `mode`
|
|
217
|
+
|
|
218
|
+
Controls which scrollbars are rendered.
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
type ScrollBarMode = "horizontal" | "vertical" | "both";
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
<ScrollToFuture
|
|
226
|
+
scrollBar={{
|
|
227
|
+
mode: "vertical",
|
|
228
|
+
}}
|
|
229
|
+
/>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Default:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
"both"
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### `positionMode`
|
|
239
|
+
|
|
240
|
+
Controls the side on which the custom scrollbar is rendered.
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
type PositionMode = "before" | "after";
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
For a vertical scrollbar:
|
|
247
|
+
|
|
248
|
+
* `"before"` places the track on the left.
|
|
249
|
+
* `"after"` places the track on the right.
|
|
250
|
+
|
|
251
|
+
For a horizontal scrollbar:
|
|
252
|
+
|
|
253
|
+
* `"before"` places the track at the top.
|
|
254
|
+
* `"after"` places the track at the bottom.
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
<ScrollToFuture
|
|
258
|
+
scrollBar={{
|
|
259
|
+
positionMode: "before",
|
|
260
|
+
}}
|
|
261
|
+
/>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Default:
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
"after"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### `superimposition`
|
|
271
|
+
|
|
272
|
+
Controls whether the scrollbar overlaps the content or reserves space for itself.
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
type Superimposition = "over" | "after";
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
#### Overlay mode
|
|
279
|
+
|
|
280
|
+
The scrollbar is placed over the content.
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
<ScrollToFuture
|
|
284
|
+
scrollBar={{
|
|
285
|
+
superimposition: "over",
|
|
286
|
+
}}
|
|
287
|
+
/>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
#### Reserved-space mode
|
|
291
|
+
|
|
292
|
+
The component adds padding to the target element so the scrollbar does not cover its content.
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
<ScrollToFuture
|
|
296
|
+
scrollBar={{
|
|
297
|
+
superimposition: "after",
|
|
298
|
+
}}
|
|
299
|
+
/>
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Default:
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
"after"
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### `boundaryOffset`
|
|
309
|
+
|
|
310
|
+
Sets the outer offset of the track.
|
|
311
|
+
|
|
312
|
+
Accepted values:
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
type BoundaryOffset =
|
|
316
|
+
| `${number}px`
|
|
317
|
+
| `${number}px ${number}px`;
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
A single value applies the same offset to both sides:
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
<ScrollToFuture
|
|
324
|
+
scrollBar={{
|
|
325
|
+
boundaryOffset: "6px",
|
|
326
|
+
}}
|
|
327
|
+
/>
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Two values define the start and end offsets:
|
|
331
|
+
|
|
332
|
+
```tsx
|
|
333
|
+
<ScrollToFuture
|
|
334
|
+
scrollBar={{
|
|
335
|
+
boundaryOffset: "4px 8px",
|
|
336
|
+
}}
|
|
337
|
+
/>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Default:
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
"4px"
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### `widthTrack`
|
|
347
|
+
|
|
348
|
+
Sets the scrollbar track thickness.
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
<ScrollToFuture
|
|
352
|
+
scrollBar={{
|
|
353
|
+
widthTrack: "10px",
|
|
354
|
+
}}
|
|
355
|
+
/>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Accepted value:
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
`${number}px`
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
The effective default thickness is `8px`.
|
|
365
|
+
|
|
366
|
+
### `heightTrack`
|
|
367
|
+
|
|
368
|
+
Sets the length of the scrollbar track.
|
|
369
|
+
|
|
370
|
+
Accepted values:
|
|
371
|
+
|
|
372
|
+
```ts
|
|
373
|
+
type HeightTrackType =
|
|
374
|
+
| `${number}px`
|
|
375
|
+
| `${number}%`
|
|
376
|
+
| `${number}vh`
|
|
377
|
+
| `${number}dvh`
|
|
378
|
+
| `${number}dsvh`;
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Examples:
|
|
382
|
+
|
|
383
|
+
```tsx
|
|
384
|
+
<ScrollToFuture
|
|
385
|
+
scrollBar={{
|
|
386
|
+
heightTrack: "80%",
|
|
387
|
+
}}
|
|
388
|
+
/>
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
```tsx
|
|
392
|
+
<ScrollToFuture
|
|
393
|
+
scrollBar={{
|
|
394
|
+
heightTrack: "240px",
|
|
395
|
+
}}
|
|
396
|
+
/>
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
```tsx
|
|
400
|
+
<ScrollToFuture
|
|
401
|
+
scrollBar={{
|
|
402
|
+
heightTrack: "80dvh",
|
|
403
|
+
}}
|
|
404
|
+
/>
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Default:
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
"98%"
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### `hideNativeScrollbar`
|
|
414
|
+
|
|
415
|
+
Controls when the browser's native scrollbar is hidden.
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
type HideNativeScrollbarMode =
|
|
419
|
+
| false
|
|
420
|
+
| "fine-pointer"
|
|
421
|
+
| "always";
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
#### Do not hide the native scrollbar
|
|
425
|
+
|
|
426
|
+
```tsx
|
|
427
|
+
<ScrollToFuture
|
|
428
|
+
scrollBar={{
|
|
429
|
+
hideNativeScrollbar: false,
|
|
430
|
+
}}
|
|
431
|
+
/>
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
#### Hide only when a fine pointer is available
|
|
435
|
+
|
|
436
|
+
This normally applies to devices with a mouse, trackpad, stylus, or another precise pointer.
|
|
437
|
+
|
|
438
|
+
```tsx
|
|
439
|
+
<ScrollToFuture
|
|
440
|
+
scrollBar={{
|
|
441
|
+
hideNativeScrollbar: "fine-pointer",
|
|
442
|
+
}}
|
|
443
|
+
/>
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
#### Always hide the native scrollbar
|
|
447
|
+
|
|
448
|
+
```tsx
|
|
449
|
+
<ScrollToFuture
|
|
450
|
+
scrollBar={{
|
|
451
|
+
hideNativeScrollbar: "always",
|
|
452
|
+
}}
|
|
453
|
+
/>
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Default:
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
"always"
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
The native scrollbar is hidden only when the custom component covers every currently scrollable axis.
|
|
463
|
+
|
|
464
|
+
For example, when the target can scroll both horizontally and vertically but `mode` is set to `"vertical"`, the native scrollbar is preserved because the horizontal axis is not covered by the custom scrollbar.
|
|
465
|
+
|
|
466
|
+
## Thumb configuration
|
|
467
|
+
|
|
468
|
+
### `boundaryOffset`
|
|
469
|
+
|
|
470
|
+
Controls the space between the thumb and the track boundaries.
|
|
471
|
+
|
|
472
|
+
```tsx
|
|
473
|
+
<ScrollToFuture
|
|
474
|
+
thumb={{
|
|
475
|
+
boundaryOffset: "2px",
|
|
476
|
+
}}
|
|
477
|
+
/>
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
```tsx
|
|
481
|
+
<ScrollToFuture
|
|
482
|
+
thumb={{
|
|
483
|
+
boundaryOffset: "2px 4px",
|
|
484
|
+
}}
|
|
485
|
+
/>
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
Default:
|
|
489
|
+
|
|
490
|
+
```ts
|
|
491
|
+
"1px 1px"
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### `heightTrack`
|
|
495
|
+
|
|
496
|
+
Controls the thumb size.
|
|
497
|
+
|
|
498
|
+
```ts
|
|
499
|
+
type ThumbHeight =
|
|
500
|
+
| "auto"
|
|
501
|
+
| `${number}px`
|
|
502
|
+
| `${number}%`;
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
#### Automatic size
|
|
506
|
+
|
|
507
|
+
The thumb size is calculated from the ratio between the visible area and the full scrollable content.
|
|
508
|
+
|
|
509
|
+
```tsx
|
|
510
|
+
<ScrollToFuture
|
|
511
|
+
thumb={{
|
|
512
|
+
heightTrack: "auto",
|
|
513
|
+
}}
|
|
514
|
+
/>
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
#### Fixed size
|
|
518
|
+
|
|
519
|
+
```tsx
|
|
520
|
+
<ScrollToFuture
|
|
521
|
+
thumb={{
|
|
522
|
+
heightTrack: "48px",
|
|
523
|
+
}}
|
|
524
|
+
/>
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
#### Percentage size
|
|
528
|
+
|
|
529
|
+
```tsx
|
|
530
|
+
<ScrollToFuture
|
|
531
|
+
thumb={{
|
|
532
|
+
heightTrack: "20%",
|
|
533
|
+
}}
|
|
534
|
+
/>
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
Default:
|
|
538
|
+
|
|
539
|
+
```ts
|
|
540
|
+
"auto"
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
The calculated thumb size is constrained to prevent it from becoming too small or occupying the entire track.
|
|
544
|
+
|
|
545
|
+
## Mobile behavior
|
|
546
|
+
|
|
547
|
+
By default, mobile-only input devices use the browser's native scrolling behavior.
|
|
548
|
+
|
|
549
|
+
```tsx
|
|
550
|
+
<ScrollToFuture nativeOnMobile />
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
This is equivalent to:
|
|
554
|
+
|
|
555
|
+
```tsx
|
|
556
|
+
<ScrollToFuture nativeOnMobile={true} />
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
On a device whose primary input is coarse and which has no fine pointer:
|
|
560
|
+
|
|
561
|
+
* The custom scrollbar is not rendered.
|
|
562
|
+
* The browser's native scrollbar is not hidden.
|
|
563
|
+
* Native touch scrolling remains available.
|
|
564
|
+
|
|
565
|
+
To force the custom scrollbar to render on mobile devices:
|
|
566
|
+
|
|
567
|
+
```tsx
|
|
568
|
+
<ScrollToFuture nativeOnMobile={false} />
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
## Built-in themes
|
|
572
|
+
|
|
573
|
+
The package includes the following presets:
|
|
574
|
+
|
|
575
|
+
```ts
|
|
576
|
+
type PresetsThemeType =
|
|
577
|
+
| "primary"
|
|
578
|
+
| "midnight"
|
|
579
|
+
| "neonCyan"
|
|
580
|
+
| "ocean"
|
|
581
|
+
| "deepSea"
|
|
582
|
+
| "forest"
|
|
583
|
+
| "moss"
|
|
584
|
+
| "lava"
|
|
585
|
+
| "ember"
|
|
586
|
+
| "gold"
|
|
587
|
+
| "roseQuartz"
|
|
588
|
+
| "violet"
|
|
589
|
+
| "royal"
|
|
590
|
+
| "arctic"
|
|
591
|
+
| "glass"
|
|
592
|
+
| "graphite"
|
|
593
|
+
| "terminal"
|
|
594
|
+
| "toxic"
|
|
595
|
+
| "candy"
|
|
596
|
+
| "sand"
|
|
597
|
+
| "monoLight"
|
|
598
|
+
| "monoDark";
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
Select a theme with `selectTheme`:
|
|
602
|
+
|
|
603
|
+
```tsx
|
|
604
|
+
<ScrollToFuture selectTheme="neonCyan" />
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
```tsx
|
|
608
|
+
<ScrollToFuture selectTheme="forest" />
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
```tsx
|
|
612
|
+
<ScrollToFuture selectTheme="monoDark" />
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
## Custom theme
|
|
616
|
+
|
|
617
|
+
Use `optionsTheme` to override part or all of the selected preset.
|
|
618
|
+
|
|
619
|
+
```tsx
|
|
620
|
+
<ScrollToFuture
|
|
621
|
+
selectTheme="primary"
|
|
622
|
+
optionsTheme={{
|
|
623
|
+
scrollBar: {
|
|
624
|
+
inactive: {
|
|
625
|
+
backgroundColor: "rgba(255, 255, 255, 0.08)",
|
|
626
|
+
borderRadius: "999px",
|
|
627
|
+
},
|
|
628
|
+
|
|
629
|
+
hover: {
|
|
630
|
+
backgroundColor: "rgba(255, 255, 255, 0.16)",
|
|
631
|
+
},
|
|
632
|
+
|
|
633
|
+
active: {
|
|
634
|
+
backgroundColor: "rgba(255, 255, 255, 0.24)",
|
|
635
|
+
},
|
|
636
|
+
},
|
|
637
|
+
|
|
638
|
+
thumb: {
|
|
639
|
+
inactive: {
|
|
640
|
+
backgroundColor: "#8b5cf6",
|
|
641
|
+
borderRadius: "999px",
|
|
642
|
+
},
|
|
643
|
+
|
|
644
|
+
hover: {
|
|
645
|
+
backgroundColor: "#a78bfa",
|
|
646
|
+
transform: "scale(1.05)",
|
|
647
|
+
},
|
|
648
|
+
|
|
649
|
+
active: {
|
|
650
|
+
backgroundColor: "#ddd6fe",
|
|
651
|
+
transform: "scale(1.12)",
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
}}
|
|
655
|
+
/>
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
Custom theme values are deeply merged with the selected preset. You only need to provide the properties you want to replace.
|
|
659
|
+
|
|
660
|
+
## Theme types
|
|
661
|
+
|
|
662
|
+
```ts
|
|
663
|
+
type ScrollToFutureThemeProps = {
|
|
664
|
+
scrollBar?: StatusElementsTheme;
|
|
665
|
+
thumb?: StatusElementsTheme;
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
type StatusElementsTheme = {
|
|
669
|
+
inactive?: ScrollToFutureGeneralTypes;
|
|
670
|
+
hover?: ScrollToFutureGeneralTypes;
|
|
671
|
+
active?: ScrollToFutureGeneralTypes;
|
|
672
|
+
};
|
|
673
|
+
|
|
674
|
+
type ScrollToFutureGeneralTypes = {
|
|
675
|
+
backgroundColor?: string;
|
|
676
|
+
opacity?: number;
|
|
677
|
+
border?: string;
|
|
678
|
+
borderRadius?: string;
|
|
679
|
+
outline?: string;
|
|
680
|
+
boxShadow?: string;
|
|
681
|
+
transition?: string;
|
|
682
|
+
transform?: string;
|
|
683
|
+
};
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
## Complete customization example
|
|
687
|
+
|
|
688
|
+
```tsx
|
|
689
|
+
import { useRef } from "react";
|
|
690
|
+
import { ScrollToFuture } from "scroll-to-future";
|
|
691
|
+
|
|
692
|
+
export const CustomScrollbarExample = () => {
|
|
693
|
+
const targetRef = useRef<HTMLDivElement>(null);
|
|
694
|
+
|
|
695
|
+
return (
|
|
696
|
+
<div style={{ position: "relative" }}>
|
|
697
|
+
<div
|
|
698
|
+
ref={targetRef}
|
|
699
|
+
style={{
|
|
700
|
+
width: "600px",
|
|
701
|
+
height: "400px",
|
|
702
|
+
overflow: "auto",
|
|
703
|
+
}}
|
|
704
|
+
>
|
|
705
|
+
<div
|
|
706
|
+
style={{
|
|
707
|
+
width: "1200px",
|
|
708
|
+
minHeight: "1400px",
|
|
709
|
+
padding: "24px",
|
|
710
|
+
}}
|
|
711
|
+
>
|
|
712
|
+
Scrollable content
|
|
713
|
+
</div>
|
|
714
|
+
</div>
|
|
715
|
+
|
|
716
|
+
<ScrollToFuture
|
|
717
|
+
target={targetRef}
|
|
718
|
+
nativeOnMobile={true}
|
|
719
|
+
selectTheme="violet"
|
|
720
|
+
scrollBar={{
|
|
721
|
+
mode: "both",
|
|
722
|
+
positionMode: "after",
|
|
723
|
+
superimposition: "over",
|
|
724
|
+
boundaryOffset: "6px",
|
|
725
|
+
widthTrack: "10px",
|
|
726
|
+
heightTrack: "90%",
|
|
727
|
+
hideNativeScrollbar: "fine-pointer",
|
|
728
|
+
}}
|
|
729
|
+
thumb={{
|
|
730
|
+
boundaryOffset: "2px",
|
|
731
|
+
heightTrack: "auto",
|
|
732
|
+
}}
|
|
733
|
+
optionsTheme={{
|
|
734
|
+
thumb: {
|
|
735
|
+
active: {
|
|
736
|
+
transform: "scale(1.15)",
|
|
737
|
+
boxShadow: "0 0 16px rgba(139, 92, 246, 0.8)",
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
}}
|
|
741
|
+
/>
|
|
742
|
+
</div>
|
|
743
|
+
);
|
|
744
|
+
};
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
## Next.js
|
|
748
|
+
|
|
749
|
+
The component uses browser APIs and must be rendered inside a Client Component.
|
|
750
|
+
|
|
751
|
+
```tsx
|
|
752
|
+
"use client";
|
|
753
|
+
|
|
754
|
+
import { ScrollToFuture } from "scroll-to-future";
|
|
755
|
+
|
|
756
|
+
export const ScrollContainer = ({
|
|
757
|
+
children,
|
|
758
|
+
}: {
|
|
759
|
+
children: React.ReactNode;
|
|
760
|
+
}) => {
|
|
761
|
+
return (
|
|
762
|
+
<div
|
|
763
|
+
style={{
|
|
764
|
+
position: "relative",
|
|
765
|
+
height: "100dvh",
|
|
766
|
+
overflow: "auto",
|
|
767
|
+
}}
|
|
768
|
+
>
|
|
769
|
+
<ScrollToFuture />
|
|
770
|
+
|
|
771
|
+
{children}
|
|
772
|
+
</div>
|
|
773
|
+
);
|
|
774
|
+
};
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
## How it works
|
|
778
|
+
|
|
779
|
+
The component observes the target element and its content using browser observers.
|
|
780
|
+
|
|
781
|
+
It automatically recalculates:
|
|
782
|
+
|
|
783
|
+
* Scrollable width and height
|
|
784
|
+
* Current scroll position
|
|
785
|
+
* Visible container size
|
|
786
|
+
* Track dimensions
|
|
787
|
+
* Thumb dimensions
|
|
788
|
+
* Thumb position
|
|
789
|
+
* Target element position
|
|
790
|
+
* Horizontal and vertical overflow availability
|
|
791
|
+
|
|
792
|
+
The scrollbar is updated when:
|
|
793
|
+
|
|
794
|
+
* The target is scrolled
|
|
795
|
+
* The window is resized
|
|
796
|
+
* The visual viewport changes
|
|
797
|
+
* The target changes size
|
|
798
|
+
* Child elements change size
|
|
799
|
+
* Elements are added or removed
|
|
800
|
+
* Relevant styles or classes change
|
|
801
|
+
|
|
802
|
+
## Interaction
|
|
803
|
+
|
|
804
|
+
### Dragging the thumb
|
|
805
|
+
|
|
806
|
+
Press and drag the thumb to change the target's scroll position.
|
|
807
|
+
|
|
808
|
+
Pointer capture is used during dragging, so the interaction continues even when the pointer leaves the thumb.
|
|
809
|
+
|
|
810
|
+
### Clicking the track
|
|
811
|
+
|
|
812
|
+
Click an empty part of the track to move the thumb toward that position.
|
|
813
|
+
|
|
814
|
+
The requested scroll position is automatically clamped to the available scroll range.
|
|
815
|
+
|
|
816
|
+
## Page scrolling
|
|
817
|
+
|
|
818
|
+
The component supports page scroll targets such as:
|
|
819
|
+
|
|
820
|
+
* `document.body`
|
|
821
|
+
* `document.documentElement`
|
|
822
|
+
* `document.scrollingElement`
|
|
823
|
+
|
|
824
|
+
When the target is part of the document scrolling system, the component resolves the active scroll container and uses the browser viewport for its measurements.
|
|
825
|
+
|
|
826
|
+
## Important CSS requirements
|
|
827
|
+
|
|
828
|
+
The target element should normally have:
|
|
829
|
+
|
|
830
|
+
```css
|
|
831
|
+
.scroll-container {
|
|
832
|
+
position: relative;
|
|
833
|
+
overflow: auto;
|
|
834
|
+
width: 100%;
|
|
835
|
+
height: 400px;
|
|
836
|
+
}
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
A scrollbar cannot appear when the element has no constrained size or when its content does not exceed its visible area.
|
|
840
|
+
|
|
841
|
+
For vertical scrolling:
|
|
842
|
+
|
|
843
|
+
```css
|
|
844
|
+
.scroll-container {
|
|
845
|
+
overflow-y: auto;
|
|
846
|
+
}
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
For horizontal scrolling:
|
|
850
|
+
|
|
851
|
+
```css
|
|
852
|
+
.scroll-container {
|
|
853
|
+
overflow-x: auto;
|
|
854
|
+
}
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
For both axes:
|
|
858
|
+
|
|
859
|
+
```css
|
|
860
|
+
.scroll-container {
|
|
861
|
+
overflow: auto;
|
|
862
|
+
}
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
## Troubleshooting
|
|
866
|
+
|
|
867
|
+
### The custom scrollbar is not visible
|
|
868
|
+
|
|
869
|
+
Check that:
|
|
870
|
+
|
|
871
|
+
1. The target has a constrained width or height.
|
|
872
|
+
2. The content is larger than the target.
|
|
873
|
+
3. The target uses `overflow: auto` or `overflow: scroll`.
|
|
874
|
+
4. The selected `mode` includes the overflowing axis.
|
|
875
|
+
5. The component is rendered in a Client Component.
|
|
876
|
+
6. `nativeOnMobile` is not disabling the custom scrollbar on the current device.
|
|
877
|
+
|
|
878
|
+
### The browser scrollbar is still visible
|
|
879
|
+
|
|
880
|
+
Check the following:
|
|
881
|
+
|
|
882
|
+
1. `hideNativeScrollbar` is not `false`.
|
|
883
|
+
2. The custom component covers every scrollable axis.
|
|
884
|
+
3. The correct element is passed through `target`.
|
|
885
|
+
4. The actual scroll container is not a nested child of the supplied target.
|
|
886
|
+
5. `nativeOnMobile` is not preserving native scrolling on the current device.
|
|
887
|
+
6. `"fine-pointer"` is only active when the device has a fine pointer.
|
|
888
|
+
|
|
889
|
+
To force native scrollbar hiding on desktop and mobile:
|
|
890
|
+
|
|
891
|
+
```tsx
|
|
892
|
+
<ScrollToFuture
|
|
893
|
+
nativeOnMobile={false}
|
|
894
|
+
scrollBar={{
|
|
895
|
+
hideNativeScrollbar: "always",
|
|
896
|
+
}}
|
|
897
|
+
/>
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
### The content moves when the scrollbar appears
|
|
901
|
+
|
|
902
|
+
The default `superimposition` value is `"after"`, which reserves space by adding padding to the target.
|
|
903
|
+
|
|
904
|
+
Use overlay mode when the scrollbar should not affect content spacing:
|
|
905
|
+
|
|
906
|
+
```tsx
|
|
907
|
+
<ScrollToFuture
|
|
908
|
+
scrollBar={{
|
|
909
|
+
superimposition: "over",
|
|
910
|
+
}}
|
|
911
|
+
/>
|
|
912
|
+
```
|
|
913
|
+
|
|
914
|
+
### The scrollbar is rendered on the wrong element
|
|
915
|
+
|
|
916
|
+
Pass an explicit ref:
|
|
917
|
+
|
|
918
|
+
```tsx
|
|
919
|
+
const targetRef = useRef<HTMLDivElement>(null);
|
|
920
|
+
|
|
921
|
+
<div ref={targetRef}>...</div>
|
|
922
|
+
|
|
923
|
+
<ScrollToFuture target={targetRef} />
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
## Browser requirements
|
|
927
|
+
|
|
928
|
+
The component relies on modern browser APIs:
|
|
929
|
+
|
|
930
|
+
* `ResizeObserver`
|
|
931
|
+
* `MutationObserver`
|
|
932
|
+
* Pointer Events
|
|
933
|
+
* `requestAnimationFrame`
|
|
934
|
+
* `matchMedia`
|
|
935
|
+
* CSS custom properties
|
|
936
|
+
|
|
937
|
+
For older browsers, additional polyfills may be required.
|
|
938
|
+
|
|
939
|
+
## TypeScript
|
|
940
|
+
|
|
941
|
+
The package exports the component and its public configuration type:
|
|
942
|
+
|
|
943
|
+
```ts
|
|
944
|
+
export { ScrollToFuture } from "scroll-to-future";
|
|
945
|
+
|
|
946
|
+
export type {
|
|
947
|
+
ScrollToFutureConfig,
|
|
948
|
+
} from "scroll-to-future";
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
Example:
|
|
952
|
+
|
|
953
|
+
```tsx
|
|
954
|
+
import type { ScrollToFutureConfig } from "scroll-to-future";
|
|
955
|
+
|
|
956
|
+
const scrollbarConfig: ScrollToFutureConfig = {
|
|
957
|
+
selectTheme: "graphite",
|
|
958
|
+
|
|
959
|
+
scrollBar: {
|
|
960
|
+
mode: "vertical",
|
|
961
|
+
positionMode: "after",
|
|
962
|
+
superimposition: "over",
|
|
963
|
+
hideNativeScrollbar: "always",
|
|
964
|
+
},
|
|
965
|
+
|
|
966
|
+
thumb: {
|
|
967
|
+
heightTrack: "auto",
|
|
968
|
+
},
|
|
969
|
+
};
|
|
970
|
+
```
|