Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
danboyle8637

SVG Switch and TImelineMax... something's missing

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

Your demo link doesn't seem to be working correctly.

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

PS I don't know anything about React so I'm not sure about the purpose of the brackets at the end, but removing them seems to fix the issue. @Rodrigo will almost certainly know what's happening here.

 

Happy tweening.

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

Weird, yeah I see the jump back in FF, but Chrome works correctly for me. 

 

hmm... I'm not sure then. I guess I'll have to bow out then and leave this to @Rodrigo or someone else familiar with React.

 

 

 

Link to comment
Share on other sites

Moving the tl variable declaration outside the App also seems to fix this while leaving the brackets in place.

 

const tl = new TimelineMax({ paused: true })
const App = ({ className, width, height }) => { 

 

Does that help at all?

  • Like 1
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

Thanks again. I'll keep messing with it. You did just give me an idea to try out. 

 

I have been re-writing my timelines without the add function. 

 

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

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

As always, @Rodrigo is the answer man. 

 

kkesqWD.jpg

  • Haha 2
Link to comment
Share on other sites

@Rodrigo Sweet thanks... I haven't gone through your answer yet. But just wanted to say thanks for the indepth answer too. 

 

I'll be pouring through it and playing with it tonight for sure.

  • Like 1
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.
×