Comprehensive event system for tracking scroll state, user interactions, and lifecycle events. Better Scroll uses a custom event emitter pattern that allows you to listen for various scroll-related events throughout the interaction lifecycle.
Registers an event listener for the specified event type.
/**
* Register an event listener
* @param type - Event type name
* @param fn - Event handler function
* @param context - Context for 'this' in handler (default: BScroll instance)
*/
on(type: string, fn: Function, context?: any): void;Registers a one-time event listener that automatically removes itself after first execution.
/**
* Register a one-time event listener
* @param type - Event type name
* @param fn - Event handler function
* @param context - Context for 'this' in handler (default: BScroll instance)
*/
once(type: string, fn: Function, context?: any): void;Removes an event listener for the specified event type.
/**
* Remove an event listener
* @param type - Event type name
* @param fn - Event handler function to remove
*/
off(type: string, fn: Function): void;Usage Examples:
import BScroll from "better-scroll";
const scroll = new BScroll('.wrapper');
// Register event listeners
scroll.on('scroll', handleScroll);
scroll.on('scrollEnd', handleScrollEnd);
// One-time listener
scroll.once('scrollStart', () => {
console.log('First scroll started!');
});
// Remove listener
scroll.off('scroll', handleScroll);
function handleScroll(position) {
console.log(`Scrolling to: ${position.x}, ${position.y}`);
}
function handleScrollEnd(position) {
console.log(`Scroll ended at: ${position.x}, ${position.y}`);
}Fired during scrolling with current position coordinates.
// Event: 'scroll'
// Payload: {x: number, y: number}
// Fired: During scrolling movement (frequency depends on probeType)Usage:
scroll.on('scroll', (position) => {
console.log(`Current position: ${position.x}, ${position.y}`);
// Update scroll indicator
updateScrollIndicator(position.y);
// Parallax effects
updateParallaxElements(position.y);
});Fired when scrolling begins, before any movement occurs.
// Event: 'scrollStart'
// Payload: none
// Fired: When user starts scroll gestureFired before scrolling starts, earliest in the scroll lifecycle.
// Event: 'beforeScrollStart'
// Payload: none
// Fired: Before scroll gesture processing beginsFired when scrolling animation completes and comes to rest.
// Event: 'scrollEnd'
// Payload: {x: number, y: number}
// Fired: When scrolling animation finishesUsage Example:
scroll.on('scrollStart', () => {
// Hide UI elements during scroll
hideFloatingButton();
});
scroll.on('scrollEnd', (position) => {
// Show UI elements when scroll stops
showFloatingButton();
// Save scroll position
saveScrollPosition(position);
// Load more content if near bottom
if (isNearBottom(position.y)) {
loadMoreContent();
}
});Fired when user lifts finger/releases mouse, regardless of whether scrolling will continue.
// Event: 'touchEnd'
// Payload: {x: number, y: number}
// Fired: On touch/mouse up eventFired when user performs a quick flick gesture.
// Event: 'flick'
// Payload: none
// Fired: When flick gesture is detected (based on flickLimitTime and flickLimitDistance)Fired when a scroll operation is canceled (e.g., when a click is detected instead of scroll).
// Event: 'scrollCancel'
// Payload: none
// Fired: When scroll is canceled due to click detectionUsage Examples:
// Handle touch end for custom logic
scroll.on('touchEnd', (position) => {
// Check if user scrolled past certain point
if (position.y < -triggerThreshold) {
triggerRefresh();
}
});
// Handle flick gestures
scroll.on('flick', () => {
// Custom flick behavior
console.log('User performed a flick gesture');
});Fired after the refresh() method completes recalculation.
// Event: 'refresh'
// Payload: none
// Fired: After refresh() method completesFired when the BScroll instance is destroyed.
// Event: 'destroy'
// Payload: none
// Fired: When destroy() method is calledUsage Examples:
scroll.on('refresh', () => {
// Update custom UI after dimensions recalculated
updateCustomScrollbar();
});
scroll.on('destroy', () => {
// Clean up custom resources
cleanupCustomElements();
removeCustomEventListeners();
});When pull-to-refresh is enabled, additional events are available:
// Event: 'pullingDown'
// Payload: none
// Fired: When pull-down threshold is reached
// Required: options.pullDownRefresh = true
// Event: 'pullingUp'
// Payload: none
// Fired: When pull-up threshold is reached
// Required: options.pullUpLoad = trueUsage:
const scroll = new BScroll('.wrapper', {
pullDownRefresh: {
threshold: 50,
stop: 20
},
pullUpLoad: {
threshold: 50
}
});
scroll.on('pullingDown', () => {
// Show loading indicator and refresh data
showRefreshLoader();
fetchNewData().then(() => {
scroll.finishPullDown();
});
});
scroll.on('pullingUp', () => {
// Load more data
showLoadMoreIndicator();
fetchMoreData().then(() => {
scroll.finishPullUp();
});
});The frequency of scroll events depends on the probeType option:
interface ProbeTypeConfiguration {
probeType: 0 | 1 | 2 | 3;
}
// probeType: 0 - No scroll events during animation
// probeType: 1 - Non-real-time scroll events (debounced)
// probeType: 2 - Real-time scroll events during momentum only
// probeType: 3 - Real-time scroll events always (including animations)Usage Examples:
// For basic scroll position tracking
const scroll = new BScroll('.wrapper', {
probeType: 1
});
// For smooth scroll indicators
const scroll = new BScroll('.wrapper', {
probeType: 2
});
// For real-time parallax effects
const scroll = new BScroll('.wrapper', {
probeType: 3
});class ScrollTracker {
constructor(scrollInstance) {
this.scroll = scrollInstance;
this.setupEventListeners();
}
setupEventListeners() {
this.scroll.on('scroll', this.updatePosition.bind(this));
this.scroll.on('scrollEnd', this.savePosition.bind(this));
}
updatePosition(pos) {
// Update scroll indicator
const percentage = Math.abs(pos.y) / Math.abs(this.scroll.maxScrollY);
this.updateScrollbar(percentage);
}
savePosition(pos) {
// Persist scroll position
localStorage.setItem('scrollPosition', JSON.stringify(pos));
}
}class InfiniteScroll {
constructor(scrollInstance, threshold = 100) {
this.scroll = scrollInstance;
this.threshold = threshold;
this.loading = false;
this.scroll.on('scroll', this.checkLoadMore.bind(this));
}
checkLoadMore(pos) {
if (this.loading) return;
const bottom = Math.abs(pos.y);
const maxScroll = Math.abs(this.scroll.maxScrollY);
if (bottom + this.threshold >= maxScroll) {
this.loadMore();
}
}
async loadMore() {
this.loading = true;
try {
await this.fetchMoreData();
this.scroll.refresh(); // Recalculate boundaries
} finally {
this.loading = false;
}
}
}const scroll = new BScroll('.wrapper', {
probeType: 3
});
scroll.on('scroll', (pos) => {
// Parallax background
const parallaxElement = document.querySelector('.parallax-bg');
const parallaxSpeed = 0.5;
const translateY = pos.y * parallaxSpeed;
parallaxElement.style.transform = `translateY(${translateY}px)`;
// Fade header based on scroll
const header = document.querySelector('.header');
const opacity = Math.max(0, 1 - Math.abs(pos.y) / 200);
header.style.opacity = opacity;
});