Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use with animation #180

Closed
anhuiliujun opened this issue Nov 17, 2016 · 59 comments
Closed

use with animation #180

anhuiliujun opened this issue Nov 17, 2016 · 59 comments

Comments

@anhuiliujun
Copy link

Can it uses with react-native's Animated

@AlbertBrand
Copy link
Contributor

Yes, it can. Probably you want to create Animated variants of each component (with Animated.createAnimatedComponent) to use Animated.Value` objects as properties.

@grubstarstar
Copy link

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!

@AlbertBrand
Copy link
Contributor

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?

@grubstarstar
Copy link

Thanks for your response. I tried a few approaches this morning which I'll share with you. There approaches are as follows:

  1. using setState
  2. attempting to use setNativeProps
  3. using interpolation

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.

screen shot 2016-12-16 at 11 05 46 am

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>
      )
   }

}

@Nimmimarco
Copy link

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?

@Lyonsclay
Copy link

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;

@ameliabradley
Copy link

Getting a very similar issue when attempting to animate using color values

screen shot 2017-02-26 at 12 33 33 am

@joshuapinter
Copy link
Contributor

joshuapinter commented Mar 3, 2017

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:

screenshot 2017-03-03 11 00 44

@anhtuank7c
Copy link

@joshuapinter
Try:

<AnimatedCircle 
    cx="250" 
    cy="250" 
    r={`${this.state.circleRadius}`} 
    fill="black" 
/>

@joshuapinter
Copy link
Contributor

joshuapinter commented Mar 10, 2017

@anhtuank7c Thanks for the suggestion but now I get this error message:

Invalid float: "[object Object]"

screenshot 2017-03-10 08 29 29

@udfalkso
Copy link

udfalkso commented May 3, 2017

@grubstarstar @LeeBradley I'm having exactly the same issue. Did you ever find a workable solution? Thanks!

I opened this before finding this thread:
#326

@grubstarstar
Copy link

grubstarstar commented May 3, 2017

@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.

@udfalkso
Copy link

udfalkso commented May 3, 2017

Thanks @grubstarstar. I'm already doing the wrapping View trick for opacity changes, but I can't do that for fill color.

@grubstarstar
Copy link

@udfalkso nah, true. Is setState too slow? I managed a pretty smooth radial animation just using that.

@joshuapinter
Copy link
Contributor

@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.

@udfalkso
Copy link

udfalkso commented May 4, 2017

@grubstarstar Yes unfortunately in my case I'm doing this for many paths at the same time (20+) and things grind to a halt.

udfalkso added a commit to udfalkso/react-native-svg that referenced this issue May 4, 2017
props being set were not passing through extractProps, leading to
various mismatches in the values.
software-mansion#180
software-mansion#326
@udfalkso
Copy link

udfalkso commented May 4, 2017

@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: #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 added a commit to udfalkso/react-native-svg that referenced this issue May 4, 2017
props being set were not passing through extractProps, leading to
various mismatches in the values.
software-mansion#180
software-mansion#326
udfalkso added a commit to udfalkso/react-native-svg that referenced this issue May 4, 2017
props being set were not passing through extractProps, leading to
various mismatches in the values.
software-mansion#180
software-mansion#326
@grubstarstar
Copy link

@udfalkso good work! I'll take a look at that...

@joshuapinter
Copy link
Contributor

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:

circle animation with setnativeprops

And this is being rendered on a very complex component where using setState isn't fluid at all.

@dk0r
Copy link

dk0r commented May 27, 2017

@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
Error

@ethantran
Copy link

If you guys need examples on how to get most of the components animated, check my comment at #55

@anhtuank7c
Copy link

@joshuapinter Your solution working good while update r, i tried to update rotate but nothing changed :(

@kuongknight
Copy link

@dk0r I found same error with Polyline :(

@joshuapinter
Copy link
Contributor

@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!

@zachgibson
Copy link

Do y’all think we’ll ever be able to offload animations on the native side via useNativeDriver?

@msand
Copy link
Collaborator

msand commented Feb 22, 2018

Its fully possible, just have to integrate with the driver and the declarative animation passing ;) You wanna take a shot at it?

@msand
Copy link
Collaborator

msand commented Feb 23, 2018

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.

@joshjhargreaves
Copy link

ah good observation! I'm stuck on the same 🤔

@msand
Copy link
Collaborator

msand commented Feb 23, 2018

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.

@joshjhargreaves
Copy link

joshjhargreaves commented Feb 23, 2018

@joshjhargreaves
Copy link

It might not actually have any affect actually, as createAnimatedComponent does some HOC magic.

@joshjhargreaves
Copy link

joshjhargreaves commented Feb 23, 2018

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.

@msand
Copy link
Collaborator

msand commented Feb 23, 2018

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.

@joshjhargreaves
Copy link

joshjhargreaves commented Feb 23, 2018

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? 🤔

@msand
Copy link
Collaborator

msand commented Feb 24, 2018

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! msand@fbd6591 (Edit force-pushed some missing files)

@msand
Copy link
Collaborator

msand commented Mar 4, 2018

@joshyhargreaves I have a PR to react-native for string interpolation on iOS now 😄facebook/react-native#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 msand@a3c9aa2

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',
  },
});

@msand
Copy link
Collaborator

msand commented Mar 5, 2018

Now with android support for native string interpolation as well: msand/react-native@d3d6e66

@oriharel
Copy link

oriharel commented May 5, 2018

@msand any update about the PR?

@oriharel
Copy link

oriharel commented May 5, 2018

@msand also, can you show an example with 'd' attribute?

@designingSparks
Copy link

@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?

@msand
Copy link
Collaborator

msand commented Sep 15, 2018

v7.0.0 has been released with support for useNativeDriver

@msand
Copy link
Collaborator

msand commented Sep 15, 2018

@designingSparks You probably need to use setNativeProps with a matrix for the transform for now, you might want to ask @msageryd

@msageryd
Copy link

msageryd commented Sep 17, 2018

@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:
#556 (comment)

@msand
Copy link
Collaborator

msand commented Sep 17, 2018

@oriharel For animation of the Path d attribute check here: facebook/react-native#18187 (comment)
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.

@oriharel
Copy link

@msand Thanks! what version of React Native I need in order to use react-native-svg v7.0.3?

@msand
Copy link
Collaborator

msand commented Oct 10, 2018

I've mostly tested using 0.56 and 0.57, but as long as it builds and runs without exceptions you should be fine. If you have any issues with any recent version then please report it.

@msand
Copy link
Collaborator

msand commented Oct 11, 2018

I've implemented support for animation of transform styles using the same syntax as for react-native views now (with and without useNativeDriver): fb4e877
and the same for x and y on Text and TSpan #803 (comment)

import React from "react";
import { Dimensions, Animated } from "react-native";
import Svg, { Text, TSpan, G } from "react-native-svg";

const { width, height } = Dimensions.get("window");

const AnimatedG = Animated.createAnimatedComponent(G);

class NativeAnimGTransform extends React.Component {
  state = {
    anim: new Animated.Value(0),
  };

  componentDidMount() {
    this.animate(this.props.value);
  }

  componentDidUpdate({ value }) {
    this.animate(value);
  }

  animate = value =>
    Animated.timing(this.state.anim, {
      useNativeDriver: true,
      duration: 4000,
      toValue: value,
    }).start();

  render() {
    const { anim } = this.state;

    return (
      <Svg width={width} height={height}>
        <AnimatedG
          style={{
            transform: [
              {
                translateY: anim.interpolate({
                  inputRange: [0, 1],
                  outputRange: [0, 100],
                }),
              },
            ],
          }}
        >
          <Text>
            <TSpan>Test</TSpan>
          </Text>
        </AnimatedG>
      </Svg>
    );
  }
}

export default function App() {
  return <NativeAnimGTransform value={1} />;
}

@msand
Copy link
Collaborator

msand commented Oct 11, 2018

Now with useNativeDriver animation support for all number accepting properties (without need for my fork of react-native, works at least in 0.57 straight away): 864d761

@msand
Copy link
Collaborator

msand commented Oct 15, 2018

@oriharel I just tried running react-native init oldVersion --version v0.50.4 upgraded sdk, gradle / build tools versions, and then yarn add react-native-svg resulting in:

		"react": "16.0.0",
		"react-native": "0.50.4",
		"react-native-svg": "^7.1.2",

And it seemed to work fine even with useNativeDriver: true
So it still seems be compatible with the versions given in the readme at least

@msand
Copy link
Collaborator

msand commented Oct 19, 2018

I've implemented the SVGLength interface now, and optimised the code for the case when the arguments are numbers rather than strings. https://github.com/react-native-community/react-native-svg/compare/SVGLength
In properties where the spec allows for SVGLengthList it now accepts single values as either number or string, and arrays of values which can even be mixed, as long as each value can be interpreted as a SVGLength. So, if want to optimise your code, you should pass all unit-less lengths/dimensions as Number instead of String, and preferably author your svg content without using units, instead rely on the viewbox attribute together with the width and height. This eliminates representing numbers as strings, which is inefficient. And, thanks to these changes, the animations do not cause a redundant double > string > double round-trip.

@oriharel
Copy link

oriharel commented Nov 6, 2018

Regarding d attribute animation in the element, I managed to animate the famous Batman example but needed a very ugly hack - I commented out the checkPattern() method inside the AnimatedInterpolation.js of the react-native node module. Check it out here. It makes me wonder, however, if the animation works, why should we be limited by the harsh checkPattern() criteria.

@msand
Copy link
Collaborator

msand commented Nov 8, 2018

@oriharel I would expect that one to work, it's probably a bug in the implementation of checkPattern, it should just make sure that there is exactly the same number of substrings which can be interpreted as independent numbers in all strings, such that it can actually produce a string with a number in each place where it is needed.

It is also possible to animate between arbitrary paths in general, but there are more ways than one to do it, so it requires deciding more boundary conditions / dynamics to enable linear interpolation. And that's something which tends to need experimentation and tailor made curves/algorithms to actually looks nice, so it doesn't belong in a simple linear interpolator.

But, at least the batman example should certainly be possible to animate using the js logic in plain react-native, and using the native driver in my fork. Or, now that I think about it, I remember vaguely that the plain react-native limits string interpolation to color strings, and the code you removed just removes that redundant restriction. I'll have to check and test a bit once I have a bit more time for this.

@msand
Copy link
Collaborator

msand commented Dec 9, 2018

Closing this now as everything should be working correctly afaik.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests