0
# Extensions
1
2
OverlayScrollbars provides a powerful extension system that allows you to add custom functionality to instances through a plugin architecture. Extensions can be registered globally and used across multiple instances.
3
4
## Extension System Overview
5
6
Extensions are objects with lifecycle methods that get called when they are added to or removed from an OverlayScrollbars instance. They provide a clean way to extend functionality without modifying the core library.
7
8
## Extension Registration
9
10
### Global Registration
11
12
```javascript { .api }
13
OverlayScrollbars.extension(
14
name: string,
15
extensionConstructor: ExtensionConstructor,
16
defaultOptions?: object
17
): boolean;
18
```
19
20
Register an extension globally so it can be used by any instance.
21
22
```javascript
23
// Register a simple logging extension
24
OverlayScrollbars.extension('logger', function(instance, options) {
25
return {
26
added: function() {
27
if (options.logInit) {
28
console.log('Logger extension added to instance');
29
}
30
},
31
32
removed: function() {
33
if (options.logDestroy) {
34
console.log('Logger extension removed from instance');
35
}
36
}
37
};
38
}, {
39
// Default extension options
40
logInit: true,
41
logDestroy: true,
42
logScroll: false
43
});
44
```
45
46
## Extension Usage
47
48
### Adding Extensions to Instances
49
50
Extensions can be added during initialization or dynamically after creation.
51
52
```javascript
53
// Add extension during initialization
54
const instance = OverlayScrollbars(element, {}, {
55
logger: {
56
logInit: true,
57
logScroll: true
58
}
59
});
60
61
// Add extension after initialization
62
instance.addExt('logger', {
63
logInit: false,
64
logDestroy: true
65
});
66
```
67
68
### Instance Extension Methods
69
70
```javascript { .api }
71
interface OverlayScrollbarsInstance {
72
addExt(extensionName: string, extensionOptions?: object): OverlayScrollbarsExtension | undefined;
73
removeExt(extensionName: string): boolean;
74
ext(extensionName: string): OverlayScrollbarsExtension | undefined;
75
}
76
```
77
78
```javascript
79
// Add extension to instance
80
const extensionInstance = instance.addExt('customExtension', {
81
option1: 'value1'
82
});
83
84
// Get extension instance
85
const ext = instance.ext('customExtension');
86
87
// Remove extension from instance
88
const removed = instance.removeExt('customExtension');
89
```
90
91
## Extension Examples
92
93
### Auto-Scroll Extension
94
95
```javascript
96
OverlayScrollbars.extension('autoScroll', function(instance, options) {
97
let intervalId;
98
let isScrolling = false;
99
100
const startAutoScroll = () => {
101
if (options.enabled && !isScrolling) {
102
isScrolling = true;
103
intervalId = setInterval(() => {
104
const state = instance.getState();
105
if (state.destroyed) {
106
stopAutoScroll();
107
return;
108
}
109
110
const currentY = state.contentScrollSize.height *
111
(instance.getElements('viewport').scrollTop /
112
(state.contentScrollSize.height - state.viewportSize.height));
113
114
const newY = currentY + options.speed;
115
const maxY = state.contentScrollSize.height - state.viewportSize.height;
116
117
if (newY >= maxY && options.loop) {
118
instance.scroll({ y: 0 }, options.resetDuration);
119
} else if (newY < maxY) {
120
instance.scroll({ y: newY });
121
} else {
122
stopAutoScroll();
123
}
124
}, options.interval);
125
}
126
};
127
128
const stopAutoScroll = () => {
129
if (intervalId) {
130
clearInterval(intervalId);
131
intervalId = null;
132
isScrolling = false;
133
}
134
};
135
136
return {
137
added: function() {
138
if (options.autoStart) {
139
startAutoScroll();
140
}
141
142
// Add public methods to instance
143
this.start = startAutoScroll;
144
this.stop = stopAutoScroll;
145
this.toggle = () => isScrolling ? stopAutoScroll() : startAutoScroll();
146
},
147
148
removed: function() {
149
stopAutoScroll();
150
}
151
};
152
}, {
153
enabled: true,
154
autoStart: false,
155
speed: 1,
156
interval: 50,
157
loop: true,
158
resetDuration: 1000
159
});
160
161
// Usage
162
const instance = OverlayScrollbars(element, {}, {
163
autoScroll: {
164
autoStart: true,
165
speed: 2,
166
loop: false
167
}
168
});
169
170
// Control auto-scroll
171
const autoScrollExt = instance.ext('autoScroll');
172
autoScrollExt.stop();
173
autoScrollExt.start();
174
```
175
176
### Scroll Position Tracker Extension
177
178
```javascript
179
OverlayScrollbars.extension('positionTracker', function(instance, options) {
180
let positions = [];
181
let currentIndex = -1;
182
183
const savePosition = () => {
184
const viewport = instance.getElements('viewport');
185
const position = {
186
x: viewport.scrollLeft,
187
y: viewport.scrollTop,
188
timestamp: Date.now()
189
};
190
191
positions.push(position);
192
currentIndex = positions.length - 1;
193
194
// Limit history size
195
if (positions.length > options.maxHistory) {
196
positions = positions.slice(-options.maxHistory);
197
currentIndex = positions.length - 1;
198
}
199
200
if (options.onPositionSaved) {
201
options.onPositionSaved(position, positions.length);
202
}
203
};
204
205
const goToPosition = (index) => {
206
if (index >= 0 && index < positions.length) {
207
const position = positions[index];
208
instance.scroll({ x: position.x, y: position.y }, options.scrollDuration);
209
currentIndex = index;
210
return position;
211
}
212
return null;
213
};
214
215
return {
216
added: function() {
217
// Save initial position
218
if (options.saveInitial) {
219
savePosition();
220
}
221
222
// Set up scroll tracking
223
if (options.trackScroll) {
224
instance.options('callbacks.onScrollStop', savePosition);
225
}
226
227
// Public API
228
this.save = savePosition;
229
this.goTo = goToPosition;
230
this.getHistory = () => [...positions];
231
this.clear = () => {
232
positions = [];
233
currentIndex = -1;
234
};
235
this.back = () => goToPosition(Math.max(0, currentIndex - 1));
236
this.forward = () => goToPosition(Math.min(positions.length - 1, currentIndex + 1));
237
},
238
239
removed: function() {
240
// Cleanup if needed
241
}
242
};
243
}, {
244
maxHistory: 20,
245
saveInitial: true,
246
trackScroll: true,
247
scrollDuration: 300,
248
onPositionSaved: null
249
});
250
251
// Usage
252
const instance = OverlayScrollbars(element, {}, {
253
positionTracker: {
254
maxHistory: 50,
255
onPositionSaved: (position, count) => {
256
console.log(`Position ${count} saved:`, position);
257
}
258
}
259
});
260
261
// Use the tracker
262
const tracker = instance.ext('positionTracker');
263
tracker.save(); // Manually save current position
264
tracker.back(); // Go to previous position
265
tracker.forward(); // Go to next position
266
console.log(tracker.getHistory()); // Get all saved positions
267
```
268
269
### Scroll Synchronization Extension
270
271
```javascript
272
OverlayScrollbars.extension('syncScroll', function(instance, options) {
273
let syncGroup = options.group || 'default';
274
let isUpdating = false;
275
276
// Global registry for sync groups
277
if (!window.OverlayScrollbarsSyncGroups) {
278
window.OverlayScrollbarsSyncGroups = {};
279
}
280
281
const registry = window.OverlayScrollbarsSyncGroups;
282
283
const onScroll = () => {
284
if (isUpdating) return;
285
286
const viewport = instance.getElements('viewport');
287
const scrollInfo = {
288
x: viewport.scrollLeft,
289
y: viewport.scrollTop,
290
xPercent: viewport.scrollLeft / (viewport.scrollWidth - viewport.clientWidth),
291
yPercent: viewport.scrollTop / (viewport.scrollHeight - viewport.clientHeight)
292
};
293
294
// Update other instances in the same group
295
if (registry[syncGroup]) {
296
registry[syncGroup].forEach(otherInstance => {
297
if (otherInstance !== instance) {
298
otherInstance._syncUpdate = true;
299
300
if (options.syncMode === 'percent') {
301
const otherViewport = otherInstance.getElements('viewport');
302
const targetX = scrollInfo.xPercent * (otherViewport.scrollWidth - otherViewport.clientWidth);
303
const targetY = scrollInfo.yPercent * (otherViewport.scrollHeight - otherViewport.clientHeight);
304
otherInstance.scroll({ x: targetX, y: targetY });
305
} else {
306
otherInstance.scroll({ x: scrollInfo.x, y: scrollInfo.y });
307
}
308
309
setTimeout(() => {
310
otherInstance._syncUpdate = false;
311
}, 10);
312
}
313
});
314
}
315
};
316
317
return {
318
added: function() {
319
// Add to sync group
320
if (!registry[syncGroup]) {
321
registry[syncGroup] = [];
322
}
323
registry[syncGroup].push(instance);
324
325
// Set up scroll listener
326
instance.options('callbacks.onScroll', onScroll);
327
},
328
329
removed: function() {
330
// Remove from sync group
331
if (registry[syncGroup]) {
332
const index = registry[syncGroup].indexOf(instance);
333
if (index > -1) {
334
registry[syncGroup].splice(index, 1);
335
}
336
337
// Clean up empty groups
338
if (registry[syncGroup].length === 0) {
339
delete registry[syncGroup];
340
}
341
}
342
}
343
};
344
}, {
345
group: 'default',
346
syncMode: 'percent' // 'percent' or 'absolute'
347
});
348
349
// Usage - synchronize scrolling between multiple elements
350
const instance1 = OverlayScrollbars(element1, {}, {
351
syncScroll: { group: 'mainContent' }
352
});
353
354
const instance2 = OverlayScrollbars(element2, {}, {
355
syncScroll: { group: 'mainContent' }
356
});
357
358
// Now scrolling one will scroll the other
359
```
360
361
## Extension Management
362
363
### Retrieving Extensions
364
365
```javascript
366
// Get all registered extensions
367
const allExtensions = OverlayScrollbars.extension();
368
369
// Get specific extension constructor
370
const loggerExt = OverlayScrollbars.extension('logger');
371
372
// Check if extension exists
373
if (OverlayScrollbars.extension('customExt')) {
374
// Extension is registered
375
}
376
```
377
378
### Unregistering Extensions
379
380
```javascript
381
// Unregister an extension
382
const success = OverlayScrollbars.extension('extensionName', null);
383
```
384
385
## Extension Lifecycle
386
387
```javascript { .api }
388
interface OverlayScrollbarsExtension {
389
added(instance: OverlayScrollbarsInstance, options: object): void;
390
removed?(): void;
391
}
392
393
type ExtensionConstructor = (
394
instance: OverlayScrollbarsInstance,
395
options: object
396
) => OverlayScrollbarsExtension;
397
```
398
399
### Extension Methods
400
401
- **added()** - Called when the extension is added to an instance
402
- **removed()** - Called when the extension is removed (optional)
403
404
### Extension Context
405
406
Inside extension methods, `this` refers to the extension instance, allowing you to store state and expose public methods.
407
408
```javascript
409
OverlayScrollbars.extension('statefulExtension', function(instance, options) {
410
let state = { count: 0 };
411
412
return {
413
added: function() {
414
// `this` is the extension instance
415
this.increment = () => state.count++;
416
this.getCount = () => state.count;
417
this.reset = () => state.count = 0;
418
},
419
420
removed: function() {
421
// Cleanup
422
}
423
};
424
});
425
426
// Access extension methods
427
const ext = instance.ext('statefulExtension');
428
ext.increment();
429
console.log(ext.getCount()); // 1
430
```
431
432
## Types
433
434
```javascript { .api }
435
interface OverlayScrollbarsExtension {
436
added(instance: OverlayScrollbarsInstance, options: object): void;
437
removed?(): void;
438
[key: string]: any; // Extensions can add custom methods
439
}
440
441
type ExtensionConstructor = (
442
instance: OverlayScrollbarsInstance,
443
options: object
444
) => OverlayScrollbarsExtension;
445
446
type OverlayScrollbarsExtensions = {
447
[extensionName: string]: object;
448
} | object[];
449
```