import React from 'react';
import { useSpring } from 'react-spring';
// UPDATE this path to your copy of the hook!
// Source here: https://joshwcomeau.com/snippets/react-hooks/use-prefers-reduced-motion
import usePrefersReducedMotion from '@/hooks/use-prefers-reduced-motion';
function useBoop({
x = 0,
y = 0,
rotation = 0,
scale = 1,
timing = 150,
springConfig = {
tension: 300,
friction: 10,
},
}) {
const prefersReducedMotion = usePrefersReducedMotion();
const [isBooped, setIsBooped] = React.useState(false);
const style = useSpring({
transform: isBooped
? `translate(${x}px, ${y}px)
rotate(${rotation}deg)
scale(${scale})`
: `translate(0px, 0px)
rotate(0deg)
scale(1)`,
config: springConfig,
});
React.useEffect(() => {
if (!isBooped) {
return;
}
const timeoutId = window.setTimeout(() => {
setIsBooped(false);
}, timing);
return () => {
window.clearTimeout(timeoutId);
};
}, [isBooped]);
const trigger = React.useCallback(() => {
setIsBooped(true);
}, []);
let appliedStyle = prefersReducedMotion ? {} : style;
return [appliedStyle, trigger];
}
export default useBoop;
This hook is described in much more detail in my tutorial, Boop: A whimsical twist on hover transitions.
In order for it to work, you'll also need to grab another hook, usePrefersReducedMotion.
You can build a thin component wrapper over it, for cases where the trigger and animation happen on the same element:
// components/Boop.jsx
import React from 'react';
import { animated } from 'react-spring';
import useBoop from '@/hooks/use-boop';
const Boop = ({ children, ...boopConfig }) => {
const [style, trigger] = useBoop(boopConfig);
return (
<animated.span onMouseEnter={trigger} style={style}>
{children}
</animated.span>
);
};
Can't wait to see what you come up with!
Last updated on
November 23rd, 2020