GSAP Carousel Documentation
A production-ready, seamless horizontal carousel component built on GSAP's seamlessLoop helper function
Welcome
GSAP Carousel is an enhanced, production-ready implementation of GSAP's seamlessLoop helper function. It provides a high-performance, feature-rich carousel component with smooth infinite scrolling, responsive design, comprehensive accessibility features, and extensive customization options. Built on top of the powerful GSAP (GreenSock Animation Platform), it leverages hardware acceleration and advanced animation techniques to deliver exceptional performance across all modern browsers.
Key Features
- True Seamless Loop - Uses GSAP's advanced xPercent positioning for pixel-perfect infinite scrolling without visible resets or jumps
- Responsive Grid System - Configurable breakpoints with CSS custom properties for flexible items-per-row layouts
- Advanced Touch & Mouse Dragging - Momentum-based scrolling with InertiaPlugin integration and customizable snap points
- Smart Autoplay System - Pause on visibility changes, user interaction, and focus events with configurable delays
- Comprehensive Navigation - Previous/next buttons, pagination dots, and full keyboard navigation (arrow keys, Home, End)
- Center Mode Support - Active slide centering with precise positioning calculations
- WCAG 2.1 Compliance - Full ARIA implementation, screen reader support, and keyboard accessibility
- Memory Management - Automatic cleanup of event listeners, observers, and animations with comprehensive destroy methods
- Developer Experience - Debug mode, comprehensive error handling, and TypeScript-friendly API design
Installation
Quick Start
Download the carousel file and include it in your project. The carousel automatically detects available GSAP plugins and adjusts functionality accordingly.
<!-- Load GSAP first -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.13.0/dist/gsap.min.js"></script>
<!-- Load carousel -->
<script src="path/to/gsap-carousel.min.js"></script>
Required Dependencies
GSAP 3.12+ is required. Additional plugins enhance functionality but are not mandatory:
<!-- Required: GSAP Core (3.12.0 or newer) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.13.0/dist/gsap.min.js"></script>
<!-- Optional: For drag functionality -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.13.0/dist/Draggable.min.js"></script>
<!-- Optional: For momentum scrolling (Club GreenSock) -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.13.0/dist/InertiaPlugin.min.js"></script>
Basic Usage
Minimal HTML Structure
The carousel requires a container element with child slides. No specific CSS classes are needed - the carousel handles all styling automatically:
<div id="my-carousel">
<div class="slide">
<h3>Slide 1</h3>
<p>Your content here</p>
</div>
<div class="slide">
<h3>Slide 2</h3>
<p>Your content here</p>
</div>
<div class="slide">
<h3>Slide 3</h3>
<p>Your content here</p>
</div>
</div>
Basic Initialization
// Minimal initialization
const carousel = horizontalLoop("#my-carousel");
// With basic options
const carousel = horizontalLoop("#my-carousel", {
speed: 1, // Animation speed multiplier
gap: "20px", // Space between slides
paused: false // Start playing immediately
});
Advanced Configuration
const carousel = horizontalLoop("#my-carousel", {
// Responsive breakpoints
responsive: {
0: { items: 1 }, // Mobile: 1 slide visible
768: { items: 2 }, // Tablet: 2 slides visible
1024: { items: 3 }, // Desktop: 3 slides visible
},
// Animation settings
speed: 1.5, // Slightly faster than default
gap: "24px", // Consistent spacing
// Interaction options
draggable: true, // Enable touch/mouse dragging
snap: 1, // Snap to nearest slide
center: false, // Don't center active slide
// Autoplay configuration
autoplayDelay: 3, // 3 seconds between advances
paused: false, // Start immediately
reversed: false, // Move left to right
// Navigation elements
prevNav: "#prev-btn", // Previous button selector
nextNav: "#next-btn", // Next button selector
dots: "#pagination", // Dots container selector
// Accessibility
ariaLabel: "Product showcase",
accessibilityEnabled: true,
// Callbacks
onChange: ({ currentIndex, currentItem, totalItems }) => {
console.log(`Showing slide ${currentIndex + 1} of ${totalItems}`);
// Update custom UI elements
updateSlideCounter(currentIndex + 1, totalItems);
updateSlideDescription(currentItem);
},
onInitialized: (payload) => {
console.log("Carousel ready!", payload);
// Hide loading indicator, start additional animations, etc.
},
// Development
debug: false // Enable for development
});
Error Handling
// Always check for successful initialization
const carousel = horizontalLoop("#my-carousel", config);
if (carousel) {
console.log("Carousel initialized successfully");
// Store reference for cleanup
window.myCarousel = carousel;
} else {
console.error("Failed to initialize carousel");
// Handle fallback UI or show error message
}
Configuration Options
Complete reference of all configuration options with detailed explanations and usage examples:
Option | Type | Default | Description |
---|---|---|---|
responsive |
Object|null |
null |
Breakpoint mapping for responsive layouts. Keys are pixel
widths, values are {items: number} objects
|
speed |
number |
1 |
Animation speed multiplier. 1 ≈ 100px/second. Higher values = faster movement |
gap |
string|number |
"0px" |
Space between slides. CSS length values ("20px", "1rem") or numbers (converted to px) |
draggable |
boolean |
false |
Enable touch/mouse dragging. Requires GSAP Draggable plugin |
repeat |
number |
0 |
Number of complete loop cycles. -1 for infinite, 0 for continuous |
paused |
boolean |
true |
Start in paused state. Set to false for immediate autoplay |
autoplayDelay |
number |
0 |
Seconds between automatic slide advances. 0 disables autoplay |
reversed |
boolean |
false |
Reverse animation direction (right to left) |
prevNav |
Element|string|Array |
null |
Previous button element, selector, or [element, options] tuple |
nextNav |
Element|string|Array |
null |
Next button element, selector, or [element, options] tuple |
dots |
Element|string|Array |
null |
Pagination container element, selector, or [container, options] tuple |
snap |
number|false |
1 |
Snap increment in slides. false disables snapping. Useful for partial slide views |
center |
boolean |
false |
Center active slide in viewport. Useful for highlighting current item |
updateOnlyOnSettle |
boolean |
false |
Fire onChange only after drag/scroll settles. Improves performance with expensive callbacks |
onChange |
Function|null |
null |
Callback when active slide changes. Receives payload object with slide data |
onInitialized |
Function|null |
null |
Callback after carousel setup completes. Useful for hiding loading states |
ariaLabel |
string |
"Carousel" |
ARIA label for the carousel container. Important for screen readers |
debug |
boolean |
false |
Enable detailed console logging. Includes timing, positioning, and state information |
accessibilityEnabled |
boolean |
true |
Enable ARIA roles, keyboard navigation, and screen reader support |
Advanced Option Examples
const carousel = horizontalLoop("#carousel", {
// Navigation elements with custom animation options
prevNav: ["#prev-btn", { duration: 0.8, ease: "power2.inOut" }],
nextNav: ["#next-btn", { duration: 0.8, ease: "power2.inOut" }],
// Dots with custom animation
dots: ["#pagination", { duration: 0.5, ease: "power1.out" }],
});
Responsive Configuration
The responsive system uses CSS custom properties and breakpoint-based configurations to create fluid, adaptive layouts that work across all device sizes.
Basic Responsive Setup
const carousel = horizontalLoop("#carousel", {
responsive: {
0: { items: 1 }, // Mobile phones
640: { items: 2 }, // Large phones / small tablets
768: { items: 2 }, // Tablets
1024: { items: 3 }, // Small desktop
1280: { items: 4 }, // Large desktop
1536: { items: 5 }, // Extra large screens
},
gap: "16px",
});
Advanced Responsive Configuration
const carousel = horizontalLoop("#carousel", {
responsive: {
// Mobile portrait
0: { items: 1 },
// Mobile landscape / small tablets
480: { items: 1.5 }, // Show partial next slide
// Tablets
768: { items: 2.2 }, // Show 2 full + partial 3rd slide
// Small desktop
1024: { items: 3 },
// Large desktop - different approach for wide screens
1440: { items: 4 },
// Ultra-wide screens
1920: { items: 5 },
},
// Responsive gap sizing
gap: window.innerWidth < 768 ? "12px" : "24px",
// Different snap behavior on mobile
snap: window.innerWidth < 768 ? 1 : 0.5,
});
CSS Integration
The carousel sets CSS custom properties that you can use for additional responsive styling:
/* The carousel automatically sets these CSS custom properties: */
.carousel-container {
--items-per-row: 3; /* Current items visible */
--gap: 20px; /* Current gap size */
}
/* Use them in your CSS for consistent styling */
.carousel-slide {
/* Slide width automatically calculated */
flex: 0 0 calc(100% / var(--items-per-row) - (var(--gap) * (var(--items-per-row) - 1) / var(--items-per-row)));
/* Additional responsive styles */
padding: calc(var(--gap) / 2);
}
/* Override styles at specific breakpoints */
@media (max-width: 767px) {
.carousel-slide {
padding: 8px;
}
}
@media (min-width: 1200px) {
.carousel-slide {
padding: 16px;
}
}
Dynamic Responsive Updates
// Listen for orientation changes
window.addEventListener('orientationchange', () => {
// Wait for orientation change to complete
setTimeout(() => {
carousel.refresh(true); // Deep refresh after orientation change
}, 100);
});
// Update responsive config dynamically
function updateCarouselForViewport() {
const width = window.innerWidth;
// You can't change the responsive config after initialization,
// but you can work with the existing breakpoints
if (width < 768) {
carousel.pauseAutoplay(); // Pause on mobile to save battery
} else {
carousel.playAutoplay(); // Resume on larger screens
}
}
// Debounced resize handler
let resizeTimeout;
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(updateCarouselForViewport, 150);
});
API Methods
The carousel returns a GSAP Timeline instance with additional methods for programmatic control. All methods include error handling and state validation.
Navigation Methods
// Navigate to specific slide by index (0-based)
carousel.toIndex(2); // Go to 3rd slide
// Navigate with custom animation options
carousel.toIndex(2, {
duration: 1.2, // Custom duration
ease: "power2.inOut", // Custom easing
onComplete: () => { // Callback when animation completes
console.log("Animation finished");
}
});
// Navigate to next/previous slide
carousel.next(); // Next slide with default options
carousel.previous(); // Previous slide with default options
// Navigate with options
carousel.next({
duration: 0.8,
ease: "back.out(1.7)"
});
// Get current slide index
const currentIndex = carousel.current(); // Returns current slide index (0-based)
console.log(`Currently on slide ${currentIndex + 1}`);
Playback Control
// Autoplay control (works with autoplayDelay setting)
carousel.pauseAutoplay(); // Pause automatic advancement
carousel.playAutoplay(); // Resume automatic advancement
// Standard GSAP timeline controls
carousel.play(); // Play the continuous loop
carousel.pause(); // Pause at current position
carousel.reverse(); // Reverse direction
carousel.resume(); // Resume from paused state
// Timeline properties
console.log(carousel.duration()); // Get total loop duration
console.log(carousel.progress()); // Get current progress (0-1)
console.log(carousel.time()); // Get current time position
// Set specific timeline position
carousel.progress(0.5); // Jump to middle of loop
carousel.time(2); // Jump to 2 seconds into loop
Utility Methods
// Refresh layout calculations (after DOM changes)
carousel.refresh(); // Light refresh - recalculate positions
carousel.refresh(true); // Deep refresh - rebuild entire timeline
// Use after adding/removing slides
// Get slide information
const closestIndex = carousel.closestIndex(); // Don't update internal state
const currentIndex = carousel.closestIndex(true); // Update internal state
// State inspection (debug mode only)
if (carousel.getState) {
const state = carousel.getState();
console.log("Current state:", state);
console.log("Configuration:", state.config);
console.log("Timeline properties:", {
duration: carousel.duration(),
progress: carousel.progress(),
paused: carousel.paused()
});
}
// Check if carousel is destroyed
const isDestroyed = carousel.paused() && !carousel.duration();
// Cleanup (important for memory management)
carousel.cleanup(); // Remove all listeners and animations
// Call when removing carousel from DOM
Advanced Timeline Manipulation
// Speed control
carousel.timeScale(0.5); // Play at half speed
carousel.timeScale(2); // Play at double speed
carousel.timeScale(1); // Reset to normal speed
// Event callbacks (GSAP timeline events)
carousel.eventCallback("onComplete", () => {
console.log("Loop cycle completed");
});
carousel.eventCallback("onUpdate", () => {
// Called continuously during animation
const progress = carousel.progress();
updateProgressBar(progress);
});
// Kill specific animations
gsap.killTweensOf(carousel); // Kill the main timeline
carousel.kill(); // Kill timeline and all associated animations
// Chain method calls
carousel
.pause()
.toIndex(0, { duration: 1 })
.play();
Available Methods & Callbacks:
play()
,
pause()
, reverse()
,
restart()
, seek()
,
timeScale()
, duration()
,
progress()
, eventCallback()
, and many
more.
📖 Complete Reference: GSAP Timeline Documentation - See all available methods, properties, and callbacks you can use with your carousel instance.
Events & Callbacks
The carousel provides comprehensive callback system for responding to user interactions and state changes. All callbacks receive detailed payload objects with relevant data.
onChange Callback
Fired when the active slide changes, providing comprehensive information about the current state:
const carousel = horizontalLoop("#carousel", {
onChange: (payload) => {
// Payload object contains:
console.log("Current slide element:", payload.currentItem); // HTMLElement
console.log("Current slide index:", payload.currentIndex); // 0-based index
console.log("Total slides:", payload.totalItems); // Total count
console.log("Animation progress:", payload.progress); // 0-1 progress
console.log("Current slide width:", payload.slideWidth); // Width in pixels
console.log("Timeline instance:", payload.timeline); // GSAP timeline
console.log("Configuration:", payload.config); // Current config
// Practical usage examples:
// Update slide counter UI
document.querySelector('.slide-counter').textContent =
`${payload.currentIndex + 1} / ${payload.totalItems}`;
// Update slide title
const title = payload.currentItem.querySelector('h3')?.textContent;
if (title) {
document.querySelector('.current-slide-title').textContent = title;
}
// Update progress bar
const progressPercent = (payload.currentIndex / (payload.totalItems - 1)) * 100;
document.querySelector('.progress-bar').style.width = `${progressPercent}%`;
// Analytics tracking
if (typeof gtag !== 'undefined') {
gtag('event', 'carousel_slide_change', {
slide_index: payload.currentIndex,
slide_title: title || `Slide ${payload.currentIndex + 1}`
});
}
// Conditional logic based on slide
if (payload.currentIndex === 0) {
document.body.classList.add('first-slide-active');
} else {
document.body.classList.remove('first-slide-active');
}
// Load lazy content for upcoming slides
const nextIndex = (payload.currentIndex + 1) % payload.totalItems;
const nextSlide = document.querySelectorAll('#carousel > *')[nextIndex];
loadLazyContent(nextSlide);
},
// Performance optimization: only fire after settling
updateOnlyOnSettle: true, // Reduces callback frequency during dragging
});
onInitialized Callback
Fired once after the carousel completes setup, ideal for initialization tasks:
const carousel = horizontalLoop("#carousel", {
onInitialized: (payload) => {
console.log("Carousel fully initialized!", payload);
// Hide loading spinner
document.querySelector('.carousel-loading')?.remove();
// Show carousel with fade-in effect
gsap.to("#carousel", {
opacity: 1,
duration: 0.5,
ease: "power2.out"
});
// Initialize related UI components
initializeCarouselControls(payload);
// Set up keyboard shortcuts
setupKeyboardShortcuts(payload.timeline);
// Start analytics session
trackCarouselSession({
totalSlides: payload.totalItems,
hasAutoplay: payload.config.autoplayDelay > 0,
hasDragging: payload.config.draggable
});
// Preload images in slides
preloadSlideImages(payload.timeline);
// Initialize intersection observer for performance
setupSlideVisibilityTracking();
}
});
GSAP Timeline Events
Since the carousel returns a GSAP Timeline, you can use all standard GSAP event callbacks:
// Set up GSAP timeline event callbacks
carousel.eventCallback("onStart", () => {
console.log("Carousel animation started");
document.body.classList.add('carousel-animating');
});
carousel.eventCallback("onUpdate", () => {
// Called on every frame during animation
const progress = carousel.progress();
// Update visual progress indicator
const indicator = document.querySelector('.carousel-progress');
if (indicator) {
indicator.style.transform = `scaleX(${progress})`;
}
// Parallax effect based on carousel progress
gsap.set('.background-element', {
x: progress * -100,
ease: "none"
});
});
carousel.eventCallback("onComplete", () => {
console.log("Carousel loop cycle completed");
// This fires after each complete loop when repeat is set
updateLoopCounter();
});
carousel.eventCallback("onReverseComplete", () => {
console.log("Reverse animation completed");
});
// Remove event callbacks
carousel.eventCallback("onUpdate", null); // Remove specific callback
carousel.eventCallback(null); // Remove all callbacks
Custom Event System
// Extend carousel with custom event system
const carouselEvents = new EventTarget();
const carousel = horizontalLoop("#carousel", {
onChange: (payload) => {
// Dispatch custom events
carouselEvents.dispatchEvent(new CustomEvent('slideChange', {
detail: payload
}));
// Dispatch specific events based on slide content
const slideType = payload.currentItem.dataset.type;
if (slideType) {
carouselEvents.dispatchEvent(new CustomEvent(`slide${slideType}Active`, {
detail: payload
}));
}
}
});
// Listen for custom events
carouselEvents.addEventListener('slideChange', (event) => {
const { currentIndex, currentItem } = event.detail;
console.log(`Slide changed to index ${currentIndex}`);
});
carouselEvents.addEventListener('slideVideoActive', (event) => {
// Handle video slides specifically
const videoElement = event.detail.currentItem.querySelector('video');
if (videoElement) {
videoElement.play();
}
});
carouselEvents.addEventListener('slideImageActive', (event) => {
// Handle image slides with specific logic
const img = event.detail.currentItem.querySelector('img');
if (img && !img.complete) {
// Show loading spinner for unloaded images
showImageLoading(img);
}
});
updateOnlyOnSettle: true
if your callback performs
expensive operations like DOM manipulation or API calls.
Important:
updateOnlyOnSettle
only
affects the onChange
callback defined in the carousel
configuration. If you need to receive every update while using
updateOnlyOnSettle
, you can use the timeline's native
eventCallback("onUpdate")
method instead:
carousel.eventCallback("onUpdate", () => { /* fires on every
frame */ });
Browser Support
Supported Browsers
Browser | Minimum Version | Features | Notes |
---|---|---|---|
Chrome | 60+ | All features | Full support including advanced dragging |
Firefox | 55+ | All features | Excellent performance |
Safari | 12+ | All features | iOS Safari 12+ also supported |
Edge | 79+ (Chromium) | All features | Legacy Edge not supported |
Chrome Mobile | 60+ | All features | Touch interactions work perfectly |
Samsung Internet | 8.2+ | Most features | Some advanced CSS features limited |
Required Browser Features
- ES6 Support: Arrow functions, const/let, template literals
- Modern DOM APIs: querySelector, addEventListener, classList
- CSS Features: CSS Custom Properties, Flexbox, Transform3d
- Optional APIs: ResizeObserver (graceful fallback), IntersectionObserver
Feature Detection
// The carousel automatically detects browser capabilities
function checkBrowserSupport() {
const checks = {
gsap: typeof gsap !== 'undefined',
es6: (() => {
try {
new Function('const x = () => {}; class Y {}');
return true;
} catch (e) {
return false;
}
})(),
customProperties: CSS.supports('--foo', 'red'),
resizeObserver: typeof ResizeObserver !== 'undefined',
draggable: typeof Draggable !== 'undefined',
intersectionObserver: typeof IntersectionObserver !== 'undefined'
};
console.log('Browser support check:', checks);
return checks;
}
// Initialize carousel with fallbacks
function initCarouselWithFallback() {
const support = checkBrowserSupport();
if (!support.gsap) {
console.error('GSAP not found - carousel cannot initialize');
showStaticFallback();
return;
}
if (!support.es6) {
console.warn('Limited ES6 support - some features may not work');
}
const config = {
draggable: support.draggable,
// Adjust config based on capabilities
};
return horizontalLoop('#carousel', config);
}
function showStaticFallback() {
// Show a basic CSS-only carousel or static grid
document.querySelector('.carousel-container').style.overflowX = 'auto';
document.querySelector('.carousel-container').style.scrollBehavior = 'smooth';
}
Troubleshooting
Common Issues and Solutions
- Check GSAP loading: Ensure GSAP 3.12+ loads before the carousel script
- Verify DOM structure: Container element must exist with child elements
- Console errors: Check browser console for JavaScript errors
- Element timing: Initialize after DOM is ready or in window load event
- Breakpoint order: Ensure responsive breakpoints are in ascending order (0, 640, 768, etc.)
- CSS conflicts: Check for conflicting CSS that overrides carousel flex properties
- ResizeObserver errors: Update to a browser that supports ResizeObserver or add a polyfill
-
Manual refresh: Call
carousel.refresh(true)
after dynamic content changes
- Plugin missing: Include GSAP Draggable plugin
-
Configuration: Set
draggable: true
in carousel options - InertiaPlugin: For momentum scrolling, include InertiaPlugin (requires Club GreenSock)
-
Touch conflicts: Check for CSS
touch-action
properties that might interfere
-
Heavy callbacks: Enable
updateOnlyOnSettle: true
for expensive onChange callbacks - Large datasets: Consider virtual scrolling for 50+ slides
- Image optimization: Use appropriate image sizes and lazy loading
-
Memory leaks: Always call
carousel.cleanup()
when destroying
-
Screen readers: Verify
accessibilityEnabled: true
(default) - Keyboard navigation: Test with Tab, Arrow keys, Enter, and Space
-
ARIA labels: Set meaningful
ariaLabel
in configuration - Focus management: Ensure buttons and slides receive proper focus
Debug Mode
Enable detailed logging to diagnose issues:
const carousel = horizontalLoop("#carousel", {
debug: true, // Enables detailed console logging
// ... other options
});
// Access debug information
if (carousel && carousel.getState) {
const debugInfo = carousel.getState();
console.log("Carousel state:", debugInfo);
// Log configuration
console.log("Configuration:", debugInfo.config);
// Log timeline properties
console.log("Timeline duration:", carousel.duration());
console.log("Current progress:", carousel.progress());
console.log("Is paused:", carousel.paused());
}
// Monitor carousel events
carousel.eventCallback("onUpdate", () => {
console.log("Update:", {
time: carousel.time(),
progress: carousel.progress(),
currentSlide: carousel.current()
});
});
Error Handling Patterns
function initializeCarouselSafely(selector, config = {}) {
try {
// Check prerequisites
if (typeof gsap === 'undefined') {
throw new Error('GSAP library not found');
}
if (gsap.version < '3.12') {
console.warn('GSAP version 3.12+ recommended for best compatibility');
}
// Check container element
const container = document.querySelector(selector);
if (!container) {
throw new Error(`Container element not found: ${selector}`);
}
const slides = container.children;
if (slides.length === 0) {
throw new Error('No slides found in container');
}
// Initialize with error boundaries
const carousel = horizontalLoop(selector, {
...config,
debug: process.env.NODE_ENV === 'development',
// Wrap callbacks with error handling
onChange: config.onChange ? (payload) => {
try {
config.onChange(payload);
} catch (error) {
console.error('Error in onChange callback:', error);
}
} : null,
onInitialized: (payload) => {
console.log('Carousel initialized successfully');
if (config.onInitialized) {
try {
config.onInitialized(payload);
} catch (error) {
console.error('Error in onInitialized callback:', error);
}
}
}
});
if (!carousel) {
throw new Error('Carousel initialization failed');
}
// Set up error recovery
window.addEventListener('error', (event) => {
if (event.filename?.includes('gsap') || event.message?.includes('carousel')) {
console.error('Carousel-related error detected:', event.error);
// Could trigger fallback UI here
}
});
return carousel;
} catch (error) {
console.error('Failed to initialize carousel:', error);
// Show fallback UI
showCarouselFallback(selector);
return null;
}
}
function showCarouselFallback(selector) {
const container = document.querySelector(selector);
if (container) {
// Convert to simple scrollable container
container.style.overflowX = 'auto';
container.style.display = 'flex';
container.style.gap = '1rem';
// Add scroll buttons if needed
const wrapper = container.parentElement;
if (wrapper) {
wrapper.insertAdjacentHTML('beforeend', `
Interactive carousel unavailable. Use scroll or swipe to navigate.
`);
}
}
}
Common Console Warnings
* { resize: none; }
Performance Tips
Optimization Strategies
-
Use
updateOnlyOnSettle
for expensive onChange callbacks to reduce execution frequency during dragging and animations -
Enable
debug: false
in production to eliminate console logging overhead -
Call
cleanup()
when removing carousels from the DOM to prevent memory leaks -
Optimize slide content by using CSS
will-change
property sparingly and only when needed - Implement lazy loading for images and heavy content in slides
-
Use
transform3d
for hardware acceleration in custom animations - Debounce resize handlers when implementing custom responsive logic
- Minimize DOM queries in onChange callbacks by caching element references
Performance Monitoring
// Performance monitoring utility
class CarouselPerformanceMonitor {
constructor(carousel) {
this.carousel = carousel;
this.metrics = {
initTime: 0,
frameCount: 0,
lastFrameTime: 0,
averageFPS: 0,
memoryUsage: []
};
this.startMonitoring();
}
startMonitoring() {
const startTime = performance.now();
// Monitor initialization time
if (this.carousel.eventCallback) {
this.carousel.eventCallback('onUpdate', () => {
if (!this.metrics.initTime) {
this.metrics.initTime = performance.now() - startTime;
console.log(`Carousel first frame: ${this.metrics.initTime.toFixed(2)}ms`);
}
this.trackFrameRate();
this.trackMemoryUsage();
});
}
}
trackFrameRate() {
const now = performance.now();
if (this.metrics.lastFrameTime) {
const delta = now - this.metrics.lastFrameTime;
this.metrics.frameCount++;
// Calculate rolling average FPS
const fps = 1000 / delta;
this.metrics.averageFPS = (this.metrics.averageFPS + fps) / 2;
// Warn if FPS drops below 30
if (fps < 30 && this.metrics.frameCount > 10) {
console.warn(`Low FPS detected: ${fps.toFixed(1)} fps`);
}
}
this.metrics.lastFrameTime = now;
}
trackMemoryUsage() {
if (performance.memory) {
const usage = {
used: performance.memory.usedJSHeapSize,
total: performance.memory.totalJSHeapSize,
limit: performance.memory.jsHeapSizeLimit,
timestamp: Date.now()
};
this.metrics.memoryUsage.push(usage);
// Keep only last 50 measurements
if (this.metrics.memoryUsage.length > 50) {
this.metrics.memoryUsage.shift();
}
// Check for potential memory leaks
if (this.metrics.memoryUsage.length >= 20) {
const recent = this.metrics.memoryUsage.slice(-10);
const older = this.metrics.memoryUsage.slice(-20, -10);
const recentAvg = recent.reduce((sum, m) => sum + m.used, 0) / recent.length;
const olderAvg = older.reduce((sum, m) => sum + m.used, 0) / older.length;
// If memory usage consistently growing
if (recentAvg > olderAvg * 1.1) {
console.warn('Potential memory leak detected in carousel');
}
}
}
}
getReport() {
return {
...this.metrics,
recommendations: this.generateRecommendations()
};
}
generateRecommendations() {
const recommendations = [];
if (this.metrics.averageFPS < 45) {
recommendations.push('Consider enabling updateOnlyOnSettle for better performance');
}
if (this.metrics.initTime > 100) {
recommendations.push('Consider lazy loading slide content');
}
const latestMemory = this.metrics.memoryUsage[this.metrics.memoryUsage.length - 1];
if (latestMemory && latestMemory.used > 50 * 1024 * 1024) { // 50MB
recommendations.push('High memory usage detected - ensure cleanup() is called');
}
return recommendations;
}
}
// Usage
const carousel = horizontalLoop('#carousel', config);
const monitor = new CarouselPerformanceMonitor(carousel);
// Get performance report
setTimeout(() => {
console.log('Performance Report:', monitor.getReport());
}, 5000);
Memory Management Best Practices
// Carousel lifecycle manager
class CarouselManager {
constructor() {
this.carousels = new Map();
this.setupGlobalCleanup();
}
create(selector, config) {
const carousel = horizontalLoop(selector, {
...config,
debug: false, // Always disable in production
// Wrap onChange to prevent memory leaks
onChange: config.onChange ? (payload) => {
// Use WeakRef if available for payload references
if (typeof WeakRef !== 'undefined') {
const weakPayload = {
...payload,
currentItem: new WeakRef(payload.currentItem),
timeline: new WeakRef(payload.timeline)
};
config.onChange(weakPayload);
} else {
config.onChange(payload);
}
} : null
});
if (carousel) {
this.carousels.set(selector, carousel);
}
return carousel;
}
destroy(selector) {
const carousel = this.carousels.get(selector);
if (carousel) {
carousel.cleanup();
this.carousels.delete(selector);
}
}
destroyAll() {
this.carousels.forEach((carousel, selector) => {
carousel.cleanup();
});
this.carousels.clear();
}
setupGlobalCleanup() {
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
this.destroyAll();
});
// Cleanup on page visibility change (mobile)
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
// Pause all carousels to save resources
this.carousels.forEach(carousel => {
carousel.pauseAutoplay();
});
}
});
}
}
// Global manager instance
const carouselManager = new CarouselManager();
// Usage in your application
const carousel = carouselManager.create('#carousel', {
autoplayDelay: 3,
draggable: true
});
// Clean up when component unmounts
function cleanupComponent() {
carouselManager.destroy('#carousel');
}
Production Optimization Checklist
-
✓ Set
debug: false
in production builds -
✓ Use
updateOnlyOnSettle: true
for expensive callbacks - ✓ Implement proper cleanup in component lifecycle methods
- ✓ Lazy load images and heavy content in slides
- ✓ Minimize onChange callback complexity and use debouncing for heavy operations
- ✓ Test on low-end devices and slow networks
- ✓ Monitor bundle size and consider code splitting for large carousels
- ✓ Use intersection observers for visibility-based optimizations