Hi everyone, I'm trying to achieve an effect like this: https://kimm...">

Animation: respond to a reaction-native-svg dash with a length of <Circle / ">

Hi everyone, I'm trying to achieve an effect like this: https://kimmobrunfeldt.imtqy.com/progressbar.js (circle)

I managed to successfully compose some svg elements before using the setNativeProps approach, but this time it doesn’t work with the stroke length, below is the gif showing the current behavior (the circle changes from full to half full when it receives new details):

enter image description here

Essentially, I'm trying to animate this change instead of just clicking, below is the full source for this rectangular progress bar, the main idea is that it uses Circle and strokeDasharray to show circular progress, it gets currentExp and nextExp as values for a character’s experience, to calculate the percentage on the left before they reach the next lvl.

The component uses a fairly standard set of elements, in addition to several sizes / animations and color attributes from styles and the styled-components library for styling.

NOTE: the project imports this library from expo.io , but essentially react-native-svg

 import React, { Component } from "react"; import PropTypes from "prop-types"; import styled from "styled-components/native"; import { Animated } from "react-native"; import { Svg } from "expo"; import { colour, dimension, animation } from "../Styles"; const { Circle, Defs, LinearGradient, Stop } = Svg; const SSvg = styled(Svg)` transform: rotate(90deg); margin-left: ${dimension.ExperienceCircleMarginLeft}; margin-top: ${dimension.ExperienceCircleMarginTop}; `; class ExperienceCircle extends Component { // -- prop validation ----------------------------------------------------- // static propTypes = { nextExp: PropTypes.number.isRequired, currentExp: PropTypes.number.isRequired }; // -- state --------------------------------------------------------------- // state = { percentage: new Animated.Value(0) }; // -- methods ------------------------------------------------------------- // componentDidMount() { this.state.percentage.addListener(percentage => { const circumference = dimension.ExperienceCircleRadius * 2 * Math.PI; const dashLength = percentage.value * circumference; this.circle.setNativeProps({ strokeDasharray: [dashLength, circumference] }); }); this._onAnimateExp(this.props.nextExp, this.props.currentExp); } componentWillReceiveProps({ nextExp, currentExp }) { this._onAnimateExp(currentExp, nextExp); } _onAnimateExp = (currentExp, nextExp) => { const percentage = currentExp / nextExp; Animated.timing(this.state.percentage, { toValue: percentage, duration: animation.duration.long, easing: animation.easeOut }).start(); }; // -- render -------------------------------------------------------------- // render() { const { ...props } = this.props; // const circumference = dimension.ExperienceCircleRadius * 2 * Math.PI; // const dashLength = this.state.percentage * circumference; return ( <SSvg width={dimension.ExperienceCircleWidthHeight} height={dimension.ExperienceCircleWidthHeight} {...props} > <Defs> <LinearGradient id="ExperienceCircle-gradient" x1="0" y1="0" x2="0" y2={dimension.ExperienceCircleWidthHeight * 2} > <Stop offset="0" stopColor={`rgb(${colour.lightGreen})`} stopOpacity="1" /> <Stop offset="0.5" stopColor={`rgb(${colour.green})`} stopOpacity="1" /> </LinearGradient> </Defs> <Circle ref={x => (this.circle = x)} cx={dimension.ExperienceCircleWidthHeight / 2} cy={dimension.ExperienceCircleWidthHeight / 2} r={dimension.ExperienceCircleRadius} stroke="url(#ExperienceCircle-gradient)" strokeWidth={dimension.ExperienceCircleThickness} fill="transparent" strokeDasharray={[0, 0]} strokeLinecap="round" /> </SSvg> ); } } export default ExperienceCircle; 

UPDATE: Expanded discussion and other examples (a similar approach working for different elements), available through the release sent to react-native-svg repo: <a3>

+5
source share
3 answers

It's actually pretty simple when you know how SVG inputs work, one of the problems with native-SVG reactions (or SVG inputs, in general, is that it doesn't work with an angle), so when you want work on the circle, you need to transform the angle of the inputs that it takes, you can do this simply by writing a function such as (you definitely don't need to remember or fully understand how the conversion works, this is the standard):

result

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

Then you add another function that can give you the details d in the correct format: function describeArc (x, y, radius, startAngle, endAngle) {

  var start = polarToCartesian(x, y, radius, endAngle); var end = polarToCartesian(x, y, radius, startAngle); var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1"; var d = [ "M", start.x, start.y, "A", radius, radius, 0, largeArcFlag, 0, end.x, end.y ].join(" "); return d; } 

Now this is great, you have a function (describeArc) that gives you the ideal parameter needed to describe your path (circular arc): so you can define PATH as:

 <AnimatedPath d={_d} stroke="red" strokeWidth={5} fill="none"/> 

for example, if you need a circular arc with a radius R between 45 degrees to 90 degrees, simply define _d as:

 _d = describeArc(R, R, R, 45, 90); 

Now that we know everything about how SVG PATH works, we can implement a native animation reaction and define an animated state, such as progress :

 import React, {Component} from 'react'; import {View, Animated, Easing} from 'react-native'; import Svg, {Circle, Path} from 'react-native-svg'; AnimatedPath = Animated.createAnimatedComponent(Path); class App extends Component { constructor() { super(); this.state = { progress: new Animated.Value(0), } } componentDidMount(){ Animated.timing(this.state.progress,{ toValue:1, duration:1000, }).start() } render() { function 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 describeArc(x, y, radius, startAngle, endAngle){ var start = polarToCartesian(x, y, radius, endAngle); var end = polarToCartesian(x, y, radius, startAngle); var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1"; var d = [ "M", start.x, start.y, "A", radius, radius, 0, largeArcFlag, 0, end.x, end.y ].join(" "); return d; } let R = 160; let dRange = []; let iRange = []; let steps = 359; for (var i = 0; i<steps; i++){ dRange.push(describeArc(160, 160, 160, 0, i)); iRange.push(i/(steps-1)); } var _d = this.state.progress.interpolate({ inputRange: iRange, outputRange: dRange }) return ( <Svg style={{flex: 1}}> <Circle cx={R} cy={R} r={R} stroke="green" strokeWidth="2.5" fill="green" /> {/* X0 Y0 X1 Y1*/} <AnimatedPath d={_d} stroke="red" strokeWidth={5} fill="none"/> </Svg> ); } } export default App; 

This simple component will work the way you want.

  • At the top of the component we write

AnimatedPath = Animated.createAnimatedComponent(Path);

because the PATH , which is imported from response-native-svg, is not a native component of native-native, and we turn it into an animated one by this.

  • at constructor we defined progress as an animated state that should change during the animation.

  • at componentDidMount the animation process starts.

  • at the beginning of the render method, two functions are declared that are necessary to determine the SVG d parameters ( polarToCartesian and describeArc ).

  • then the native interpolate reaction is used on this.state.progress to interpolate the change in this.state.progress from 0 to 1, on changing the d parameter. However, there are two points:

    1- the change between two arcs with different lengths is not linear, so linear interpolation from an angle of 0 to 360 does not work as you would like, as a result, it is better to determine the animation at different stages of n degrees (I used 1 degree, u can increase or reduce it if necessary.).

    A 2-arc cannot extend to 360 degrees (because it is equivalent to 0), so it’s better to finish the animation to a degree close to but not equal to 360 (for example, 359.9).

  • At the end of the return section, the user interface is described.

+5
source

Another absolute large library for svg animation is https://maxwellito.imtqy.com/vivus/ It is standalone without dependencies and easy to use.

Perhaps this fits your needs?

+1
source

If you are not tied to the svg library, I think you can check out this library: https://github.com/bgryszko/react-native-circular-progress , this could be a much easier way to achieve what you are looking for.

0
source

Source: https://habr.com/ru/post/1271675/


All Articles