MerryGo - Complete Documentation
MerryGo is a highly customizable JavaScript carousel that supports multiple slides, drag & swipe, autoplay, navigation arrows, pagination, and responsive breakpoints.
Key Features
- ✅ Infinite loop or limited navigation
- ✅ Drag & swipe (mouse and touch)
- ✅ Configurable autoplay
- ✅ Arrow and pagination navigation
- ✅ Responsive breakpoints
- ✅ Horizontal or vertical orientation
- ✅ Multiple visible slides
- ✅ Automatic cloning for infinite loop
Installation
Via CDN
<script src="https://cdn.jsdelivr.net/npm/merrygo-carousel@1.0.7/dist/merrygo.js"></script>
Via NPM
npm install merrygo-carousel
import MerryGo from 'merrygo-carousel';
HTML Structure
Minimum Structure
<div class="carousel">
<div class="carousel__inner">
<div class="carousel__slide">Slide 1</div>
<div class="carousel__slide">Slide 2</div>
<div class="carousel__slide">Slide 3</div>
</div>
</div>
Complete Structure (with navigation and pagination)
<div class="carousel-container">
<!-- Gallery -->
<div class="carousel">
<div class="carousel__inner">
<div class="carousel__slide">Slide 1</div>
<div class="carousel__slide">Slide 2</div>
<div class="carousel__slide">Slide 3</div>
</div>
</div>
<!-- Navigation arrows -->
<button class="carousel__arrow carousel__arrow--prev">←</button>
<button class="carousel__arrow carousel__arrow--next">→</button>
<!-- Pagination -->
<div class="carousel__pagination"></div>
</div>
Initialization
Basic Initialization
const carousel = new MerryGo({
gallery: document.querySelector('.carousel'),
galleryInner: document.querySelector('.carousel__inner')
});
Complete Initialization
const carousel = new MerryGo({
gallery: document.querySelector('.carousel'),
galleryInner: document.querySelector('.carousel__inner'),
prevBtn: document.querySelector('.carousel__arrow--prev'),
nextBtn: document.querySelector('.carousel__arrow--next'),
pagination: document.querySelector('.carousel__pagination'),
gap: 20,
slidesVisible: 1,
slidesToScroll: 1,
infinityLoop: true,
autoplay: 5000,
enableDrag: true,
orientation: 'horizontal'
});
// ⚠️ IMPORTANT: Save reference for destruction in PWA/SPA
window.activeCarousels = window.activeCarousels || [];
window.activeCarousels.push(carousel);
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
gallery |
Element | required | Main carousel container |
galleryInner |
Element | required | Slides container |
prevBtn |
Element | null |
Previous button |
nextBtn |
Element | null |
Next button |
pagination |
Element | null |
Pagination container |
thumbs |
Array | [] |
Array of radio inputs for thumbnails |
gap |
Number | 0 |
Space between slides (px) |
slidesVisible |
Number | 1 |
Number of visible slides |
slidesToScroll |
Number | 1 |
Number of slides to scroll |
infinityLoop |
Boolean | true |
Enable infinite loop |
autoplay |
Number/Boolean | false |
Autoplay interval (ms) |
enableDrag |
Boolean | true |
Enable drag/swipe |
orientation |
String | 'horizontal' |
'horizontal' or 'vertical' |
breakpoints |
Object | null |
Responsive configuration |
Responsive Breakpoints
Breakpoints allow you to adjust carousel behavior at different screen sizes.
Structure
breakpoints: {
[minWidth]: {
slidesVisible: Number,
slidesToScroll: Number,
gap: Number,
infinityLoop: Boolean,
autoplay: Number/Boolean
}
}
Practical Example
const carousel = new MerryGo({
gallery: document.querySelector('.carousel'),
galleryInner: document.querySelector('.carousel__inner'),
breakpoints: {
0: {
slidesVisible: 1,
gap: 0,
infinityLoop: true
},
561: {
slidesVisible: 2,
gap: 15
},
769: {
slidesVisible: 3,
gap: 15
},
1025: {
slidesVisible: 4,
gap: 30,
infinityLoop: true
}
}
});
How it works:
- - The carousel applies the configuration from the closest breakpoint ≤ current width
- - Example: at 800px width, applies settings from
769 - - At 400px, applies settings from
0
CSS Styling
Required Base CSS
/* Main container */
.carousel {
width: 100%;
overflow: hidden;
user-select: none;
}
/* Slides container */
.carousel__inner {
display: flex;
gap: 30px; /* Must match JavaScript gap */
transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
will-change: transform;
}
/* Individual slides */
.carousel__slide {
flex-shrink: 0;
box-sizing: border-box;
/* Width is calculated dynamically by JS */
}
Slide Width Calculation
Why calculate width?
The carousel uses display: flex and controls slide position via transform. For this to work, each slide needs a fixed width defined in CSS. This width must account for how many slides are visible at once and the gap between them.
The Formula:
slide width = (100% - (gap × (slidesVisible - 1))) / slidesVisible
Practical example: For 4 visible slides with 30px gap:
- Total gaps = 30px × (4 - 1) = 90px
- Available space for slides = 100% - 90px
- Width of each slide = (100% - 90px) / 4
.carousel__slide {
width: calc((100% - 90px) / 4);
}
Calculation Examples
| Visible Slides | Gap | CSS Formula |
|---|---|---|
| 1 | 0px | width: 100% |
| 2 | 15px | width: calc((100% - 15px) / 2) |
| 3 | 15px | width: calc((100% - 30px) / 3) |
| 4 | 30px | width: calc((100% - 90px) / 4) |
| 6 | 16px | width: calc((100% - 80px) / 6) |
Complete Example with Navigation
.carousel-container {
position: relative;
padding: 40px 0;
}
.carousel {
width: 100%;
overflow: hidden;
user-select: none;
}
.carousel__inner {
display: flex;
gap: 30px;
transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
will-change: transform;
}
.carousel__slide {
flex-shrink: 0;
width: calc((100% - 90px) / 4);
box-sizing: border-box;
}
/* Navigation arrows */
.carousel__arrow {
width: 50px;
height: 50px;
border-radius: 4px;
background-color: #fff;
border: 1px solid #e0e0e0;
position: absolute;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 10;
transition: opacity 0.3s ease;
}
.carousel__arrow--prev {
left: -56px;
}
.carousel__arrow--next {
right: -56px;
}
.carousel__arrow:hover {
background-color: #f5f5f5;
}
/* Pagination */
.carousel__pagination {
display: flex;
gap: 8px;
justify-content: center;
margin-top: 24px;
}
.merrygo-bullet {
width: 16px;
height: 16px;
border: 1px solid #e0e0e0;
border-radius: 50%;
background-color: #fff;
cursor: pointer;
transition: all 0.3s ease;
}
.merrygo-bullet-active {
background-color: #007bff;
border-color: #007bff;
}
/* Responsive */
@media (max-width: 1024px) {
.carousel__inner {
gap: 15px;
}
.carousel__slide {
width: calc((100% - 30px) / 3);
}
.carousel__arrow {
display: none;
}
}
@media (max-width: 768px) {
.carousel__slide {
width: calc((100% - 15px) / 2);
}
}
@media (max-width: 560px) {
.carousel__inner {
gap: 0;
}
.carousel__slide {
width: 100%;
}
}
Public Methods
goToSlide(index)
Navigate to a specific slide.
carousel.goToSlide(2); // Go to the third slide (index 2)
nextSlide()
Advance to the next slide.
carousel.nextSlide();
prevSlide()
Go back to the previous slide.
carousel.prevSlide();
refresh()
Reinitialize the carousel (useful after DOM changes).
carousel.refresh();
destroy()
Remove all event listeners and clean up the carousel.
carousel.destroy();
startAutoplay() / stopAutoplay()
Manually control autoplay.
carousel.startAutoplay();
carousel.stopAutoplay();
Practical Examples
1. Simple Banner Carousel
<div class="main-banner">
<div class="main-banner__inner">
<div class="main-banner__slide">
<img src="banner1.jpg" alt="Banner 1">
</div>
<div class="main-banner__slide">
<img src="banner2.jpg" alt="Banner 2">
</div>
</div>
</div>
<button class="main-banner__prev">←</button>
<button class="main-banner__next">→</button>
const bannerCarousel = new MerryGo({
gallery: document.querySelector('.main-banner'),
galleryInner: document.querySelector('.main-banner__inner'),
prevBtn: document.querySelector('.main-banner__prev'),
nextBtn: document.querySelector('.main-banner__next'),
gap: 0,
autoplay: 7000,
infinityLoop: true
});
.main-banner {
width: 100%;
overflow: hidden;
}
.main-banner__inner {
display: flex;
gap: 0;
transition: transform 0.4s ease;
}
.main-banner__slide {
flex-shrink: 0;
width: 100%;
}
.main-banner__slide img {
width: 100%;
height: auto;
display: block;
}
.main-banner__arrow {
background-color: #fff;
width: 44px;
height: 44px;
border-radius: 800px;
border: 1px solid #E6E6E6;
box-shadow: 0px 1px 8px 0px #0000001F;
position: absolute;
top: 50%;
transform: translateY(-50%);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.main-banner__arrow svg {
width: 16px;
height: 16px;
}
.main-banner__arrow--prev {
left: 15px;
}
.main-banner__arrow--next {
right: 15px;
}
2. Product Gallery (4 visible)
const productsCarousel = new MerryGo({
gallery: document.querySelector('.products-carousel'),
galleryInner: document.querySelector('.products-carousel__inner'),
prevBtn: document.querySelector('.products-carousel__prev'),
nextBtn: document.querySelector('.products-carousel__next'),
pagination: document.querySelector('.products-carousel__pagination'),
gap: 20,
slidesVisible: 4,
infinityLoop: false,
breakpoints: {
0: { slidesVisible: 1, gap: 0 },
561: { slidesVisible: 2, gap: 15 },
769: { slidesVisible: 3, gap: 15 },
1025: { slidesVisible: 4, gap: 20 }
}
});
const productsCarousel = new MerryGo({
gallery: document.querySelector('.products-carousel'),
galleryInner: document.querySelector('.products-carousel__inner'),
prevBtn: document.querySelector('.products-carousel__prev'),
nextBtn: document.querySelector('.products-carousel__next'),
pagination: document.querySelector('.products-carousel__pagination'),
gap: 20,
slidesVisible: 4,
slidesToScroll: 4,
infinityLoop: false,
breakpoints: {
0: { slidesVisible: 1, gap: 0 },
561: { slidesVisible: 2, gap: 15 },
769: { slidesVisible: 3, gap: 15 },
1025: { slidesVisible: 4, gap: 20 }
}
});
window.activeCarousels = window.activeCarousels || [];
window.activeCarousels.push(productsCarousel);
.products-carousel {
overflow: hidden;
}
.products-carousel__inner {
display: flex;
gap: 20px;
transition: transform 0.4s ease;
}
.products-carousel__slide {
width: calc((100% - 60px) / 4);
flex-shrink: 0;
box-sizing: border-box;
}
.products-carousel__arrow {
background-color: #fff;
width: 44px;
height: 44px;
border-radius: 800px;
border: 1px solid #E6E6E6;
box-shadow: 0px 1px 8px 0px #0000001F;
position: absolute;
top: 50%;
transform: translateY(-50%);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.products-carousel__arrow svg {
width: 16px;
height: 16px;
}
.products-carousel__arrow--prev {
left: 15px;
}
.products-carousel__arrow--next {
right: 15px;
}
.products-carousel__pagination {
width: max-content;
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: 8px;
height: 20px;
margin-inline: auto;
}
.products-carousel__pagination .merrygo-bullet {
width: 16px;
min-width: 16px;
height: 16px;
border: 1px solid #e6e6e6;
border-radius: 100%;
transition: all .3s ease;
background-color: #ffffff;
cursor: pointer;
}
.products-carousel__pagination .merrygo-bullet-active {
background-color: #000000;
border-color: #000000;
}
@media (max-width: 1024px) {
.products-carousel__inner {
gap: 15px;
}
.products-carousel__slide {
width: calc((100% - 30px) / 3);
}
}
@media (max-width: 768px) {
.products-carousel__slide {
width: calc((100% - 15px) / 2);
}
}
@media (max-width: 560px) {
.products-carousel__inner {
gap: 0;
}
.products-carousel__slide {
width: 100%;
}
}
3. Gallery with Thumbnails
const thumbInputs = Array.from(
document.querySelectorAll('.thumbnails input[type="radio"]')
);
const productGallery = new MerryGo({
gallery: document.querySelector('.gallery'),
galleryInner: document.querySelector('.gallery__inner'),
thumbs: thumbInputs,
gap: 0,
infinityLoop: true
});
const thumbInputs = Array.from(
document.querySelectorAll('.product-gallery__thumbnails input[type="radio"]')
);
const productGallery = new MerryGo({
gallery: document.querySelector('.product-gallery__main'),
galleryInner: document.querySelector('.product-gallery__inner'),
thumbs: thumbInputs,
gap: 0,
infinityLoop: true
});
window.activeCarousels = window.activeCarousels || [];
window.activeCarousels.push(productGallery);
4. Multiple Carousels on Same Page
window.activeCarousels = window.activeCarousels || [];
const showcases = document.querySelectorAll('[data-carousel]');
showcases.forEach(showcase => {
const carousel = new MerryGo({
gallery: showcase.querySelector('.carousel'),
galleryInner: showcase.querySelector('.carousel__inner'),
prevBtn: showcase.querySelector('.carousel__prev'),
nextBtn: showcase.querySelector('.carousel__next'),
pagination: showcase.querySelector('.carousel__pagination'),
breakpoints: {
0: { slidesVisible: 1, gap: 0 },
561: { slidesVisible: 2, gap: 15 },
769: { slidesVisible: 3, gap: 15 },
1025: { slidesVisible: 4, gap: 30 }
}
});
window.activeCarousels.push(carousel);
});
Best Practices
1. CSS-JS Synchronization
The CSS gap must be identical to the JavaScript gap:
// JavaScript
gap: 30
// CSS
.carousel__inner {
gap: 30px;
}
2. Width Calculation
Use the correct formula for each breakpoint:
width = (100% - (gap × (slidesVisible - 1))) / slidesVisible
3. Instance Management
Save carousel references to destroy them when needed:
⚠️ CRITICAL: Destruction in PWA/SPA
PWA or Single Page Applications require special attention!
When users navigate between pages without reloading the browser, previous carousels remain in memory. This causes:
- ❌ Memory leaks
- ❌ Duplicate event listeners
- ❌ Unexpected carousel behavior
- ❌ Progressive performance degradation
Mandatory solution: Destroy all carousels before navigating to a new page.
Implementation Example:
window.activeCarousels = window.activeCarousels || [];
window.onNavigate = (oldHref, newHref, state) => {
const oldPath = oldHref.split('?')[0];
const newPath = newHref.split('?')[0];
if (oldPath !== newPath) {
if (state === 'start') {
// CRITICAL: Destroy all carousels
if (window.activeCarousels && window.activeCarousels.length > 0) {
window.activeCarousels.forEach(carousel => {
if (carousel && typeof carousel.destroy === 'function') {
carousel.destroy();
}
});
window.activeCarousels = [];
}
}
}
}
Destruction Checklist:
- ✅ Always destroy before navigating
- ✅ Check if destroy method exists
(typeof carousel.destroy === 'function') - ✅ Clear array after destruction
(window.activeCarousels = []) - ✅ Do this in
state === 'start'event (before loading new page)
4. Performance
- Use
will-change: transformon the slides container - Use
loading="lazy"on images outside the first slide - Avoid transitions on heavy elements
5. Accessibility
- Add
aria-labelto navigation buttons - Use descriptive
alttext on images - Ensure adequate contrast on pagination bullets
Browser Support
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
- iOS Safari 10+
- Android Chrome
Troubleshooting
Slides not displaying correctly
Make sure:
- - CSS
gapmatches JavaScriptgap - - Slide width is calculated correctly with
calc() - - Container has
overflow: hidden
Drag not working
Check that:
- -
enableDragis not set tofalse - - Container doesn't have conflicting event listeners
- - Touch events aren't being prevented elsewhere
Loop not working
Verify:
- -
infinityLoopis set totrue - - You have more than 1 slide
- - Clones are being created (check DOM inspector)
Made with ❤️ inspired by the joy of merry-go-rounds 🎠
Licensed under MIT • View on GitHub