Lightweight scroll snap slider with smooth navigation for touch devices, mouse dragging, and trackpad swiping. It includes accessible controls, dynamic height adjustment, and customizable autoplay.
- Native Scroll Snap: Provides a smooth, natural scrolling experience on all touch devices.
- Intuitive Controls: Built-in dots and arrows for easy navigation.
- Dynamic Height Adjustment: Automatically adapts to the height of visible slides, ensuring seamless transitions.
- Customizable Autoplay: Set your preferred interval for automatic sliding.
- Have a Unique Requirement? Open an issue, and we'll explore adding it!
Check out the live demo of Snapgrab in action:
Snapgrab Demo
To install Snapgrab, use npm:
npm install snapgrab
Add the following HTML structure to your project:
<div class="slider" role="region" id="sliderControls">
<ul class="slider__wrapper" aria-live="polite" data-ref="wrapper">
<li class="slider__slide" aria-hidden="false" aria-current="true">Slide 1</li>
<li class="slider__slide" aria-hidden="true">Slide 2</li>
<li class="slider__slide" aria-hidden="true">Slide 3</li>
<li class="slider__slide" aria-hidden="true">Slide 4</li>
</ul>
<div class="slider__buttons">
<button data-ref="prev" aria-label="Previous slide" aria-controls="sliderControls"></button>
<button data-ref="next" aria-label="Next slide" aria-controls="sliderControls"></button>
</div>
<div class="slider__dots" data-ref="dots" aria-label="Dots"></div>
</div>
Import and initialize Snapgrab in your JavaScript file:
import { Snapgrab } from 'snapgrab'
const slider = new Snapgrab(document.querySelector('.slider'), {
autoplay: 6000,
autoplayStopOnInteraction: true,
autoheight: true,
})
slider.init()
Add some basic CSS to style the component:
.slider {
position: relative;
&__wrapper {
display: flex;
margin-bottom: 24px;
scroll-behavior: smooth;
scroll-snap-stop: always;
scroll-snap-type: x mandatory;
touch-action: pan-x pan-y;
overflow: scroll hidden;
transition: height 0.4s cubic-bezier(0.19, 1, 0.22, 1);
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
&__slide {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
scroll-snap-align: start;
scroll-snap-stop: normal;
padding: 24px;
margin-right: 24px;
height: max-content;
min-height: 200px;
min-width: 100%;
font-size: 24px;
font-weight: 500;
text-align: center;
user-select: none;
background: #bdc3c7;
border-radius: 10px;
// Layout shift fix
&:not(&:first-of-type) {
&:not(.is-loaded &) {
min-height: 0;
}
}
&:nth-child(even) {
min-height: 250px;
background-color: #95a5a6;
}
}
&__buttons {
display: flex;
justify-content: space-between;
width: 100%;
gap: 32px;
position: absolute;
right: 0;
bottom: -48px;
button {
display: block;
height: 40px;
width: 40px;
border-radius: 50%;
background-color: white;
cursor: pointer;
border: 1px solid #eaecf0;
transition: background 0.6s cubic-bezier(0.19, 1, 0.22, 1);
color: rgba(0, 0, 0, 60%);
&:after {
content: '';
transition: color 0.6s cubic-bezier(0.19, 1, 0.22, 1);
font-size: 2rem;
}
&[data-ref='prev'] {
&:after {
content: '←';
}
}
&[data-ref='next'] {
&:after {
content: '→';
}
}
@media (pointer: fine) {
&:hover {
&:not([disabled]) {
background: #eaecf0;
}
}
}
&[disabled] {
cursor: default;
&:after,
&:before {
color: rgba(0, 0, 0, 10%);
}
}
}
}
&__dots {
position: absolute;
left: 50%;
transform: translate(-50%);
display: flex;
justify-content: center;
gap: 8px;
button {
display: block;
height: 8px;
width: 8px;
border-radius: 50%;
cursor: pointer;
background: rgba(0, 0, 0, 42%);
transition: background 0.6s cubic-bezier(0.19, 1, 0.22, 1);
&:hover {
background: rgba(0, 0, 0, 54%);
}
&.is-active {
background: rgba(0, 0, 0, 80%);
}
}
}
.no-more-right {
animation: shake 0.5s ease;
}
.no-more-left {
animation: shake-left 0.5s ease;
}
@keyframes shake {
0%,
100% {
transform: translateX(0);
}
25% {
transform: translateX(-5px);
}
75% {
transform: translateX(5px);
}
}
@keyframes shake-left {
0%,
100% {
transform: translateX(0);
}
25% {
transform: translateX(5px);
}
75% {
transform: translateX(-5px);
}
}
}
Snapgrab accepts an optional configuration object. Below are the available options:
-
autoplay (number)
: Interval in milliseconds for autoplay. If not provided, autoplay is disabled. -
autoplayStopOnInteraction (boolean)
: Whether to stop autoplay on any user interaction. -
autoheight (boolean)
: Dynamically adjusts the slider height based on visible slides.
-
init()
: Initializes the Snapgrab component and binds all necessary event listeners. -
destroy()
: Unbinds event listeners and cleans up the component. -
goToSlide(index)
: Scrolls to a specific slide by its index. -
updateButtonState()
: Updates the state of the navigation buttons based on the current scroll position.
The handleHeight()
method dynamically recalculates and adjusts the height of the slider based on the current visible slides. This method can be called again if needed to ensure the height is updated correctly. For instance, you may want to call handleHeight()
after a specific event like a window resize, a slide change, or any dynamic content update that affects the height of the slider.
// Assuming `slider` is an instance of Snapgrab
slider.handleHeight() // Call this method to adjust the height manually
Snapgrab emits custom events during its lifecycle:
-
slideChange
: Dispatched when the current slide changes. It includes a detail object with the current slide index.
snapgrab.wrapper.addEventListener('slideChange', (e) => {
console.log('Current slide index:', e.detail.slideIndex)
})
Contributions are welcome! Please follow these steps:
- Fork the repository.
- Create your feature branch (
git checkout -b feature/AmazingFeature
). - Commit your changes (
git commit -m 'Add some AmazingFeature'
). - Push to the branch (
git push origin feature/AmazingFeature
). - Open a pull request.
This project is licensed under the MIT License - see the LICENSE file for details.