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

Animation #55

Closed
felixakiragreen opened this issue Jun 10, 2016 · 41 comments
Closed

Animation #55

felixakiragreen opened this issue Jun 10, 2016 · 41 comments

Comments

@felixakiragreen
Copy link

Thanks for the great component!

I'm trying to implement some animation with my SVGs. I can't get the built in library to work. I've been playing with react-motion and that kind of works, but the looping is messy and I don't only want to do springs.

Any ideas?

@magicismight
Copy link
Collaborator

Animation is in my TODO list.

@felixakiragreen
Copy link
Author

Perhaps I could help with it?

@felixakiragreen
Copy link
Author

Hey, no worries. I got it working by doing this:

import AnimatedImplementation from 'AnimatedImplementation'

import Svg, {
  G,
  Path,
} from 'react-native-svg'

const A = {
  Svg: AnimatedImplementation.createAnimatedComponent(Svg),
  G: AnimatedImplementation.createAnimatedComponent(G),
  Path: AnimatedImplementation.createAnimatedComponent(Path),
}

@matc4
Copy link

matc4 commented Jun 23, 2016

Hello @DUBERT,

What is AnimatedImplementation?

Thanks!

@felixakiragreen
Copy link
Author

It's React-Native's built in Animation library

@beeirl
Copy link

beeirl commented Aug 19, 2016

@DUBERT Is there any possibility to provide a code example of your solution? Is your approach quite equal to the Svg's <animate /> synatx?

@felixakiragreen
Copy link
Author

felixakiragreen commented Aug 19, 2016

No @Druux , I'm following the React Native animated syntax. Here is an example from my app:

(I pulled out a lot of excess code that isn't related to the animation)

import React, { Component } from 'react'
import {
  View,
} from 'react-native'
import RNAnimated from 'Animated'
import AnimatedImplementation from 'AnimatedImplementation'

import Svg, {
  G,
  Path,
} from 'react-native-svg'

const Animated = {
  ...RNAnimated,
  G: AnimatedImplementation.createAnimatedComponent(G),
}


export default class Example extends Component {

  constructor(props) {
    super(props)

    this.state = {
      B1: new Animated.Value(0),
    }
  }

  componentDidMount() {
    this.runAnimation()
  }

  runAnimation() {
    Animated.stagger(50, [

      this.timing('B1'),

    ]).start()

    // I had more here but just cut them out

  }

  timing(id) {
    return Animated.sequence([
      Animated.timing(
        this.state[id],
        { toValue: 1, duration: 150 }
      ),
      Animated.timing(
        this.state[id],
        { toValue: 0, duration: 150 }
      )
    ])
  }

  interp(id, value) {
    return this.state[id].interpolate({
      inputRange: [0, 1],
      outputRange: value,
    })
  }

  render() {

    return (
      <Svg
        height={191}
        width={259}
      >
        <Animated.G
          originX={20}
          y={this.interp('B1', [0, -12])}
          x={this.interp('B1', [0, 2])}
          scaleX={this.interp('B1', [1, .85])}
        >
          <Path d='M36.9 23.6c0-4.07-1.32-7.34-4-9.8-2.7-2.5-6.24-3.75-10.6-3.75H1.74v57.9l18.58-.02c6.3 0 11.3-1.5 15-4.48 3.67-3 5.5-7.06 5.5-12.15 0-4.15-1.12-7.43-3.38-9.84l-.42-.42c-2.27-2.15-5.55-3.55-9.84-4.2 3.27-1.34 5.72-3.13 7.36-5.34 1.6-2.22 2.4-4.85 2.4-7.9zm-21.68 8.7v-11h2.52c1.85 0 3.24.43 4.16 1.28.9.84 1.37 2.1 1.37 3.77 0 1.86-.58 3.3-1.73 4.37-1.2 1.06-2.8 1.6-4.82 1.6h-1.5zm0 10.75h4.34c2.14 0 3.92.6 5.36 1.85 1.43 1.22 2.15 2.85 2.15 4.9 0 2-.7 3.64-2.1 4.9-1.43 1.25-3.23 1.87-5.4 1.87H15.2V43.05z'/>
        </Animated.G>

      </Svg>

    )
  }
}

Disclaimer: I'm sure this isn't the best way to do this. But it worked for me.
I set up my own interpolate function so that I could pass the two values into the render. I also used IDs because I had 20 different animations going.

Good luck. Don't know if this will help you.

@beeirl
Copy link

beeirl commented Aug 19, 2016

Alright - that were my thoughts how you solved this as well @DUBERT.
The issue I am currently facing is that I can't assign an Animated.Value() to the svg attribute strokeDasharray because it needs an array. And it isn't possible to convert an Animated.Value() to an array with this syntax: strokeDasharray={[this.state.strokeDasharray]}. (Tried out some more workarounds for that specific case, but it seems like there is no one).

I should probably go ahead with requestAnimationFrame()

@felixakiragreen
Copy link
Author

can you post the values that you're trying to animate?

@FlaviooLima
Copy link

@DUBERT how did you make it to work?
I can use your theory with react native ART but not with react-native-svg.
I use the same code and same components and the only thing i change was the components from react native ART and react-native-svg, and in ART works but no success in svg.

@felixakiragreen
Copy link
Author

felixakiragreen commented Sep 15, 2016

@flaviotobi do you have your source code on a repo somewhere that I can take a look?
If not, I will consider posting a working example of SVG animation because it's gotten a lot of questions.

@FlaviooLima
Copy link

FlaviooLima commented Sep 16, 2016

Thanks for the reply =D

This code was tested in iOS, using :
react-native: 0.31.0
react-native-svg: 4.1.5
and
react-native: 0.33.0
react-native-svg: 4.3.1

Animation working with ART: §
https://rnplay.org/apps/EudZ0g

Animation not working with SVG:
https://rnplay.org/apps/95DeZA

@julesmoretti
Copy link

Would love also some help here. I am trying to animate a path to create an animated pie chart... But right now I am not able to get the animated to work with it, unless I use a separate setInterval and forceUpdate() which I am sure = bad!

So here is what I have right now and hopefully someone can help me a tad with this :) thank you.

import React from 'react';

import {
	View,
	StyleSheet,
	Animated,
	Easing,
} from 'react-native';

import Svg,{
	G,
	RadialGradient,
	Path
} from 'react-native-svg';

// create custom animatable component for paths
let AnimatedPath = Animated.createAnimatedComponent(Path);

const SCREEN_SIZE = 200;

export class AnimatedPie extends React.Component {

	constructor(props, context) {
		super(props, context);

		// set state with animatable value
		this.state = {
			piePercentage: new Animated.Value(100),
		};
	}

	componentDidMount() {

		// start animation on mounting going from 100 to 0
		// full pie chart down to empty pie
		Animated.timing(
			this.state.piePercentage,
			{
				toValue: 0,
				easing: Easing.linear,
				duration: 10000,
			}
		).start();
	}

	polarToCartesian(centerX, centerY, radius, angleInDegrees) {
		var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;

		return {
			x: centerX + (radius * Math.cos(angleInRadians)),
			y: centerY + (radius * Math.sin(angleInRadians))
		};
	}

	// function that spits out a pie chart path shape using a range to determine the pie volume
	progress( timeRange, time ) {

		let center = SCREEN_SIZE / 2;

		if ( timeRange && time ) {

			let x = center;
			let y = center;
			let radius = center;
			let startAngle = 0;
			let endAngle = 360 / timeRange * time

			let start = this.polarToCartesian(x, y, radius, startAngle);
			let end = this.polarToCartesian(x, y, radius, endAngle);

			let largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";

			let d = [
					"M", radius, 0,
					"L", radius, radius,
					"L", end.x, end.y,
					"M", end.x, end.y,
					"A", radius, radius, 0, largeArcFlag, 0, start.x, start.y,
					"Z"
			].join(" ");
			return(d);
		} else {
			return("M0 0");
		}
	}

	render() {
		return (
			<Svg
				width={SCREEN_SIZE}
				height={SCREEN_SIZE}>

				<AnimatedPath
					d={this.progress( 100, this.state.piePercentage)} // this is not working as is...
					fill="#fe5d4d"
					stroke="none"
				/>

			</Svg>
		)
	}
}

@joshuapinter
Copy link
Contributor

Were you ever able to get an AnimatedPath working? I'm currently trying to do something similar, but even easier with just changing the radius of a circle, without using styles and scale.

// 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 = 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

@ClinicalCharles
Copy link

Some SVG elements are animatable but others aren't. SVG is animatable and I think G may be as well but Path isn't.
I performed a basic fade animation of a path by making the enclosing SVG element animatable and operating on it like this:

import Svg,{
    Line,
    Path,
    Rect,
    Text as SVGText
} from 'react-native-svg';

import RNAnimated from 'Animated'
import AnimatedImplementation from 'AnimatedImplementation'

const Animated = {
  ...RNAnimated,
  Svg: AnimatedImplementation.createAnimatedComponent(Svg),
}

<Animated.Svg height={Height} width={Width} 
           style={{position: 'absolute', opacity: this.state.fadeAnim}}>  
          <Path
            d={linePath}
            stroke='black'
            fill="none"
            strokeWidth={2}
          />
        </Animated.Svg>

If you want to do anything more complex then I recommend ART.
There are some really useful articles available at http://browniefed.com/

@TheDirtyCalvinist
Copy link

It appears that Path is animatable via the setNativeProps function.

@keksipurkki
Copy link

@TheDirtyCalvinist Could you elaborate, please?

@TheDirtyCalvinist
Copy link

By setting a listener on the animation that calculates a new path and calls this.pathComponent.setNativeProps({ d });, some semblance of an animation can be had. The Path component does not need to be wrapped in Animated.

@edo1493
Copy link

edo1493 commented May 9, 2017

Hey @TheDirtyCalvinist, how would you interpolate a d path?

@TheDirtyCalvinist
Copy link

It would depend on the animation you are trying to achieve.

@edo1493
Copy link

edo1493 commented May 10, 2017

@TheDirtyCalvinist In my case, I have a piechart and when I highlight a slice, the radius gets bigger, so I'd like to animate the path and make it grow smoothly.

At this point, I am not sure, if the best approach is to animate the path or even the radius.

@keksipurkki
Copy link

@edo1493, I figured out how to make animations work. The process of generating the intermediate paths is called tweening and there is actually a library that lets you do it given two paths: art (https://www.npmjs.com/package/art).

I adapted the code from this demo: https://medium.com/the-react-native-log/animated-charts-in-react-native-using-d3-and-art-21cd9ccf6c58

See: http://ix.io/tgW/javascript

@edo1493
Copy link

edo1493 commented May 10, 2017

@keksipurkki I don't use ART, cause it doesn't support click events. ;)

@keksipurkki
Copy link

keksipurkki commented May 10, 2017

@edo1493, Yeah, but take a look at the code, it uses react native SVG for rendering and ART for tweening. I have implemented a clickable SVG pie chart ;-P

AFAIK, the tweening functionality could be incorporated into react native svg. It's pure computation with no dependencies on a particular DOM implementation.

@edo1493
Copy link

edo1493 commented May 10, 2017

@keksipurkki Yes, I know that blog post really well. I just re-adapted the whole thing without ART. I am using the onClick events to highlight the slice and make it bigger, it would be nice to have an Animation when the slice gets bigger.

@keksipurkki
Copy link

@edo1493 See my edited comment above.

@edo1493
Copy link

edo1493 commented May 10, 2017

I tried the tweening too in the past couple of months, but it was breaking things for me. Adding an eventListener as @TheDirtyCalvinist was mentioning (also here: http://stackoverflow.com/questions/39094349/how-to-make-svg-animation-with-animated-in-react-native) seemed a smoother solution.

However, I am not sure how to interpolate my next path, which I am getting from componentWillReceiveProps.

@keksipurkki
Copy link

keksipurkki commented May 10, 2017

@edo1493 Yup. For me the problem is currently that orientation change screws up the chart.

@joshuapinter
Copy link
Contributor

Hey All, I just wanted to follow up from my comment before.

I did a bit more tinkering and found a very workable solution using addListener and 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.

@toearth
Copy link

toearth commented May 19, 2017

I try keksipurkki's solution, it works well.

@edo1493
Copy link

edo1493 commented May 22, 2017

I am trying to animate a slice of a piechart, in this way:

class AnimatedSlice extends React.Component {

  constructor(props) {
    super(props);

    this.state = { path: new Animated.Value(this.props.d) };

    this.state.path.addListener( (path) => {
      this.mySlice.setNativeProps({ d: path.value.toString() });
    });

  }

  componentWillReceiveProps(nextProps) {
    let value = new Animated.Value(nextProps.d);
    setTimeout( () => {
      Animated.spring( this.state.path, { toValue: value, friction: 3 } ).start();
    });
  }

  render() {
    let path = this.props.d;
    return (
      <AnimatedPath
        ref={ ref => this.mySlice = ref }
        fill={this.props.color}
        fillOpacity={1}
        d={path}
        onPress={() => this.props.onPress()}/>
    )
  }
}

module.exports = AnimatedSlice;

Do you guys have any tips? Nothing is moving here. I just want a smooth animation when a new path comes in. I have chosen spring with some friction, just to give it a go, it's not the animation that I want. If I can animate it in any way first, it would be great.

@TheDirtyCalvinist @joshuapinter

@ethantran
Copy link

Hey guys, I managed to figure out how to animate almost every component and their props with setNativeProps. The key is to format the value correctly and I had to look at the code in https://github.com/react-native-community/react-native-svg/tree/master/lib/extract to get it right. All of the transform props had to be converted into a matrix for example. For path data, I used D3 to create paths and interpolate paths. You can see it in action at https://exp.host/@ethantran2/react-native-examples and find the messy but good enough code at https://github.com/ethantran/react-native-examples

@FMCorz
Copy link

FMCorz commented Oct 9, 2017

It'd be nice if the information found in this issue was transformed into documentation. I have not found any other documentation regarding how to animate SVG parts.

In someone is looking at interpolating colours, I've managed to make it work. Here is a snippet which is has to be mixed with the examples found above:

import extractProps from 'react-native-svg/lib/extract/extractProps';

...

const colour = new Animated.Value(0);
colour.addListener(v => {

    const c = colour.interpolate({
      inputRange: [0, 1],
      outputRange: ['black', 'red']
    });

    // Make sure that the ref was acquired.
    if (this.myRef) {

      // Convert the interpolation to a value, and assign to prop name.
      const props = {
        fill: c.__getValue()
      }

      // Conver the properties to native properties.
      const nativeProps = extractProps(props, this.myRef);

      // Finally send the properties to the object.
      this.myRef.setNativeProps(nativeProps);
    }
});

As @ethantran mentioned, some props need to be converted to their native equivalents. However, a colour interpolation first needs to be converted to a value or the extractProps chain will not know what to do with it. So the trick in the above example is to use __getValue(). Perhaps __getNativeValue() is better, but the former worked for me.

@ricklove
Copy link

@ethantran Well done! Very Nice! 👏👏👏

I didn't even know using D3 was doable. Will be using your examples for sure. 🤜🤛

@ma-pe
Copy link

ma-pe commented Feb 13, 2018

@FMCorz Thanks for the inspiration. This can be even easier if you just want to set the fill-color:

import extractBrush from 'react-native-svg/lib/extract/extractBrush';
...
const colour = new Animated.Value(0);
colour.addListener(v => {
     const c = color.interpolate({
        inputRange: [0, 1],
        outputRange: ['black', 'red'],
      });

      // Make sure that the ref was acquired.
      if (this.maskRectRef) {
        this.maskRectRef.setNativeProps({ fill: extractBrush(c.__getValue()) });
      }
});

and rendering:

    const c = color.interpolate({
      inputRange: [0, 1],
      outputRange: ['black', 'red'],
    });
    return (
          <Rect
            ref={ref => (this.maskRectRef = ref)}
            fill={c.__getValue()}
            ...
          />
     );
...

@YaweiZhang-930
Copy link

@ethantran Hi I am new to react native and I'm trying to build This Example in react native. Basically it's a line chart with real-time data. Would you mind sharing some thoughts? Much appreciated!

@YaweiZhang-930
Copy link

@DUBERT Hi I am new to react native and I'm trying to buildThis Example in react native. Basically it's a line chart with real-time data. Would you mind sharing some thoughts? Much appreciated!

@rantoinne
Copy link

@DUBERT how do we animate a pie chart so that each pie of the chart renders in a circular way.
I have been on this since a lot of days and have tried dozens of ideas but unable to achieve a perfect animation.
Can you help me with this.??

@xiawaner
Copy link

xiawaner commented Nov 1, 2018

@DUBERT我们如何设置饼图的动画,以便图表的每个饼以圆形方式呈现。
很多天以来我一直在这方面,并尝试过几十个想法,但无法实现完美的动画效果。
你能帮帮我吗?

Study this library and try it out

https://github.com/JesperLekland/react-native-svg-charts
https://github.com/JesperLekland/react-native-svg-charts-examples

@rantoinne
Copy link

Have tried these too. Actually I also need the pies to be touchable. Using it caused a lot of bugs while integrating touchables though the docs support that the library has it.

@rantoinne
Copy link

rantoinne commented Nov 6, 2018

Wrapping the path component in a new class will help us achieve functionality for adding a delay for successive mappings.. I did so to animate my pie chart such that each of its pie are visible one after the other after a delay. Thanks for all the help I got from here. :-)

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