React hook / Vanilla JS to manage scroll-state for CSS Scroll Snap
Gives you states and actions for CSS Scroll Snap sliders:
count:number
- total pagescountDelta:number
- total slides, for 1/1 width slides the same as countindex:number
- current page indexindexDelto:number
- current slide index, for 1/1 width slides the same as indexprevEnabled:boolean
- can go to previous slidenextEnabled:boolean
- can go to next slidegoPrev()
- scroll to previous slidegoNext()
- scroll to next slidejumpTo(index:number)
- go to a spesified slide
npm i use-snap-slider
In your react component:
See a more complete react-example here
import React, { useRef } from 'react'
import { useSnapSlider } from 'use-snap-slider'
export function MySlider () {
const ref = useRef<HTMLDivElement | null>(null)
const slides = [1,2]
// Passing inital count avoid extra render, for best lighthouse score, pass the same count as you show on mobile
const {index, count, jumpTo, goPrev, goNext, prevEnabled, nextEnabled} = useSnapSlider(ref, slides.length)
const pages = Array.from(Array(count).keys())
return (
<div>
<div className="flex scroll-smooth snap-x snap-mandatory overflow-x-auto" ref={ref}>
{slides.map((slide) => (
<div key={slide} className="flex snap-start shrink-0 w-full md:w-1/2">
Slide {slide}
</div>
))}
</div>
<nav aria-label="Pages navigation">
{pages.map((page) => (
<button onClick={() => jumpTo(page)} disabled={page === index}>{page + 1}</button>
))}
</nav>
<nav aria-label="Prev / Next navigation">
<button onClick={goPrev} disabled={!prevEnabled}>Prev</button>
<button onClick={goNext} disabled={!nextEnabled}>Next</button>
</nav>
</div>
)
}
See a more complete vanilla javascript example here
import { createSnapSlider } from 'use-snap-slider/dist/snap-slider'
function createSnapSliderVanilla(element: HTMLElement) {
const { jumpTo, goNext, goPrev, subscribe } = createSnapSlider({
element,
count: count,
index: 0,
})
subscribe((state) => {
// Update UI with sate
})
document.querySelector('.prev-btn')?.addEventListener('click', goPrev)
document.querySelector('.next-btn')?.addEventListener('click', goNext)
}
// Expose globally
window.createSnapSliderVanilla = createSnapSliderVanilla
import { useSnapSlider } from 'use-snap-slider'
const {
index: number,
// If displaying multiple slides on the same page, this will be slide at left position
indexDelta: number,
// Count of pages
count: number,
countDelta: number,
prevEnabled: boolean,
nextEnabled: boolean,
jumpTo: (index: number) => void,
goNext: () => void,
goPrev: () => void,
} = useSnapSlider(
ref: MutableRefObject<HTMLDivElement | null>,
// Pass inital count
count?: number = 1,
// Pass inital index
index?: number = 0,
// onPrev / next buttons go to end / start
circular = false,
// Will reset index on change of count, but pass something here to force a reset even if count dont change
countHash?: string | number
)
import { createSnapSlider } from 'use-snap-slider/dist/snap-slider'
const {
// Removes event listner for window.resize and element.scroll
destroy: () => void,
// Get current state
getState: () => TSnapSliderStateFull,
// Go to slide index
jumpTo: (index?: number, indexDelta?: number) => void,
goNext: () => void,
goPrev: () => void,
// Subscribe to updates
subscribe: (fn: TSnapListner) => () => void,
// Set element at later stage
setElement: (el: HTMLElement) => void,
// Updates count and countDelta, call if you change inner slides
calculate: () => void,
// Should subscribe return a inital publish after subscribing
initalSubscriptionPublish: boolean = true
} = createSnapSlider({
element: HTMLDivElement | null,
count?:number = 1,
countDelta?:number,
index?:number = 0,
circular?:boolean,
indexDelta?:number,
itemSelector?:string = ':scope > *',
})
This library only delivers javascript for handle the state, you will need to make your own component for complete solution. See examples in this repo:
See also tailwinds own documentation for scroll snap
<div class="flex scroll-smooth snap-x snap-mandatory overflow-x-auto">
<div class="flex snap-start shrink-0">
Slide 1
</div>
<div class="flex snap-start shrink-0">
Slide 2
</div>
</div>
<style>
.css-slider {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
}
.css-slider::-webkit-scrollbar {
display: none;
}
.css-slider-item {
display: flex;
scroll-snap-align: start;
flex-shrink: 0;
}
.css-slider-item-half {
width: 50%;
}
</style>
<!-- Markup -->
<div class="css-slider">
<div class="css-slider-item css-slider-item-half">
Slide 1
</div>
<div class="css-slider-item css-slider-item-half">
Slide 2
</div>
</div>