Skip to content

Instantly share code, notes, and snippets.

@mithi
Created January 10, 2022 13:54
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 mithi/5b83e1355c9f76151c3398fc1d094791 to your computer and use it in GitHub Desktop.
Save mithi/5b83e1355c9f76151c3398fc1d094791 to your computer and use it in GitHub Desktop.
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