简体   繁体   中英

Animate SVG Arc Path in React

I'm using React, and what I want to achieve is to have an animation on SVG arc path when they change. Basically, I've a gauge that show a certain value between 0 and 100, and the value can change (in the following example it changes every second).

I've created this codepen that simulate what I want (code below): https://codepen.io/Gesma94/pen/oJvjwe

As you can see in the example, I've a Gauge created with d3 inside an SVG, where the blue bar can take more or less space in time; as you can see, when the Gauge is re-rendered, the new blue bar is just rendered, without any animation between the "old point" and "new point".

What I would like to achieve is having a smooth movement between the point the bar was before, and the point where the bar is going to be (hope I've been clear).

class MyComponent extends React.Component {
   render() {
      console.log("Rendering");
      const value = (this.props.value * Math.PI / 100) - Math.PI/2;

      const currentValueFilledCircle = d3.arc()
      .innerRadius(37.5)
      .outerRadius(49.5)
      .startAngle(-Math.PI/2)
      .endAngle(value)(null);

      const currentValueEmptyCircle = d3.arc()
      .innerRadius(37.5)
      .outerRadius(49.5)
      .startAngle(value)
      .endAngle(Math.PI/2)(null);

      return (
         <div style={{width: "300px", height: "300px"}}>
            <svg height="100%" width="100%" viewBox="-50 -50 100 100">
               <g>
                  <path d={currentValueFilledCircle} fill="blue" />
                  <path d={currentValueEmptyCircle} fill="gray" />
               </g>
            </svg>
         </div>
      );
   };
}

class App extends React.Component {
   constructor() {
      super();
      this.value = 77;
   }

   componentDidMount() {
      this.interval = setInterval(() => {
         const diff = Math.floor(Math.random() * 7) - 3;
         let newCurrentValue = this.value + diff;

         if (newCurrentValue > 100) newCurrentValue = 100;
         else if (newCurrentValue < 0) newCurrentValue = 0;

         this.value = newCurrentValue;
         this.forceUpdate();
      }, 500);
   }

   render() {
      return (<MyComponent value={this.value} />)
   }
}

ReactDOM.render(<App />, document.getElementById('app'));

So, I struggled for some times, but I found a solution using react-move/Animate : https://react-move.js.org/#/documentation/animate

Since I couldn't make it work on Codepen, I recreate the situation in a sandbox, there it is: https://codesandbox.io/embed/0qyrmyrw

The gist is the following part of code:

<Animate
  start={{ value: this.props.value }}
  update={{
    value: [this.props.value], // Before the sqaure brackets!!
    timing: { duration: 750 }
  }}
>
  {(state: { value: number }) => {
    const scaledValue = (state.value * Math.PI) / 100 - Math.PI / 2;
    const currentValueFilledCircle = arc()
      .innerRadius(37.5)
      .outerRadius(49.5)
      .startAngle(-Math.PI / 2)
      .endAngle(scaledValue)(null);

    const currentValueEmptyCircle = arc()
      .innerRadius(37.5)
      .outerRadius(49.5)
      .startAngle(scaledValue)
      .endAngle(Math.PI / 2)(null);

    return (
      <React.Fragment>
        <path d={currentValueFilledCircle} fill="blue" />
        <path d={currentValueEmptyCircle} fill="gray" />
      </React.Fragment>
    );
  }}
</Animate>

Basically, by writing update={{value: [this.props.value] ... }} , the Animate Component just run a set of render() method with different values, from the previous to the current, and so it gives a smooth-movement effect.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM