whileInView and whileHover conflicting - reactjs

I have a grid component and a card component that goes inside this. I wanted to animate each child of the grid whileInView so I added this prop to the parent and was working properly.
The problem comes when I try to add whileHover prop to the childs, it causes the whileInView prop to be disabled. I have also tried to add it on the parent but it makes the grid to anime as a whole.
Grid component:
<SmallGridSection key={`div-${title}`}>
<SmallGrid
key={`grid-${title}`}
variants={gridVariants}
initial={"hidden"}
whileInView={"visible"}
viewport={{ once: true }}
>
{array.map(
technology =>
<Card technology={technology} key={technology.name}/>
)}
</SmallGrid>
</SmallGridSection>
using this variants:
const gridVariants = {
visible: {
transition: {
when: "beforeChildren",
delayChildren: 1,
staggerChildren: 0.1,
},
},
hidden: {
transition: {
when: "afterChildren",
duration: 1,
type: "spring",
},
},
}
Card component:
const Card = ({ technology, ...props }) => {
return(
<CardStyled
color={technology.color}
variants={cardVariants}
title={technology.name}
( *this is where i tried to add "whileHover={"hovered"}*)
{...props}
>
<a>
<TechnologyIcon technology={technology}/>
</a>
</CardStyled>
)
}
with this variants:
const cardVariants = {
hidden: {
opacity: 0,
rotate: "100deg"
},
visible: {
opacity: 1,
rotate: 0,
},
animated: {
translateY: "-5px",
transition: {
type: "spring",
delay: 3,
duration: 0.5,
repeat: "infinity",
},
},
hovered: {
scale: 1.08,
transition: {
duration: 0.2,
ease: "easeInOut",
},
}
}

I stumbled upon this problem as well and managed to solve it by applying whileInView and whileHover to different levels, not to one element. So you need to wrap your card content into another div (or whatever you want) and apply whileHover to it as well as variants with the hover property (actually, the whole cardVariants will work as well):
<CardStyled
color={technology.color}
variants={cardVariants}
title={technology.name}
>
<motion.div
whileHover="hovered"
variants={{
hover: cardVariants.hovered,
}}
>
<a>
<TechnologyIcon technology={technology}/>
</a>
</motion.div>
</CardStyled>
Of course, it would make sense to move the new div into your CardStyled component, but the overall structure should look this way.

Related

How to properly animate group of mapped items with framer-motion while array is changing?

I have an array like:
base = [{title:'foo',items: [1,2]},{title:'bar',items:[1,2,3,4]}], then I map through this array with conditions:
<motion.div variants={container} initial="hidden" animate="visible" exit={'hidden'}>
{base[current].items.map(i =>
<motion.div key={index} variants={item} >
<div{i}</div>
</motion.div>)}
</motion.div>
I need to animate all children, but now I only animate that children, which more in array...
for example: (in that case) two children from first array in base is visible, if i change array (by toggle with a button for example) children 3 and 4 are appearing with animation, and hiding too, but children 1 and 2 changing without animation, how to resolve it ?
animation variants:
export const container = {
hidden: { opacity: 1, scale: 0 },
visible: {
opacity: 1,
scale: 1,
transition: {
delayChildren: 0.3,
staggerChildren: 0.2
}
}
};
export const item = {
hidden: { y: 20, opacity: 0 },
visible: {
y: 0,
opacity: 1
}
};
To resolve it we must to wrap our motion.div with AnimatePresence with attributes initial={false} mode={"wait"}, where mapped items will have different keys: for example key={i.title}

How to perform a Reverse Animation Using Framer Motion

I have start doing animations in web recently using Framer Motion. I was wondering how can I make it work in reverse. In my case, I have a Modal which animates from positive X-aixs to 0 Position on X-axis, when animation is completed.
I want it go off the screen in a same way as it entered the screen.
Here's the code
const variants = {
hidden: { x: 400, transition: { duration: 0.3 } },
visible: { x: 0, transition: { duration: 0.3 } },
};
return(
<motion.section
animate='visible'
initial='hidden'
variants={variants}
className='container'
>...
</motion.section>
)
here is the link to the codesandbox

framer-motion custom hover variant is affected by a delay from another variant

I have a set of variants defined as such:
const letterVariants: Variants = {
visible: (custom: number) => ({
opacity: 1,
y: 0,
transition: {
delay: custom * 0.1,
},
}),
hidden: {
opacity: 0,
y: 100,
},
hover: {
y: -30,
transition: {
delay: 0,
},
},
};
which are used for the hero title:
<h1 className="font-peralta">
{letters.map((letter, i) => (
<motion.div
className="inline-block"
variants={letterVariants}
custom={i}
initial="hidden"
animate="visible"
whileHover="hover"
key={nanoid()}
>
{letter}
</motion.div>
))}
</h1>
On initial render, each letter of the hero title fades in from the bottom. I am trying to make it also that when I hover (before or after animation end), the letters slide up a bit. The problem I am facing is that the custom transition delay that allows for the letters to pop up one-by-one is also applied to the hover variant. So when I hover over the first letter, it moves up and down immediately, if I hover over the last letter, it goes up but takes x * 0.1 seconds to return to its normal position.
It's almost exactly the same as in this post except that solution has not worked for me.

How to make a Header that Animates from Transparent to Opaque Color on Scrolling down in React-Native React Navigation 5?

I'm trying to make header that will animate from transparent to solid opaque color upon scrolling down using in React-Native React Navigation 5.
Starts to transition to opaque when scrolling halfway
Becomes fully opaque when reach the maximum offset
You can do this by setting the header style opacity to an animated value.
First define your animated value, we'll interpolate the yOffset to get the opacity desired.
const yOffset = useRef(new Animated.Value(0)).current;
const headerOpacity = yOffset.interpolate({
inputRange: [0, 200],
outputRange: [0, 1],
extrapolate: "clamp",
});
then you want to attach an animated.event listener to an animated scroll view
<Animated.ScrollView
onScroll={Animated.event(
[
{
nativeEvent: {
contentOffset: {
y: yOffset,
},
},
},
],
{ useNativeDriver: true }
)}
scrollEventThrottle={16}
>
Your content should be inside the scroll view
In your screen add a on mount or use effect where you set the animatedValue as the header opacity
useEffect(() => {
navigation.setOptions({
headerStyle: {
opacity: headerOpacity,
},
headerBackground: () => (
<Animated.View
style={{
backgroundColor: "white",
...StyleSheet.absoluteFillObject,
opacity: headerOpacity,
}}
/>
),
headerTransparent: true,
});
}, [headerOpacity, navigation]);
I've used header transparent and header background so that the background component changes also.
Here is an example:
https://snack.expo.io/#dannyhw/react-navigation-animated-header
const handleScroll = () => {
YourElementRef.current.style.backgroundColor = `rgba(245, 245, 245, ${window.scrollY > 300 ? 1 : '0.' + (window.scrollY * 3)})`;
}
window.addEventListener('scroll', handleScroll, true);
You need to add scroll listener and call function that animates it.
The scroll element is represented by a ref stuff. e.g.
const YourElementRef = React.useRef(null);
<SomeElement ref={YourElementRef}...

React Router + React Pose slide in/out with "POP" or "PUSH" direction

I've been having this issue for over a month, getting back to it with new ideas over and over, but I can't wrap my head around a solution. There must be one out there, this is trivial!
My goal is to normally slide pages in from left and slide out to right. When it reverses (user click back button or history notices a route has been popped) the page should exit to the right and next route should enter from left (opposite).
Everything works, but when I PUSH a route and then POP a route, the next page will both exit to right and enter from right. The entering page uses an old pose before it has time to update. If I POP another time, it then works correctly, and the new page slides from left while the old page exits to right. It's only the first time I change with a POP. I'm very dependent on the const reverse value. The component only renders once when I change route and reverse = true the first time I click POP.
I have an AnimatedSwitch component like this
export const AnimatedSwitch = ({ location, children, ...rest }) => {
const clickedBack = useSelector(state => state.interfaceReducer.clickedBack);
const routePopped = useSelector(state => state.routeReducer.routePopped);
const reverse = clickedBack || routePopped;
const onRest = () => {
clickedBack && dispatch(toggleClickedBackButton(false));
}
return (
<PoseGroup
animateOnMount={true}
preEnterPose={reverse ? "leftSide" : "rightSide"}
exitPose={reverse ? "rightSide" : "leftSide"}
onRest={onRest}
reverse={reverse}
>
<ContextRouteAnimation poseKey={`${reverse}-${location.pathname}`} key={location.pathname} reverse={reverse}>
<Switch location={location} {...rest}>
{children}
</Switch>
</ContextRouteAnimation>
</PoseGroup>
)
}
And this is the ContextRouteAnimation pose component:
const ContextRouteAnimation = posed.div({
enter: {
opacity: 1,
x: "0%",
transition: {
type: "tween",
ease: "easeInOut",
duration: TIMING.SLOW,
},
},
leftSide: {
x: "-100%",
opacity: 0,
transition: {
type: "tween",
ease: "easeInOut",
duration: TIMING.SLOW,
},
},
rightSide: {
x: "100%",
opacity: 0,
transition: {
type: "tween",
ease: "easeInOut",
duration: TIMING.SLOW,
},
},
});
I've tried multiple things such as
Pass reverse down to ContextRouteAnimation and use preEnter + exit poses.
Set pose directly on ContextRouteAnimation
Store component as state and only update it after new pose is updated, and then trigger another render

Resources