Closed anhuiliujun closed 5 years ago
Yes, it can. Probably you want to create Animated variants of each component (with Animated.createAnimatedComponent) to use
Animated.Value` objects as properties.
I'm struggling to use Animated to animate the 'd' property on a Path component. It looks like this is because Animated.Values can only be numerical. So I reverted to trying to use setNativeProps on the Path component to set 'd', but an error is thrown.
Can you tell me if setNativeProps is supported on Paths? Thanks!
I think d property is atm impossible to do with Animated as you only can interpolate from number to string. I've not tried the setNativeProps route, maybe you can show your code? I could try and figure out what's happening in native. Btw on Android or iOS?
Thanks for your response. I tried a few approaches this morning which I'll share with you. There approaches are as follows:
setState
setNativeProps
using setState
works fine, but I just thought there would be a more performant way of doing it that having to re-render the tree each frame, which is why I attempted using setNativeProps
. The error message I get for the second and third approach are the same, i'm assuming this is because Animated.Value
just uses setNativeProps
internally. This is the error message. It looks like it's on the native side (iOS) but I haven't investigated it any further.
Here are the code snippets I used for each:
using setState
export default class testing extends Component {
constructor(props) {
super(props)
this._origin = { x: 100, y: 100 }
this._radius = 50
this.state = {
arcEndX: Math.sin(0) * this._radius,
arcEndY: Math.cos(0) * this._radius - this._radius,
largeArcFlag: Math.sin(0) >= 0 ? 0 : 1
}
this.setArcEndFromRadians = this.setArcEndFromRadians.bind(this)
}
setArcEndFromRadians(radians) {
this.setState({
arcEndX: Math.sin(radians) * this._radius,
arcEndY: Math.cos(radians) * this._radius - this._radius,
largeArcFlag: Math.sin(radians) >= 0 ? 0 : 1
})
}
componentDidMount() {
let radians = 0
let timer = setInterval(() => {
radians += 0.02
this.setArcEndFromRadians(radians)
}, 16)
}
render() {
return (
<View>
<Svg
height="200"
width="200">
<Path
d={ `M ${this._origin.x},${this._origin.y} l 0,50 a 50,50 0 ${this.state.largeArcFlag} 0 ${this.state.arcEndX},${this.state.arcEndY} z` }/>
</Svg>
</View>
)
}
}
using setNativeProps (for better performance, error thrown)
export default class testing extends Component {
constructor(props) {
super(props)
this._origin = { x: 100, y: 100 }
this._radius = 50
this._arc = {
arcEndX: Math.sin(0) * this._radius,
arcEndY: Math.cos(0) * this._radius - this._radius,
largeArcFlag: Math.sin(0) >= 0 ? 0 : 1
}
this.setArcEndFromRadians = this.setArcEndFromRadians.bind(this)
}
setArcEndFromRadians(radians) {
let arcEndX = Math.sin(radians) * this._radius
let arcEndY = Math.cos(radians) * this._radius - this._radius
let largeArcFlag = Math.sin(radians) >= 0 ? 0 : 1
this._thePath.setNativeProps({
d: `M ${this._origin.x} ${this._origin.y} l 0 50 a 50,50 0 ${largeArcFlag} 0 ${arcEndX} ${arcEndY} z`
})
}
componentDidMount() {
let radians = 0
let timer = setInterval(() => {
radians += 0.02
this.setArcEndFromRadians(radians)
}, 16)
}
render() {
return (
<View>
<Svg
height="200"
width="200">
<Path
ref={ ref => this._thePath = ref }
d={ `M ${this._origin.x},${this._origin.y} l 0,50 a 50,50 0 ${this._arc.largeArcFlag} 0 ${this._arc.arcEndX},${this._arc.arcEndY} z` }/>
</Svg>
</View>
)
}
}
using Animated interpolation (simplified to just drawing a line, rather than a circle, error thrown)
let AnimatedPath = Animated.createAnimatedComponent(Path)
export default class testing extends Component {
constructor(props) {
super(props)
this._origin = { x: 100, y: 100 }
this._radians = new Animated.Value(0)
this._arcX = this._radians.interpolate({
inputRange: [
0,
100
],
outputRange: [
`M ${this._origin.x},${this._origin.y} l 0,0`,
`M ${this._origin.x},${this._origin.y} l 100,100`
]
})
}
componentDidMount() {
Animated.spring(this._radians, {
toValue: Math.PI,
friction: 5,
tension: 135
}).start()
}
render() {
return (
<View>
<Svg
height="200"
width="200">
<AnimatedPath
d={ this._arcX }/>
</Svg>
</View>
)
}
}
Components has method setNativeProps but he doesnt work. Is there any other normal ways animate svg's part, other then just to wrap each in Svg?
I was able to animate an Svg object by animating a react-native View component wrapped around it.
import React, { Component } from 'react';
import {
View,
Animated,
Easing
} from 'react-native';
import Svg, {
Line,
G,
Text
} from 'react-native-svg';
class MovingHand extends Component {
constructor(props){
super(props)
const { remainder, duration } = props.timer
const start = duration - remainder
this.state = {
wind: new Animated.Value(start),
duration,
start
}
}
componentDidMount() {
Animated.timing(
this.state.wind,
{
toValue: 1,
duration: this.state.start * 1000,
easing: Easing.none,
}
).start()
}
render() {
const {
width,
height,
radius,
strokeWidth,
} = this.props;
const { start, duration } = this.state
const motionStyle = {
transform: [{
rotate: this.state.wind.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
})
}]
}
return (
<Animated.View style={motionStyle}>
<Svg width={width} height={height}>
<Line
x1={radius}
y1={0.20 * radius}
x2={radius}
y2={radius}
stroke='brown'
strokeWidth={2 * strokeWidth}
strokeLinecap='round'
/>
</Svg>
</Animated.View>
)
}
}
export default MovingHand;
Getting a very similar issue when attempting to animate using color values
At the risk of posting to more than one animation issue, is there any way to animate an SVG element without wrapping it in a Animated View?
I'm trying to adjust the radius of a circle without using scale transform in the styles, because I later want to adjust the start and end points of a SVG Line.
Here's what I'm running into:
// var AnimatedCircle = Animated.createAnimatedComponent(Circle); <-- Done before.
<AnimatedCircle cx="250" cy="250" r={this.state.circleRadius} fill="black" />
With the following animation on mount:
// this.state.circleRadius = new Animated.Value(50) <-- Starting value
Animated.spring(
this.state.circleRadius,
{ toValue: 100, friction: 3 }
).start();
And getting the following error:
@joshuapinter Try:
<AnimatedCircle
cx="250"
cy="250"
r={`${this.state.circleRadius}`}
fill="black"
/>
@anhtuank7c Thanks for the suggestion but now I get this error message:
Invalid float: "[object Object]"
@grubstarstar @leebradley I'm having exactly the same issue. Did you ever find a workable solution? Thanks!
I opened this before finding this thread: https://github.com/react-native-community/react-native-svg/issues/326
@udfalkso I found that simply using setState as per my first example above was sufficiently performant in the end for my needs. @Lyonsclay solution of animating the wrapping View is good if you can accomplish what you need using that but I couldn't in my case. I found that once I had my solution built in release mode the performance was more acceptable.
It would be great to be able to use with Animated though, especially if it could hand over the animation details to the native thread using "useNative" https://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html it seems like this is where animation belongs.
Thanks @grubstarstar. I'm already doing the wrapping View trick for opacity changes, but I can't do that for fill color.
@udfalkso nah, true. Is setState too slow? I managed a pretty smooth radial animation just using that.
@grubstarstar setState
's performance really depends on what else you're doing in the component's methods, like render
. For simple components, setState
may be sufficient but it quickly loses value on more complex components.
The advantage of using Animated is that it completes the animation on the UI thread so is more or less unaffected by what else your component is trying to do.
@grubstarstar Yes unfortunately in my case I'm doing this for many paths at the same time (20+) and things grind to a halt.
@grubstarstar @joshuapinter I managed to get it working with some fairly small changes.
Check out the PR. I hope it works for you guys too: https://github.com/react-native-community/react-native-svg/pull/328
The only gotcha is that you still have to use this.myAnim.__getAnimatedValue() when sending the value into setNativeProps
animate = () => {
if (this._path && this.fillColor) {
this._path.setNativeProps({
fill: this.fillColorInterpolation.__getAnimatedValue(),
})
}
requestAnimationFrame(this.animate.bind(this))
}
@udfalkso good work! I'll take a look at that...
Hi guys,
I just wanted to follow up from my comment.
Found a workable solution using addListener
and using setNativeProps
. A little messy but works none-the-less and is quite performant.
Here's a simplified version of the solution:
constructor(props) {
super(props);
this.state = { circleRadius: new Animated.Value(50) };
this.state.circleRadius.addListener( (circleRadius) => {
this._myCircle.setNativeProps({ r: circleRadius.value.toString() });
});
setTimeout( () => {
Animated.spring( this.state.circleRadius, { toValue: 100, friction: 3 } ).start();
}, 2000)
}
render() {
return(
<Svg height="400" width="400">
<AnimatedCircle ref={ ref => this._myCircle = ref } cx="250" cy="250" r="50" fill="black" />
</Svg>
)
}
And the resulting animation:
And this is being rendered on a very complex component where using setState
isn't fluid at all.
@joshuapinter Thanks. I implemented your animated circle and tried to extend the example to a Polygon (in an attempt to animate a point in the polygon) but received the following error:
undefined is not a function (evaluating '_this3.root.getNativeElement()')
Here's my source for both the broken polygon and the working circle: https://gist.github.com/dk0r/76761b6cc5a3069b9443fabb81801a55
If you guys need examples on how to get most of the components animated, check my comment at #55
@joshuapinter Your solution working good while update r
, i tried to update rotate
but nothing changed :(
@dk0r I found same error with Polyline :(
@anhtuank7c There are only certain attributes that are exposed to setNativeProps
. I'm not sure what they are but if anybody finds a good list of them, please post it here!
Do y’all think we’ll ever be able to offload animations on the native side via useNativeDriver
?
Its fully possible, just have to integrate with the driver and the declarative animation passing ;) You wanna take a shot at it?
@joshuapinter For the native props you can check the view managers https://github.com/react-native-community/react-native-svg/tree/master/ios/ViewManagers
@msand I’d like to possibly take a stab at it. :) I’m not very familiar with how this might work though. Would every SVG prop need to be implemented for natively driving animation?
@zachgibson I don't really know. But, I think somehow on the native side we need to allow animated values as props, and then register onchange listeners to actually set the values on the shadow nodes and invalidate up the tree to queue a re-rendering of the bitmap. It might be a really minor change, just have to figure out how to glue the listener and the animated/shadow nodes. @vjeux Could you mentor here a bit, would be much appreciated!
I’m sorry but I haven’t been working on react native for 1.5 years now... i won’t be able to spend time on this :(
Alright, no probs. I managed to animate opacity of a Svg Rect with the native driver in iOS, by removing this one method 😃 https://github.com/react-native-community/react-native-svg/blob/152e839126e66a708a9492d203ef7fb4302e1030/ios/ViewManagers/RNSVGNodeManager.m#L27-L30
@msand I had just done the same actually, but had been trying to animate the radius of SVG circle (getting RCTConvert errors still)!
It looks like viewNameForRectTag
will come back nil
here, unless the view gets added to the map of shadowViews
here if you remove that line.
RCTShadowView
seems to have a tonne of yoga stuff, so still not sure what makes the most sense to do.
Yeah I was having some issues with animating the props represented as NSString, haven't figured out how to deal with that yet. But CGFloat based ones seem to work with just removing that one method to get the shadow nodes registered, trying to figure out how to get it to skip the YGValue conversion now.
ah good observation! I'm stuck on the same 🤔
I think any property name collisions with RCTShadowView need to be pre-fixed with rnsvg or something similar. Or now that I think about it, the removed method probably needs to exist, and give a shadow node corresponding to the element.
@msand this looks pretty suspect: https://github.com/react-native-community/react-native-svg/blob/master/elements/Circle.js#L35, for example.
It might not actually have any affect actually, as createAnimatedComponent
does some HOC magic.
Ah so yes, that was part of the problem actually! Just as a proof of concept I had changed the exported view property type of r
on RNSVGCircle
to NSNumber
, and then converted the number value to a string inside the setter of r
. But the toString
in Circle.js
, would've meant a string would've still been passed to the native side for the first render of the circle, thus failing the props validation.
But with that, animating the radius works with useNativeDriver
!
I had my suspicions before, but I'm pretty sure this is not anything to do with property name collisions before with RCTShadowView
, as I had hardcoded the 'viewName' to test this.
Most of the properties are strings, because they support units and they depend on e.g. the font-size and clip bounds so need to be passed as such to the native side for the css resolution logic. E.g. Rect has a name collision for width and height with RCTShadowView causing it to use the YGValue converter.
It looks like the string property aspect is a bit of a pain point! I'll have a look some of the string properties that support units in the documentation as I'm not too familiar with that myself. Having a separate prop for each unit like so fontSizePx
and fontSizeEm
would allow us to get around this issue by the looks of it for example, and use numbers for these props.
However it might actually be useful to have a Animated.convertToString
native method where you could do something like this animatedValue.convertToString('${value}px')
, and you'd pass that to your fontSize
prop. I'm not sure if this would be useful for anything else outside of react-native svg? 🤔
I've managed to hack together a proof of concept for android as well, but I'm really hoping it's the wrong approach and that there is some better way of getting it to work, using this approach it would have to create a View/ViewGroup for each node, at least it doesn't have to be attached to the layout tree, but its silly to have it, when all I use it for is to get the id / shadow node corresponding to it. Also the queueing of the rendering might not be as efficient as it should be. Any feedback would be much appreciated! https://github.com/msand/react-native-svg/commit/fbd65912512aa74c00d13d32a26c1f7b3e538b46 (Edit force-pushed some missing files)
@joshyhargreaves I have a PR to react-native for string interpolation on iOS now 😄https://github.com/facebook/react-native/pull/18187 Waiting for some feedback, and if good will port it to Android as well. Allowing us to animate all of the string props with unit support. Added the missing ReactProps to Android as well https://github.com/msand/react-native-svg/commit/a3c9aa287257eb6ef8a6853dded0a490b0b1830c
import React, { Component } from 'react';
import { StyleSheet, View, Dimensions, Animated } from 'react-native';
import { Svg, Rect } from 'react-native-svg';
const { width, height } = Dimensions.get('window');
const AnimatedRect = Animated.createAnimatedComponent(Rect);
function getInitialState() {
const anim = new Animated.Value(0);
const fillOpacity = anim.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
});
const offset = fillOpacity.interpolate({
inputRange: [0, 1],
outputRange: [0, 10],
});
const strokeOpacity = offset.interpolate({
inputRange: [0, 5],
outputRange: [0, 1],
extrapolateRight: 'clamp',
});
const strokeWidth = strokeOpacity.interpolate({
inputRange: [0, 1],
outputRange: ['0', '5'],
});
return { anim, fillOpacity, offset, strokeOpacity, strokeWidth };
}
export default class App extends Component {
state = getInitialState();
componentDidMount() {
const { anim } = this.state;
Animated.timing(anim, {
toValue: 1,
duration: 3000,
useNativeDriver: true,
}).start();
}
render() {
const { fillOpacity, offset, strokeOpacity, strokeWidth } = this.state;
return (
<View style={styles.container}>
<Svg width={width} height={height} viewBox="0 0 100 100">
<AnimatedRect
x="5"
y="5"
width="90"
height="90"
stroke="blue"
fill="green"
strokeDasharray="1 1"
strokeWidth={strokeWidth}
strokeDashoffset={offset}
strokeOpacity={strokeOpacity}
fillOpacity={fillOpacity}
/>
</Svg>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#ecf0f1',
},
});
Now with android support for native string interpolation as well: https://github.com/msand/react-native/commit/d3d6e669fcf5e53c7b8c8cb33dbe8e81f587cef3
@msand any update about the PR?
@msand also, can you show an example with
@msand Your answer from 5 March was very helpful. However, it doesn't seem to work when animating rotation properties. (See this issue). Do you have any idea on how to solve this?
v7.0.0 has been released with support for useNativeDriver
@designingSparks You probably need to use setNativeProps with a matrix for the transform for now, you might want to ask @msageryd
@designingSparks I didn't actually cope with the matrix algebra needed, so I'm using a library for the calculations.
https://gitlab.com/epistemex/transformation-matrix-js
This library let's you chain your transforms and gives a matrix back to you. Get hold of a ref to your svg component (I'm moving around G-elements). Use the components of the returned matrix as input to setNativeProps like this:
const matrix = new Matrix()
.translate(x, y)
.rotateDeg(45)
.scaleU(0.5);
this.svgGroupRef.setNativeProps({
matrix: [matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f], //[scaleX, 0, 0, scaleY, x, y],
});
The comment "[scaleX, .." is only roughly what the components means. It serves as a reminder of what goes where, but don't use it as the complete truth of the components.
Edit: I managed to find the original discussion, which helped me with this: https://github.com/react-native-community/react-native-svg/issues/556#issuecomment-354099452
@oriharel For animation of the Path d attribute check here: https://github.com/facebook/react-native/pull/18187#issuecomment-414157937 Although, be aware that the path parsing on android is quite inefficient at the moment, and one of the biggest consumers of cpu when animating the path data. At least from what I could see when profiling that example in android studio.
Can it uses with react-native's Animated