Jump to content
Search Community

SVG Switch and TImelineMax... something's missing

danboyle8637 test
Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

Okay. I have been staring at this for a day now and it's possible I'm so fried I'm missing something... but...

 

I'm using these toggles in a form to turn on and off certain questions. The form works and the toggles work but I can't get their animations to work.

 

I linked to a CodeSandbox with a little example. In this example... I can toggle the switch on... but it won't reverse. I feel like I'm misusing Greensock someway.

 

Below is also my SVG component in my actual website. I have commented out some code, but when I try to set the timeline in my initial useEffect call... and then I try to play or reverse it based on the click... nothing happens.

 

If I put in console.log statements into the click handler... they are called correctly.

 

If I just put in a TweenMax call into the click handler... the switches animate correctly.

 

What am I missing?

 

Here's the code and thanks!

 

import React, { useRef, useEffect } from "react"
import { TimelineMax, Power2, TweenMax } from "gsap"

import { useFormStore } from "../context/FormContext"

const FormToggleSwitch = ({
  width,
  height,
  className,
  handleWebsiteClick,
  name,
}) => {
  const [formState, dispatch] = useFormStore()
  const toggleSwitchRef = useRef(null)
  const tl = new TimelineMax({ paused: true })

  useEffect(() => {
    tl.add(
      TweenMax.fromTo(
        toggleSwitchRef.current,
        0.6,
        {
          x: 0,
          fill: "#F96666",
        },
        {
          x: 36,
          ease: Power2.easeInOut,
          fill: "#54ba5e",
        }
      ),
      0
    )
  }, [])

  if (formState.haveWebsite && name === "websiteToggle") {
    //console.log("go to green")
    tl.play()
    // TweenMax.to(toggleSwitchRef.current, 0.6, {
    //   x: 36,
    //   ease: Power2.easeInOut,
    //   fill: "#54ba5e",
    // })
  } else {
    tl.reverse()
  }

  if (formState.haveTimeline && name === "timelineToggle") {
    // console.log("go to red")
    tl.play()
    // TweenMax.to(toggleSwitchRef.current, 0.6, {
    //   x: 0,
    //   ease: Power2.easeInOut,
    //   fill: "#F96666",
    // })
  } else {
    tl.reverse()
  }

  return (
    <svg
      id="toggle-switch"
      xmlns="http://www.w3.org/2000/svg"
      className={className}
      width={width}
      height={height}
      viewBox="0 0 70 34.48"
      onClick={handleWebsiteClick}
    >
      <g id="body">
        <path
          d="M22.57 38a16.24 16.24 0 0 1 0-32.48h35.52a16.24 16.24 0 0 1 0 32.48z"
          transform="translate(-5.33 -4.54)"
          fill="#ebebeb"
        />
        <path
          d="M58.09 6.54a15.24 15.24 0 0 1 0 30.48H22.57a15.24 15.24 0 0 1 0-30.48h35.52m0-2H22.57A17.24 17.24 0 0 0 5.33 21.78 17.24 17.24 0 0 0 22.57 39h35.52a17.24 17.24 0 0 0 17.24-17.22A17.24 17.24 0 0 0 58.09 4.54z"
          transform="translate(-5.33 -4.54)"
          fill="#d7d7d7"
        />
      </g>
      <circle
        ref={toggleSwitchRef}
        cx="17.07"
        cy="17.24"
        r="12.93"
        fill="#f96666"
        id="switch"
      />
    </svg>
  )
}

export default FormToggleSwitch

 

See the Pen by s (@s) on CodePen

Link to comment
Share on other sites

this seems to work

  useEffect(() => {

      tl.fromTo(toggleSwitchRef.current, 0.6, {
        x: 0,
        fill: '#F96666'
      }, {
        x: 36,
        ease: Power2.easeInOut,
        fill: '#54ba5e',
      })

  })

 

  • Like 2
Link to comment
Share on other sites

The brackets tell React to only run the code when the component initially mounts. 

 

So I figured that was the best time to set up the timeline.

 

Without the brackets... the function run anytime the component re-renders. In this case that would be with every click of the toggle because it's changing the state of the form.

 

In my demo... if I get rid of the brackets rather than reverse... it just jumps back. 

 

 

Link to comment
Share on other sites

Whoa! Didn't expect that. 

 

I don't know if I can do that in my site because with the tl outside the function... i think it might be global and then conflict with other tl's I've created... but then again since those are in functions themselves... maybe not. 

 

Thanks @PointC for your help so far. 

 

I tried console logging the tl object in each of my functions and it does log with the right data I set up so it does exist. I just don't understand... 

Link to comment
Share on other sites

Again, my React knowledge is minuscule, but what I see happening is the App function fires on load and on each click. So the first time you play() the timeline all is well, but the App fires again and declare the tl variable again which wipes out the original timeline. The useEffect function doesn't fire on click so that is now just an empty timeline and will therefore not reverse(). That's what's happening in your original demo.

 

That's the best I can do with my limited knowledge and guesses.

 

Happy tweening.

 

PS I think we mentioned this in one of your other threads, but the code is shorter and easier to read the way I wrote it above rather than using the add() method for tweens on the timeline. Just my two cents.

  • Like 1
Link to comment
Share on other sites

10 minutes ago, danboyle8637 said:

So do you pretty much only use add for function calls or labels then? 

 

Yup — callbacks, labels and building up master timelines are my primary uses for the add() method. 

 

Good luck with the project.

  • Like 1
Link to comment
Share on other sites

Hi,

 

I haven't used hooks a lot, since I've been extremely busy with literally no free time so I couldn't tell you in this one. The one solution (that you probably are aware of) I can suggest is to use a regular class component just for the switch element. I know is not the ideal case, but with a component that small the overhead created by the extra code shouldn't be noticeable, unless we're talking about hundreds of instances.

 

You can compare the outcome of this small snippet in Babel's repl playground:

 

const MyComponent = () => {
  const tl = "TimelineInstance";
  return <div></div>;
}

class ClassComponent extends Component {
	constructor(props){
    	super(props);
    }
  
  render() {
    return <div></div>;
  }
}

 

Of course Babel is going to add a few lines at the start of the outcome for the class declaration but you'll find this:

 

var MyComponent = function MyComponent() {
  var tl = "TimelineInstance";
  return React.createElement("div", null);
};

var ClassComponent =
/*#__PURE__*/
function (_Component) {
  _inherits(ClassComponent, _Component);

  function ClassComponent(props) {
    _classCallCheck(this, ClassComponent);

    return _possibleConstructorReturn(this, _getPrototypeOf(ClassComponent).call(this, props));
  }

  _createClass(ClassComponent, [{
    key: "render",
    value: function render() {
      return React.createElement("div", null);
    }
  }]);

  return ClassComponent;
}(Component);

 

As you can see the difference is not much, for something as simple as this of course, but as I mentioned, the gain in a small component that it doesn't have hundreds of instances the overhead is minimal and most likely unnoticeable.

 

3 hours ago, danboyle8637 said:

I don't know if I can do that in my site because with the tl outside the function... i think it might be global and then conflict with other tl's I've created... but then again since those are in functions themselves... maybe not. 

 

Actually there is not a lot of issue in creating instances outside the declaration of a React component. Keep in mind that those are just object constructors, nothing more. Also is very important to keep in mind that the bundling tools (today almost everyone uses Webpack) creates independent, encapsulated modules so each file has no communication with the global scope unless is set up in such way, which @PointC's suggestion doesn't so it should be very safe to do so.

 

Hopefully this comes as helpful.

 

Happy Tweening!!

  • Like 2
Link to comment
Share on other sites

Just a strange update... switched to class component and everything works perfect.

 

One thing I tried was moving my instantiation of my timeline outside my functional component to see if it worked... it did work!

 

But then the timing of the animations got all wonky. Sometimes they would be snappy... sometimes they would be super delayed even though I had no delay. 

 

I'll see if I can replicate on a CodeSandbox later... but I have to get this site DONE!

 

Thanks for all the help!

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...