I want to create a button that will trigger every 2 seconds and change it's scale from 1 to 1.5 and have on the same element second animation but only when you put mouse over it. My problem is that I don't know how to connect this two useSpring hooks.
My animation on hover:
const buttonAnimation = useSpring({
backgroundColor: !isVisible ? '#4b1220' : '#fff',
});
and I want to apply second animation
const buttonInfAnimation = useSpring({
from: {
scale: 1,
},
to: {
scale: 1.5,
},
});
When I call this like this, I'm getting only latest animation in code:
<animated.button
style={{...buttonInfAnimation, ...buttonAnimation}}
onMouseLeave={() => setVisibility(false)}
onMouseEnter={() => setVisibility(true)}
className="hover"
></animated.button>
How to apply two animations with React Springs on one element? I have tried to implement useSprings but I don't understand it correctly how to use this.
https://www.react-spring.io/docs/hooks/use-springs
You do not have to create two separate spring animation. You can combine them. And for the scale you have to use the transform css property.
const buttonInfAnimation = useSpring({
from: {
transform: 'scale(1)',
},
to: {
transform: 'scale(1.5)',
backgroundColor: !isVisible ? '#4b1220' : '#fff',
},
});
Related
On my React website, I have a vertical list of screenshots of my past projects, when the page first loads the images load, but are only a sliver until the user hovers over them.
Once hovered over the images expand to their full size and then work properly. I am looking to prevent the user from hovering over them in order for it to load properly.
The list is mapped from an array
items: [
{
image: "./images/spokanepowerstroke.jpg",
title: "Spokane Power Stroke",
link: "/powerstroke",
},
...
]
<Col md="auto">
{this.state.items.map(({ title, image, link }) => (
<motion.div className="thumbnail" variants={thumbnailVariants}>
{" "}
<motion.div
key={title}
className="frame"
whileHover="hover"
variants={frameVariants}
transition={transition}
>
<p className="projectstitle">{title}</p>
<Link to={link}>
<motion.img
src={image}
alt={image}
variants={imageVariants}
transition={transition}
/>
</Link>
</motion.div>
</motion.div>
))}
</Col>
And the hover and transition effects are controlled with framer motion.
const transition = { duration: 0.5, ease: [0.43, 0.13, 0.23, 0.96] };
const thumbnailVariants = {
initial: { scale: 0.9, opacity: 0 },
enter: { scale: 1, opacity: 1, transition },
exit: {
scale: 0.5,
opacity: 0,
transition: { duration: 1.5, ...transition },
},
};
const frameVariants = {
hover: { scale: 0.95 },
};
const imageVariants = {
hover: { scale: 1.1 },
};
const changepage = {
in: {
opacity: 1,
},
out: {
opacity: 0,
},
};
const pagetransition = {
duration: 1.5,
};
I've looked over my code and have found no reason why the images are only loading partially.
The website is viewable here Website
And the Github repo with all the code is here Github
(the projects page)
Thank you in advance for your expertise.
In your App.css code, change this:
.thumbnail img { width: 46vw; height: 100%; }
to height:auto;
% heights will stretch/distort an image usually, if you want to keep aspect ratio use auto
also maybe setting width & height on the img element in your code might help prevent sizing issues, because right now the browser doesn't know how big the image actually is, and since it's loaded in via React JS not in the HTML first served it probably can't calculate it until the hover animation forces a repaint.
Is there any built-in way to make the animation start when the element is in-view (for example, when we scroll to the element)?
Framer Motion has mount animations section which says:
When a component mounts, it'll automatically animate to the values in animate if they're different from those defined in style or initial
So I couldn't really find a straight forward way to make the animation start when it comes into view.
However, I reached the only option I see for now is using Animation Controls which means I'll have to implement a listener on the scroll manually and trigger the control.start(), any easier ways are much appreciated.
framer-motion has built-in support for this use case since version 5.3.
Here's a CodeSandbox demonstrating the pattern: https://codesandbox.io/s/framer-motion-animate-in-view-5-3-94j13
Relevant code:
function FadeInWhenVisible({ children }) {
return (
<motion.div
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
transition={{ duration: 0.3 }}
variants={{
visible: { opacity: 1, scale: 1 },
hidden: { opacity: 0, scale: 0 }
}}
>
{children}
</motion.div>
);
}
Usage:
<FadeInWhenVisible>
<Box />
</FadeInWhenVisible>
Previous versions:
You can currently use the imperative animation controls to achieve this effect. Intersection observers are useful to detect if an element is currently visible.
Here's a CodeSandbox demonstrating the pattern: https://codesandbox.io/s/framer-motion-animate-in-view-gqcc8.
Relevant code:
function FadeInWhenVisible({ children }) {
const controls = useAnimation();
const [ref, inView] = useInView();
useEffect(() => {
if (inView) {
controls.start("visible");
}
}, [controls, inView]);
return (
<motion.div
ref={ref}
animate={controls}
initial="hidden"
transition={{ duration: 0.3 }}
variants={{
visible: { opacity: 1, scale: 1 },
hidden: { opacity: 0, scale: 0 }
}}
>
{children}
</motion.div>
);
}
I am trying to use a React Native Animation to remove a padding(by making it 0) and then to put it back.
The following code is managing the animations:
componentWillMount() {
this.animatedValueLateralPadding = new Animated.Value(Constants.LIST_ITEM_MARGIN * this.props.dimensions.windowWidth);
}
componentWillReceiveProps(nextProps) {
if(nextProps.index == this.props.element.ordinalNumber) {
Animated.stagger(Constants.RESIZED_TIME, [
Animated.parallel([
Animated.timing(this.animatedValueLateralPadding, {
toValue: 0,
duration: Constants.RESIZE_TRANSITION_TIME
}),
]),
Animated.parallel([
Animated.timing(this.animatedValueLateralPadding, {
toValue: Constants.LIST_ITEM_MARGIN * nextProps.dimensions.windowWidth,
duration: Constants.RESIZE_TRANSITION_TIME
}),
])
]).start();
}
}
In my render method I specify the style like this:
const animatedStyle = {paddingLeft: this.animatedValueLateralPadding, paddingRight: this.animatedValueLateralPadding};
And then animatedStyle is used in this component which is returned:
<ScrollView
contentContainerStyle={[listStyles.container, animatedStyle]}
>
//some other code
The rest of the style is this:
const listStyles = StyleSheet.create({
container: {
backgroundColor: Constants.COLOR_BLACK,
minHeight: '100%'
}
});
The problem is that the padding does not disappear and I am getting this warning:
Failed prop type:Invalid prop 'paddingLeft' supplied to 'ScrollView'. Bad object:
{
"backgroundColor": "#000000",
"minHeight": "100%",
"paddingLeft": 18,
"paddingRight": 18
}
I don't understand why it doesn't like how I specified paddingLeft.
I tried to pass a String instead of Int all to the Animated.Value object:
this.animatedValueLateralPadding = new Animated.Value(String.valueOf(Constants.LIST_ITEM_MARGIN * this.props.dimensions.windowWidth));
and
toValue: String.valueOf(0),
and
toValue: String.valueOf(Constants.LIST_ITEM_MARGIN * nextProps.dimensions.windowWidth)
However, I am getting:
Error while updating property: 'paddingLeft' in shadow node of type: RCTView
null
Unknown value: function String() {
[native code]
}0
So why does it have a problem with the way in which I specify the padding? Any idea how this can be fixed?
You need to use Animated components like Animated.View, Animated.Text to have this animation.
Consider reading this: https://facebook.github.io/react-native/docs/animations.html
Just change your ScrollView to Animated.ScrollView
I have a FlatList with an onScroll function that looks like this:
<Animated.View style={{ transform: [{translateX: this.state.scrollX}] }}>
<FlatList
data={ reactionGroup.reactions }
keyExtractor={ (i) => i.id + uuid.v4() }
renderItem={ this._renderItem }
horizontal={ true }
scrollEventThrottle={1}
onScroll={ reactionGroup.reactions.length > 1 ? this.onScroll : null }
showsHorizontalScrollIndicator={ false } />
</Animated.View>
onScroll(event) {
const { timing } = Animated
if (event.nativeEvent.contentOffset.x > 0) {
timing(
this.state.scrollX,
{ toValue: -60, duration: 100, useNativeDriver: true }
).start()
} else {
timing(
this.state.scrollX,
{ toValue: 0, duration: 100, useNativeDriver: true }
).start()
}
},
This works great on iOS, but for Android the animation won't start until the FlatList has stopped scrolling.
I'm basically just kicking off an animation when the user scrolls and setting it back when they go back to the beginning of the horizontal scroll.
Is there a better way to handle this so it works on Android?
I also tried doing Animation.event inside onScroll, but I don't want to tie the animation directly to the scroll position. This approach allowed Android to animate while scrolling, but it was pretty jittery.
RN: 0.43.3
You should use the Animated.event approach. As seen in the example in the docs, it maps the event.nativeEvent for you.
Here's a blogpost with an example of how to animate the nav header on scroll by a RN contributor:
https://medium.com/appandflow/react-native-collapsible-navbar-e51a049b560a
Hope it helps :-)
I'm unable to animate the borderRadius property in a ReactNative Image, it seems to only re-render the image as the animation completes. It fades out on animation start, and fades back in on animation completion. This only happens on Android; on iOS the animation plays properly.
I am trying to animate a circle expanding into a square by animating the borderRadius:
constructor(props) {
super(props);
this.state = {
borderRadius: new Animated.Value(ALBUM_CIRCLE_DIAMETER /2)
};
}
_zoomIn = () => {
Animated.timing(
this.state.borderRadius,
{
toValue: 0,
duration: ZOOM_ANIMATION_DURATION_MS,
easing: Easing.linear
}
).start()
}
And the markup:
<Animated.Image
style={[
styles.albumArtCircle,
{ width: this.state.albumArtWidth },
{ height: this.state.albumArtHeight },
{ borderRadius: this.state.borderRadius },
]}
resizeMode='contain'
source={require('../images/sampleAlbum.jpg')}>
</Animated.Image>
Right! Remove resizeMode property. This will solve your problem
Removing resizeMode='contain' made this work.