Created
January 10, 2022 13:54
-
-
Save mithi/5b83e1355c9f76151c3398fc1d094791 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useCallback, useState } from 'react'; | |
/******************* | |
This custom hook `useSwipeMotion()` return the props to be passed | |
to <motion.div /> and <AnimatePresence /> from framer-motion (among other things) | |
inorder to produce the swiping effect for whatever component that needs it. | |
animation that will be produced by using this hook is heavily based on: | |
https://www.framer.com/docs/examples/ | |
https://codesandbox.io/s/framer-motion-image-gallery-pqvx3 | |
*********************************** | |
SAMPLE USAGE | |
*********************************** | |
const coloredBoxes = ['red', 'yellow', 'green', 'blue'].map((color) => ( | |
<div key={color} style={{ background: color, height: '100px' }}> | |
{color} | |
</div> | |
)); | |
const Example = () => { | |
const { | |
motionDivProps, | |
goToNext, | |
goToPrevious, | |
animatePresenceProps, | |
currentIndex, | |
} = useSwipeMotion(coloredBoxes.length); | |
return ( | |
<div> | |
<div tw="flex justify-between bg-white"> | |
<button onClick={goToPrevious}>Prev</button> | |
<button onClick={goToNext}>Next</button> | |
</div> | |
<div tw="relative py-96"> | |
<AnimatePresence {...animatePresenceProps}> | |
<motion.div | |
key={currentIndex} | |
tw="absolute top-0 left-0 w-full" | |
{...motionDivProps} | |
> | |
{coloredBoxes[currentIndex]} | |
</motion.div> | |
</AnimatePresence> | |
</div> | |
</div> | |
); | |
}; | |
* ***************** | |
*/ | |
const variants = { | |
enter: (_direction: number) => { | |
return { | |
//x: _direction > 0 ? 0 : -1000, | |
opacity: 0, | |
}; | |
}, | |
center: { | |
zIndex: 1, | |
x: 0, | |
opacity: 1, | |
}, | |
exit: (_direction: number) => { | |
return { | |
zIndex: 0, | |
//x: _direction < 0 ? 1000 : 0, | |
opacity: 0, | |
}; | |
}, | |
}; | |
const constantMotionProps = { | |
variants, | |
initial: 'enter', | |
animate: 'center', | |
exit: 'exit', | |
transition: { | |
x: { type: 'spring', stiffness: 300, damping: 30 }, | |
opacity: { duration: 1.0 }, | |
}, | |
drag: 'x' as 'x' | 'y', | |
dragConstraints: { left: 0, right: 0 }, | |
dragElastic: 1, | |
}; | |
const swipeConfidenceThreshold = 10000; | |
const swipePower = (offset: number, velocity: number) => { | |
return Math.abs(offset) * velocity; | |
}; | |
const useSwipeMotion = (cardsLength: number) => { | |
const [[currentIndex, direction], setPage] = useState([0, 0]); | |
const beforeIndex = currentIndex === 0 ? cardsLength - 1 : currentIndex - 1; | |
const afterIndex = (currentIndex + 1) % cardsLength; | |
const goToNext = useCallback(() => { | |
setPage([afterIndex, -1]); | |
}, [afterIndex]); | |
const goToPrevious = useCallback(() => { | |
setPage([beforeIndex, 1]); | |
}, [beforeIndex]); | |
const onDragEnd = useCallback( | |
(_e, { offset, velocity }) => { | |
const swipe = swipePower(offset.x, velocity.x); | |
if (swipe < -swipeConfidenceThreshold) { | |
goToNext(); | |
} else if (swipe > swipeConfidenceThreshold) { | |
goToPrevious(); | |
} | |
}, | |
[goToNext, goToPrevious] | |
); | |
return { | |
motionDivProps: { | |
custom: direction, | |
...constantMotionProps, | |
onDragEnd, | |
}, | |
animatePresenceProps: { | |
custom: direction, | |
initial: false, | |
}, | |
currentIndex, | |
beforeIndex, | |
afterIndex, | |
goToNext, | |
goToPrevious, | |
}; | |
}; | |
export default useSwipeMotion; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment