javascript - Animating react-native-svg dash length of a <Circle /> -


hey i'm trying achieve effect similar to: https://kimmobrunfeldt.github.io/progressbar.js (circle one)

i able animate svg elements before using setnativeprops approach, failing me time dash length, below gif demonstrating current behaviour (circle change full semi full when receives new props):

enter image description here

essentially trying animate change instead of flicking in, below full source rectangular progress bar, basic idea is uses circle , strokedasharray in order show circular progress, receives currentexp , nextexp values characters experience in order calculate percentage left before reach next lvl.

component uses pretty standard set of elements, besides few dimension / animation , colour props stylesheed , styled-components library styling.

note: project importing library expo.io it's react-native-svg

import react, { component } "react"; import proptypes "prop-types"; import styled "styled-components/native"; import { animated } "react-native"; import { svg } "expo"; import { colour, dimension, animation } "../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: extended discussion , more examples (similar approach working different elements) available via issue posted react-native-svg repo: https://github.com/react-native-community/react-native-svg/issues/451

it quite simple when know how svg inputs work, 1 of problems react-native-svg (or svg inputs, in general, doesn't work angle), when want work on circle need transform angle inputs takes, can done, writing function such (you don't need memorize or totally understand how transformation works, 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 add function can give d props in right 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 great, have function (describearc) gives perfect parameter need describe path (an arc of circle): can define path as:

<animatedpath d={_d} stroke="red" strokewidth={5} fill="none"/> 

for example, if need arc of circle of radius r between 45 degrees 90 degrees, define _d as:

_d = describearc(r, r, r, 45, 90); 

now know how svg path works, can implement react native animation, , define animated state such progress:

import react, {component} 'react'; import {view, animated, easing} 'react-native'; import svg, {circle, path} '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;         (var = 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 work want

  • at top of componet, write,

animatedpath = animated.createanimatedcomponent(path);

  because path imported react-native-svg not native react-native component , turn animated this.

  • at constructor defined progress animated state should change during animation.

  • at componentdidmount animation process started.

  • at beginning of render method, 2 functions needed define svg d parameters declared (polartocartesian , describearc).

  • then react-native interpolate used on this.state.progress interpolate change in this.state.progress 0 1, change in d parameter. however, there 2 points here should bear in mind:

       1- change between 2 arcs different lengths not linear, linear interpolation angle 0 360 not work like, result, better define animation in different steps of n degrees (i used 1 degrees, u can increase or decrease if needed.).

       2- arc cannot continue 360 degrees (because equivalent 0), better finish animation @ degree close not equal 360 (such 359.9)

  • at end of return section, ui described.


Comments

Popular posts from this blog

resizing Telegram inline keyboard -

command line - How can a Python program background itself? -

php - "cURL error 28: Resolving timed out" on Wordpress on Azure App Service on Linux -