3

I'm building a simple App in react. And I'm trying to change an svg fill color when I click on like button. I've researched around and many developers use React Transition Group as a way, but I'm having hard time understanding how it works in this case.

Each time I click on like button, it increments the number and I want to change the color of svg background when it does. I've tried this way but it doesn't work. What am I doing wrong here? Here is code.

<button onClick={this.props.increment.bind(null,index)}>
        <span>{this.state.portfLikes}</span>
       <CSSTransition
              key="heart"
              classNames="anim"
              timeout={{ enter: 500, exit: 300 }}>
      <span className="like-icon" key="heart-icon"><Like/></span>
       </CSSTransition>
      </button>

Like svg as a component.

import React from 'react'

class Like extends React.Component{
    render() {
        return (
            <svg viewBox="0 0 64 64" width="64px"  xmlns="http://www.w3.org/2000/svg">
            <g id="Layer_1"><g><circle cx="32" cy="32" fill="#a3bed7" r="32"/></g><g opacity="0.2"><g><path d="M49.982,31.003c-0.094-5.522-4.574-10.442-10.107-10.442c-3.2,0-6.019,1.674-7.875,4.131     c-1.856-2.457-4.676-4.131-7.875-4.131c-5.533,0-10.012,4.921-10.107,10.442H14c0,0.034,0.007,0.065,0.007,0.099     c0,0.025-0.007,0.049-0.007,0.076c0,0.155,0.038,0.272,0.045,0.421c0.495,14.071,17.813,19.84,17.813,19.84     s17.572-5.762,18.092-19.818C49.959,31.464,50,31.34,50,31.178c0-0.027-0.007-0.052-0.007-0.076c0-0.036,0.007-0.065,0.007-0.099     H49.982z" fill="#231F20"/></g></g><g><g><path d="M49.982,29.003c-0.094-5.522-4.574-10.442-10.107-10.442c-3.2,0-6.019,1.674-7.875,4.131 c-1.856-2.457-4.676-4.131-7.875-4.131c-5.533,0-10.012,4.921-10.107,10.442H14c0,0.034,0.007,0.065,0.007,0.099c0,0.025-0.007,0.049-0.007,0.076c0,0.155,0.038,0.272,0.045,0.421c0.495,14.071,17.813,19.84,17.813,19.84     s17.572-5.762,18.092-19.818C49.959,29.464,50,29.34,50,29.178c0-0.027-0.007-0.052-0.007-0.076c0-0.036,0.007-0.065,0.007-0.099 H49.982z" fill="#f5f5f5"/></g></g></g><g id="Layer_2"/></svg>
            )
    }
}

export default Like

CSS Note: I've picked opacity as a test to see if the animation works.

.anim-enter {
    opacity: 0.12;

}
.anim-enter.anim-enter-active svg {
   opacity: 0.5;    
   transition: opacity 500ms ease-in;
 }

.anim-exit {
   opacity: 1;
 }

.anim-exit.anim-exit-active {
   opacity: 0.01;
   transition: opacity 300ms ease-in;
 }

I'm also open to suggestions about other performance friendly react animation tools.

Ndx
  • 517
  • 2
  • 12
  • 29
  • 1
    Hands down best SVG animation package I've used here: [React Move](https://github.com/react-tools/react-move). – Chase DeAnda Oct 09 '17 at 18:50
  • Thanks, I'm going to check it out. meanwhile hopefully someone would shed some lights on React Transition Group. It seems very difficult to implement. – Ndx Oct 09 '17 at 18:58
  • Yeah I've never had any luck with React Transition Group. Way to hard for something that should be so simple. – Chase DeAnda Oct 09 '17 at 19:04
  • maybe this is stupid, but could ```:focus``` to control the animation work? – Sebastian Rothbucher Oct 09 '17 at 19:38
  • (i.e. use vanilla CSS animations for ```button:focus #Layer_1``` - just tried that in a codepen and per se, it works... – Sebastian Rothbucher Oct 09 '17 at 19:47
  • It worked! Thanks.I've spent so many hours trying to resolve this only to find out simple vanilla css is the solution. If would like, you can format this in an asnwer so i'd upvote and mark as an answer :) – Ndx Oct 09 '17 at 20:13
  • @ChaseDeAnda: I've looked into the react move and it seems much simple but its used for group of elements not just one. I will def use when I have similar issue. Thank you for the suggestion! – Ndx Oct 09 '17 at 20:14

2 Answers2

3

I see Sebastian has already provided an answer for using css styles and the hover pseudo element. It did seem like you wanted it to happen on a click though.

CSSTransition is only really needed when you want to animate elements as they are added to the DOM. If the items already exist, you just use all the typical css jazz to animate items (transitions and transforms).

Here is an example using react lifecycle functions to add an event listener and animate the heart when a button is clicked.

See the codepen here https://codepen.io/bluesixty/pen/gGzbrj?editors=0111.

Note: there is something goofy with codepen in the callback on the eventlistener. Codepen is not calling animatingDone to change the state back to false. (I tested on my local and it works correctly). Just refresh the window to reset it.

Javascript

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      animate: false
    };
  }


  //------------------------------------------------------------------------
  // Handle the click animations
  //------------------------------------------------------------------------
  componentDidMount() {
    const svgToPulse = this.itemToPulse;
    svgToPulse.addEventListener("animationend", this.animatingDone);
  }
  componentWillUnmount() {
    const svgToPulse = this.itemToPulse;
    svgToPulse.removeEventListener("animationend", this.animatingDone);
  }
  animatingDone() {   
    this.setState({ animate: false });
  }
  pulse() {        
    this.setState({ animate: true });
  }

  render() {
    return (
      <div>
        <button onClick={() => this.pulse()}>Clicky</button>
        <svg
          ref={e => {
            this.itemToPulse = e;
          }}
          className={this.state.animate ? " animate-pulse" : ""}
          viewBox="0 0 64 64"
          width="64px"
          xmlns="http://www.w3.org/2000/svg"
        >
          <g id="Layer_1">
            <g>
              <circle cx="32" cy="32" fill="#a3bed7" r="32" />
            </g>
            <g opacity="0.2">
              <g>
                <path
                  d="M49.982,31.003c-0.094-5.522-4.574-10.442-10.107-10.442c-3.2,0-6.019,1.674-7.875,4.131     c-1.856-2.457-4.676-4.131-7.875-4.131c-5.533,0-10.012,4.921-10.107,10.442H14c0,0.034,0.007,0.065,0.007,0.099     c0,0.025-0.007,0.049-0.007,0.076c0,0.155,0.038,0.272,0.045,0.421c0.495,14.071,17.813,19.84,17.813,19.84     s17.572-5.762,18.092-19.818C49.959,31.464,50,31.34,50,31.178c0-0.027-0.007-0.052-0.007-0.076c0-0.036,0.007-0.065,0.007-0.099     H49.982z"
                  fill="#231F20"
                />
              </g>
            </g>
            <g>
              <g>
                <path
                  d="M49.982,29.003c-0.094-5.522-4.574-10.442-10.107-10.442c-3.2,0-6.019,1.674-7.875,4.131 c-1.856-2.457-4.676-4.131-7.875-4.131c-5.533,0-10.012,4.921-10.107,10.442H14c0,0.034,0.007,0.065,0.007,0.099c0,0.025-0.007,0.049-0.007,0.076c0,0.155,0.038,0.272,0.045,0.421c0.495,14.071,17.813,19.84,17.813,19.84     s17.572-5.762,18.092-19.818C49.959,29.464,50,29.34,50,29.178c0-0.027-0.007-0.052-0.007-0.076c0-0.036,0.007-0.065,0.007-0.099 H49.982z"
                  fill="#f5f5f5"
                />
              </g>
            </g>
          </g>
          <g id="Layer_2" />
        </svg>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));

And the css

.animate-pulse {
  animation: animate-pulse 300ms;
}
@keyframes animate-pulse {
  0% {transform: scale(1.2)}
  50% {transform: scale(1.5)}
  100% {transform: scale(1.2)}
}

How it works

  • Clicking the button sets the animate state to true
  • React responds to the state change and the animate-pulse class is added to the svg element
  • the eventlistener waits for the animation to end and makes the call to set the animate state back to false.
  • react responds to the state change and removes the animate-pulse class
bluesixty
  • 2,367
  • 3
  • 21
  • 21
2

You might try vanilla CSS - see this simplified example with a button:

<button>
  <svg height="20" width="20"><circle cx=10 cy=10 r=8 /></svg>
</button>

per se, it's just a button with a circle. I format this using CSS:

button circle {
  fill: yellow; 
}

as soon as I extend the CSS to do an animation on focus, I think I already achieve what you are looking for

button circle {
  fill: yellow; 
  transition: fill 2s; 
}
button:focus circle {
  fill: green;
}

The button changes from yellow to green when clicked. Instead of focus, you could also assign CSS classes.

I've put the exact same code into a codepen if you want to try directly: https://codepen.io/sebredhh/pen/NaMPoP