I'm building a react carousel and have the basic app set up. It's a slider that follows your finger on mobile, basically a flexbox with overflow: scroll. The buttons work by scrolling the container by (item index * the width of the item) times, so if I scroll to item #3, it will scroll by 300% the width of the item from the starting position.
Here's the code:
function Carousel(props) {
const {children} = props
const [index, setIndex] = useState(0)
const [length, setLength] = useState(children.length)
const [touchPosition, setTouchPosition] = useState(null)
const [movement, setMovement] = useState(0) // saves the distance swiped
useEffect(() => {
setLength(children.length)
}, [children])
const next = () => {
if (index < (length-1)) {
setIndex(prevState => prevState + 1)
}
// sets the index to next if you are not on the last slide
}
const previous = () => {
if (index > 0) {
setIndex(prevState => prevState - 1)
}
// sets the index to be the previous if you are further than first slide
}
const handleTouchStart = (e) => {
const touchDown = e.touches[0].clientX
setTouchPosition(touchDown)
// saves the touch position on touch start
}
const handleTouchEnd = (e) => {
const touchDown = touchPosition
if (touchDown === null) {
return
}
const currentTouch = e.changedTouches[0].clientX
const diff = touchDown - currentTouch
console.log(diff) // for testing
setMovement(diff)
if (diff > 5) {
setTimeout(() => next(), 100)
}
if (diff < -5) {
setTimeout(() => previous(), 100)
}
setTouchPosition(null)
// compares the starting and ending touch positions, calculates the difference
}
// to test the distance swiped
const transformMultiplier =() => {
let mu = (movement / window.innerWidth) * 100
console.log(`m = ${mu}`)
}
useEffect(()=>{
transformMultiplier()
}, [movement])
return (
<div className="carousel-container">
<div className="carousel-wrapper"
onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd}
>
{ index > 0 && <button className="left-arrow" onClick={previous}>
<
</button> }
<div className="carousel-content-wrapper">
{ index < children.length -1 && <button className="right-arrow" onClick={next}>
>
</button>}
{/* the sliding is done by the translateX below. it translates by (100% of the slides * the index of the slide) from the starting position. */}
<div className="carousel-content"
style={{transform: `translateX(-${index * (window.innerWidth > 480 ? 100 : (100 - (movement / window.innerWidth) * 100))}%)`}}
>
{ children}
</div>
</div>
</div>
</div>
)
}
.carousel-container {
width: 100vw;
display: flex;
flex-direction: column;
}
.carousel-wrapper {
display: flex;
width: 100%;
position: relative;
}
.carousel-content-wrapper {
overflow: scroll;
-webkit-overflow-scrolling: auto;
width: 100%;
height: 100%;
-ms-overflow-style: none; /* hide scrollbar in IE and Edge */
scrollbar-width: none; /* hide scrollbar in Firefox */
}
.carousel-content {
display: flex;
transition: all 250ms linear;
-ms-overflow-style: none; /* hide scrollbar in IE and Edge */
scrollbar-width: none; /* hide scrollbar in Firefox */
}
.carousel-content::-webkit-scrollbar, .carousel-content::-webkit-scrollbar {
display: none;
}
.carousel-content-wrapper::-webkit-scrollbar, .carousel-content::-webkit-scrollbar {
display: none;
}
.carousel-content > * {
width: 100%;
flex-shrink: 0;
flex-grow: 1;
}
.left-arrow, .right-arrow {
position: absolute;
z-index: 1;
top: 50%;
transform: translateY(-50%);
width: 48px;
height: 48px;
border-radius: 24px;
background-color: white;
border: 1px solid #ddd;
}
.left-arrow {
left: 24px;
}
.right-arrow {
right: 24px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
When I used to swipe on mobile, the slider transitions by the swipe amount + the default transition amount. I tried to fix that by trying to subtract the percentage of the screen the swipe took from the transition amount, so if I swipe for 15% of the screen, the slide should transition the remaining 85%.
The problems:
- I try to set the amount of swipe movement as a state value "movement", which seems to be causing trouble and doesn't update properly during every swipe. So sometimes the slider uses the "movement" state from the previous swipe.
- The inertial scrolling on mobile makes the swipes unpredictable. I couldn't turn it off with some methods I found online.
If anyone could take a look at the code, and maybe point me toward how I should try and fix these problems, that would be great. Or better yet, if someone knows a less jank way of making such a carousel snap to the next/previous item while maintaining the finger-following scroll that would be perfect.
I know this isn't ideal, but the SO snippet editor is giving me an unexplainable error so here's codesandbox if anyone wants to play around with it in action: https://codesandbox.io/s/hardcore-goodall-usspz?file=/src/carousel.js&resolutionWidth=320&resolutionHeight=675