Skip to content

Instantly share code, notes, and snippets.

@ajsmth
Created August 10, 2019 19:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ajsmth/360e46aa57b1546da7427a7ea8f0b11c to your computer and use it in GitHub Desktop.
Save ajsmth/360e46aa57b1546da7427a7ea8f0b11c to your computer and use it in GitHub Desktop.
Pager Drag Handlers
import React, { useState, useEffect } from 'react'
import { useSpring, animated, interpolate } from 'react-spring'
import { useDrag } from 'react-use-gesture'
// let's handle gestures within our pager component
const PAGE_SIZE = 375
// arbitrary value that will determine if we should transition after dragging
const DRAG_THRESHOLD = Math.floor(PAGE_SIZE * 0.3)
function Pager({ children, activeIndex, onChange }) {
const offset = activeIndex * 100 * -1
// dragX will represent the current drag value to animate
const [{ translateX, dragX }, set] = useSpring(() => ({
translateX: offset,
dragX: 0,
}))
// this might look a bit strange but it's part of the api for useDrag
// bind() is a function we'll add to our container div that gives us a bunch of gesture state data
// think of this as an event listener for gestures
const bind = useDrag(({ delta, last, vxvy }) => {
// this is the drag value
const [x] = delta
// the velocity of the drag -- important to track to prevent jank after user releases
const [vx] = vxvy
// we want the value to immediate update w/ a user drag event, not spring to the value
set({ dragX: x, immediate: true })
// last is true when the user releases from dragging
if (last) {
// user has dragged beyond our threshold to transition (either left or right)
const shouldTransition = Math.abs(x) >= DRAG_THRESHOLD
if (!shouldTransition) {
// restore to initial position when user started dragging:
set({ dragX: 0, immediate: false })
} else {
// determine the next position based on the drag value (left or right)
let nextPosition
if (x > DRAG_THRESHOLD) {
// transition to previous page
nextPosition = offset + 100
// update our controller component w/ the previous index
onChange(activeIndex - 1)
}
if (x < DRAG_THRESHOLD) {
// transition to next page
nextPosition = offset - 100
// update our controller component w/ the next index
onChange(activeIndex + 1)
}
// start spring transition to next position
// we want to spring the drag value back to 0 as we translate to the next position
set({
dragX: 0,
translateX: nextPosition,
immediate: false,
config: {
velocity: vx,
},
})
}
}
})
useEffect(() => {
set({ translateX: offset })
}, [offset, set])
// attach the bind() function to our container div to listen for gesture events
// we will now include our drag values in the interpolate() function below
return (
<animated.div
{...bind()}
style={{
border: 'thin solid blue',
position: 'relative',
height: '100%',
width: '100%',
transform: interpolate(
[translateX, dragX],
(translateX, dragX) => `translateX(calc(${translateX}% + ${dragX}px))`,
),
}}>
{React.Children.map(children, (element, index) => {
return (
<div
style={{
...absoluteFill,
transform: `translateX(${index * 100}%)`,
}}>
{element}
</div>
)
})}
</animated.div>
)
}
const absoluteFill = {
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
top: 0,
}
// this will represent a consumer component or any part of your application
function App() {
// all we need to pass are children and an activeIndex prop to our pager component
const [activeIndex, setActiveIndex] = useState(3)
function handleChange(index) {
setActiveIndex(index)
}
const children = Array.from({ length: 10 }).map((c, i) => (
<h1 key={i} style={{ textAlign: 'center' }}>
Index {i}
</h1>
))
return (
<div style={{ overflow: 'hidden' }}>
<div
style={{
width: PAGE_SIZE,
height: PAGE_SIZE,
display: 'flex',
margin: 'auto',
padding: '5px',
border: 'thin solid red',
}}>
<Pager activeIndex={activeIndex} onChange={handleChange}>{children}</Pager>
</div>
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-around',
}}>
<strong style={{ margin: '5px 0' }}>activeIndex: {activeIndex}</strong>
<button
style={{ margin: '5px 0' }}
onClick={() => setActiveIndex(activeIndex + 1)}>
Increment
</button>
<button
style={{ margin: '5px 0' }}
onClick={() => setActiveIndex(activeIndex - 1)}>
Decrement
</button>
</div>
</div>
)
}
export default App
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment