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.
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.
I am trying to run a few simple animations using react-native-animatable library. (But I believe the question should be generic to any react animations so adding other tags as well.)
The problem is, in the first time, the image animates just as expected. But when aimed to start second animation animation with the gesture, the image translation starts from its original coordinates.
A search yielt, in Android development (which is obviously not my case) there seems a method, setFillAfter which sets the coordinate after the animation.
My question is, how to set the location (left / top values for example) to the final translated point so that consecutive animation starts from the point the previous translation left.
The expo snack for below code block is here.
import * as React from 'react';
import { Image, StyleSheet, ImageBackground } from 'react-native';
import * as Animatable from 'react-native-animatable';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import testImg from './test.png';
import backImg from './back.png';
export default class App extends React.Component {
onTestMove(event) {
this.testAnimRef.transitionTo({
translateX: event.nativeEvent.translationX,
translateY: event.nativeEvent.translationY,
}, 0);
}
render() {
return (
<ImageBackground source={backImg} style={{ flex: 1 }} >
<PanGestureHandler
key={`test`}
onGestureEvent={(e) => { this.onTestMove(e) }}
onHandlerStateChange={e => { }}
>
<Animatable.View style={styles._animatable_view}
ref={((ref) => { this.testAnimRef = ref }).bind(this)}
useNativeDriver={true}
>
<Image source={testImg} style={styles._image} />
</Animatable.View>
</PanGestureHandler>
</ImageBackground>
);
}
}
const styles = StyleSheet.create({
_image: {
width: 50,
height: 25,
resizeMode: 'contain',
backgroundColor: 'black',
borderColor: 'gainsboro',
borderWidth: 2,
},
_animatable_view: {
position: "absolute",
top: 200,
left: 100,
},
});
I had the same problem trying to move around some cards in a view, and upon further dragging, they would reset to their origin.
My theory is/was that while the translated view would have its x / y coordinates translated, this would not apply to the parent of that view, and so the animated event passed from that component would initially have the original coordinates (nuke me if I'm wrong here)
So my solution was to keep an initial offset value in state, and maintain this every time the user releases the dragged motion
_onHandleGesture: any
constructor(props: OwnProps) {
super(props)
this.state = {
animationValue: new Animated.ValueXY({ x: 0, y: 0 }),
initialOffset: { x: 0, y: 0 },
}
this._onHandleGesture = (e: PanGestureHandlerGestureEvent) => {
this.state.animationValue.setValue({
x: e.nativeEvent.translationX + this.state.initialOffset.x, <- add initial offset to coordinates passed
y: e.nativeEvent.translationY + this.state.initialOffset.y,
})
}
}
_acceptCard = (cardValue: number) => {
const { targetLocation, onAccept } = this.props
const { x, y } = targetLocation
onAccept(cardValue)
Animated.spring(this.state.animationValue, {
// Some animation here
}).start(() => {
this.setState({ initialOffset: targetLocation }) // <- callback to set state value for next animation start
})
}
and the render method
<PanGestureHandler
onHandlerStateChange={this.onPanHandlerStateChange}
onGestureEvent={this._onHandleGesture}
failOffsetX={[-xThreshold, xThreshold]}
>
<Animated.View
style={{
position: "absolute",
left: 0,
top: 0,
transform: [{ translateX: this.state.animationValue.x }, { translateY: this.state.animationValue.y }],
}}
>
<CardTile size={size} content={content} layout={layout} backgroundImage={backgroundImage} shadow={shadow} />
</Animated.View>
</PanGestureHandler>
This example is based on the react-native-gesture-handler library, but the concept should apply to other solutions.
Dont know if this way is advisable, though it is functional.
Hope this helps!
I'm using the react package react-moves to create an animation of three separate stripes that will appear on a page when the page refreshes. The background color doesn't seem to be rendering correctly. My react component is entered below;
class Stripes extends Component {
state = {
stripes: [
{
background:'#98c5e9',
left: 120,
rotate: 25,
top: -260 ,
delay: 0
},
{
background:'#ffffff',
left: 360,
rotate: 25,
top: -397,
delay: 500
},
{
background:'#98c5e9',
left: 600,
rotate: 25,
top: -498,
delay: 1000
}
]
}
AnimatedStripesHander = () => (
this.state.stripes.map((stripe, index) => (
<Animate
key={index}
show={true}
start={{
background: '#ffffff',
opacity: 0,
left: 0,
rotate: 0,
top: 0
}}
enter={{
background: [stripe.background],
opacity: [1],
left: [stripe.left],
rotate: [stripe.rotate],
top: [stripe.top],
timing: { delay: stripe.delay, duration: 500, ease: easePolyOut },
events: {
end() {
console.log(stripe.background)
}
}
}}
>
{({opacity,left,rotate,top,background})=>{
return(
<div
style={{
background,
opacity,
transform: `rotate(${rotate}deg) translate(${left}px,${top}px)`
}}
></div>
);
}}
</Animate>
))
)
The issue I am having trouble with is the background tag in the style object returned at the bottom. This background is erroring but I just can't seem to understand why. When I remove it everything else in the animation works very good however not the background color, has anybody got any experience with react-moves?
Thanks
There's no npm package react-moves so I'm assuming that you're using react-move.
The docs for react-move say that you need to use a third-party package to use color interpolation:
https://github.com/react-tools/react-move#cadillac-interpolation----depends-on-d3-interpolate
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 :-)