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):
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):
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 svgd
parameters declared (polartocartesian
,describearc
).then react-native
interpolate
used onthis.state.progress
interpolate change inthis.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
Post a Comment