CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-overlayscrollbars

A javascript scrollbar plugin which hides native scrollbars, provides custom styleable overlay scrollbars and keeps the native functionality and feeling.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

extensions.mddocs/

Extensions

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.

Extension System Overview

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.

Extension Registration

Global Registration

OverlayScrollbars.extension(
  name: string,
  extensionConstructor: ExtensionConstructor,
  defaultOptions?: object
): boolean;

Register an extension globally so it can be used by any instance.

// Register a simple logging extension
OverlayScrollbars.extension('logger', function(instance, options) {
  return {
    added: function() {
      if (options.logInit) {
        console.log('Logger extension added to instance');
      }
    },
    
    removed: function() {
      if (options.logDestroy) {
        console.log('Logger extension removed from instance');
      }
    }
  };
}, {
  // Default extension options
  logInit: true,
  logDestroy: true,
  logScroll: false
});

Extension Usage

Adding Extensions to Instances

Extensions can be added during initialization or dynamically after creation.

// Add extension during initialization
const instance = OverlayScrollbars(element, {}, {
  logger: {
    logInit: true,
    logScroll: true
  }
});

// Add extension after initialization
instance.addExt('logger', {
  logInit: false,
  logDestroy: true
});

Instance Extension Methods

interface OverlayScrollbarsInstance {
  addExt(extensionName: string, extensionOptions?: object): OverlayScrollbarsExtension | undefined;
  removeExt(extensionName: string): boolean;
  ext(extensionName: string): OverlayScrollbarsExtension | undefined;
}
// Add extension to instance
const extensionInstance = instance.addExt('customExtension', {
  option1: 'value1'
});

// Get extension instance
const ext = instance.ext('customExtension');

// Remove extension from instance
const removed = instance.removeExt('customExtension');

Extension Examples

Auto-Scroll Extension

OverlayScrollbars.extension('autoScroll', function(instance, options) {
  let intervalId;
  let isScrolling = false;
  
  const startAutoScroll = () => {
    if (options.enabled && !isScrolling) {
      isScrolling = true;
      intervalId = setInterval(() => {
        const state = instance.getState();
        if (state.destroyed) {
          stopAutoScroll();
          return;
        }
        
        const currentY = state.contentScrollSize.height * 
          (instance.getElements('viewport').scrollTop / 
           (state.contentScrollSize.height - state.viewportSize.height));
        
        const newY = currentY + options.speed;
        const maxY = state.contentScrollSize.height - state.viewportSize.height;
        
        if (newY >= maxY && options.loop) {
          instance.scroll({ y: 0 }, options.resetDuration);
        } else if (newY < maxY) {
          instance.scroll({ y: newY });
        } else {
          stopAutoScroll();
        }
      }, options.interval);
    }
  };
  
  const stopAutoScroll = () => {
    if (intervalId) {
      clearInterval(intervalId);
      intervalId = null;
      isScrolling = false;
    }
  };
  
  return {
    added: function() {
      if (options.autoStart) {
        startAutoScroll();
      }
      
      // Add public methods to instance
      this.start = startAutoScroll;
      this.stop = stopAutoScroll;
      this.toggle = () => isScrolling ? stopAutoScroll() : startAutoScroll();
    },
    
    removed: function() {
      stopAutoScroll();
    }
  };
}, {
  enabled: true,
  autoStart: false,
  speed: 1,
  interval: 50,
  loop: true,
  resetDuration: 1000
});

// Usage
const instance = OverlayScrollbars(element, {}, {
  autoScroll: {
    autoStart: true,
    speed: 2,
    loop: false
  }
});

// Control auto-scroll
const autoScrollExt = instance.ext('autoScroll');
autoScrollExt.stop();
autoScrollExt.start();

Scroll Position Tracker Extension

OverlayScrollbars.extension('positionTracker', function(instance, options) {
  let positions = [];
  let currentIndex = -1;
  
  const savePosition = () => {
    const viewport = instance.getElements('viewport');
    const position = {
      x: viewport.scrollLeft,
      y: viewport.scrollTop,
      timestamp: Date.now()
    };
    
    positions.push(position);
    currentIndex = positions.length - 1;
    
    // Limit history size
    if (positions.length > options.maxHistory) {
      positions = positions.slice(-options.maxHistory);
      currentIndex = positions.length - 1;
    }
    
    if (options.onPositionSaved) {
      options.onPositionSaved(position, positions.length);
    }
  };
  
  const goToPosition = (index) => {
    if (index >= 0 && index < positions.length) {
      const position = positions[index];
      instance.scroll({ x: position.x, y: position.y }, options.scrollDuration);
      currentIndex = index;
      return position;
    }
    return null;
  };
  
  return {
    added: function() {
      // Save initial position
      if (options.saveInitial) {
        savePosition();
      }
      
      // Set up scroll tracking
      if (options.trackScroll) {
        instance.options('callbacks.onScrollStop', savePosition);
      }
      
      // Public API
      this.save = savePosition;
      this.goTo = goToPosition;
      this.getHistory = () => [...positions];
      this.clear = () => {
        positions = [];
        currentIndex = -1;
      };
      this.back = () => goToPosition(Math.max(0, currentIndex - 1));
      this.forward = () => goToPosition(Math.min(positions.length - 1, currentIndex + 1));
    },
    
    removed: function() {
      // Cleanup if needed
    }
  };
}, {
  maxHistory: 20,
  saveInitial: true,
  trackScroll: true,
  scrollDuration: 300,
  onPositionSaved: null
});

// Usage
const instance = OverlayScrollbars(element, {}, {
  positionTracker: {
    maxHistory: 50,
    onPositionSaved: (position, count) => {
      console.log(`Position ${count} saved:`, position);
    }
  }
});

// Use the tracker
const tracker = instance.ext('positionTracker');
tracker.save(); // Manually save current position
tracker.back(); // Go to previous position
tracker.forward(); // Go to next position
console.log(tracker.getHistory()); // Get all saved positions

Scroll Synchronization Extension

OverlayScrollbars.extension('syncScroll', function(instance, options) {
  let syncGroup = options.group || 'default';
  let isUpdating = false;
  
  // Global registry for sync groups
  if (!window.OverlayScrollbarsSyncGroups) {
    window.OverlayScrollbarsSyncGroups = {};
  }
  
  const registry = window.OverlayScrollbarsSyncGroups;
  
  const onScroll = () => {
    if (isUpdating) return;
    
    const viewport = instance.getElements('viewport');
    const scrollInfo = {
      x: viewport.scrollLeft,
      y: viewport.scrollTop,
      xPercent: viewport.scrollLeft / (viewport.scrollWidth - viewport.clientWidth),
      yPercent: viewport.scrollTop / (viewport.scrollHeight - viewport.clientHeight)
    };
    
    // Update other instances in the same group
    if (registry[syncGroup]) {
      registry[syncGroup].forEach(otherInstance => {
        if (otherInstance !== instance) {
          otherInstance._syncUpdate = true;
          
          if (options.syncMode === 'percent') {
            const otherViewport = otherInstance.getElements('viewport');
            const targetX = scrollInfo.xPercent * (otherViewport.scrollWidth - otherViewport.clientWidth);
            const targetY = scrollInfo.yPercent * (otherViewport.scrollHeight - otherViewport.clientHeight);
            otherInstance.scroll({ x: targetX, y: targetY });
          } else {
            otherInstance.scroll({ x: scrollInfo.x, y: scrollInfo.y });
          }
          
          setTimeout(() => {
            otherInstance._syncUpdate = false;
          }, 10);
        }
      });
    }
  };
  
  return {
    added: function() {
      // Add to sync group
      if (!registry[syncGroup]) {
        registry[syncGroup] = [];
      }
      registry[syncGroup].push(instance);
      
      // Set up scroll listener
      instance.options('callbacks.onScroll', onScroll);
    },
    
    removed: function() {
      // Remove from sync group
      if (registry[syncGroup]) {
        const index = registry[syncGroup].indexOf(instance);
        if (index > -1) {
          registry[syncGroup].splice(index, 1);
        }
        
        // Clean up empty groups
        if (registry[syncGroup].length === 0) {
          delete registry[syncGroup];
        }
      }
    }
  };
}, {
  group: 'default',
  syncMode: 'percent' // 'percent' or 'absolute'
});

// Usage - synchronize scrolling between multiple elements
const instance1 = OverlayScrollbars(element1, {}, {
  syncScroll: { group: 'mainContent' }
});

const instance2 = OverlayScrollbars(element2, {}, {
  syncScroll: { group: 'mainContent' }
});

// Now scrolling one will scroll the other

Extension Management

Retrieving Extensions

// Get all registered extensions
const allExtensions = OverlayScrollbars.extension();

// Get specific extension constructor
const loggerExt = OverlayScrollbars.extension('logger');

// Check if extension exists
if (OverlayScrollbars.extension('customExt')) {
  // Extension is registered
}

Unregistering Extensions

// Unregister an extension
const success = OverlayScrollbars.extension('extensionName', null);

Extension Lifecycle

interface OverlayScrollbarsExtension {
  added(instance: OverlayScrollbarsInstance, options: object): void;
  removed?(): void;
}

type ExtensionConstructor = (
  instance: OverlayScrollbarsInstance,
  options: object
) => OverlayScrollbarsExtension;

Extension Methods

  • added() - Called when the extension is added to an instance
  • removed() - Called when the extension is removed (optional)

Extension Context

Inside extension methods, this refers to the extension instance, allowing you to store state and expose public methods.

OverlayScrollbars.extension('statefulExtension', function(instance, options) {
  let state = { count: 0 };
  
  return {
    added: function() {
      // `this` is the extension instance
      this.increment = () => state.count++;
      this.getCount = () => state.count;
      this.reset = () => state.count = 0;
    },
    
    removed: function() {
      // Cleanup
    }
  };
});

// Access extension methods
const ext = instance.ext('statefulExtension');
ext.increment();
console.log(ext.getCount()); // 1

Types

interface OverlayScrollbarsExtension {
  added(instance: OverlayScrollbarsInstance, options: object): void;
  removed?(): void;
  [key: string]: any; // Extensions can add custom methods
}

type ExtensionConstructor = (
  instance: OverlayScrollbarsInstance,
  options: object
) => OverlayScrollbarsExtension;

type OverlayScrollbarsExtensions = {
  [extensionName: string]: object;
} | object[];

Install with Tessl CLI

npx tessl i tessl/npm-overlayscrollbars

docs

extensions.md

index.md

initialization.md

instance-methods.md

jquery.md

options.md

static-methods.md

tile.json