Aquasar Posted March 22, 2020 Share Posted March 22, 2020 I have the following code and am trying to create a menu icon that closes and open. I am having issues getting the animation to run in reverse using React. Here is my code snapshot, const [reverse, setReverse] = useState(true); function moveBurger() { const tl = new gsap.timeline({ paused: true, reversed: reverse }); tl.to('.burger_rect1', 0.2, { rotation: 45, transformOrigin: '50% 50%', }) .to('.burger_rect2', 0.2, { scaleX: 0 }) .to('.burger_rect3', 0.2, { rotation: -45, transformOrigin: '50% 50%', }); if (reverse === true) { console.log('play it'); tl.play(); } else { console.log('reverse play it'); tl.reverse(); } setReverse(prev => !prev); } I did a console.log and looks like it runs the animation fwd and it does not run the animation backward, but it logs 'reverse play it' Any suggestions ? Full component here import React, { useState } from 'react'; import styled from '@emotion/styled'; import { gsap } from 'gsap'; const SVG = styled.svg` & rect { fill: #fff; } &:hover { & .line-one { stroke: red; } } `; const BurgerSVG2 = () => { // main mobile function, gets called on hover const [reverse, setReverse] = useState(true); function moveBurger() { const tl = new gsap.timeline({ paused: true, reversed: reverse }); tl.to('.burger_rect1', 0.2, { rotation: 45, transformOrigin: '50% 50%', }) .to('.burger_rect2', 0.2, { scaleX: 0 }) .to('.burger_rect3', 0.2, { rotation: -45, transformOrigin: '50% 50%', }); if (reverse === true) { tl.play(); } else { tl.reverse(); } setReverse(prev => !prev); } return ( <SVG onClick={moveBurger} className="burgerSVG" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 80" width="40" height="40" > <rect className="burger_rect burger_rect1" width="100" height="10"></rect> <rect className="burger_rect burger_rect2" y="30" width="100" height="10" ></rect> <rect className="burger_rect burger_rect3" y="60" width="100" height="10" ></rect> </SVG> ); }; export default BurgerSVG2; Link to comment Share on other sites More sharing options...
Rodrigo Posted March 22, 2020 Share Posted March 22, 2020 It's a bit hard to debug without a live sample. For something as simple as a GSAP instance toggle, perhaps using the component's state to do that could be a bit unnecessary. Here is a super simple example of an hamburger menu toggle in React using GSAP: https://codesandbox.io/s/gsap-hamburger-toggle-menu-u07i6 As you can see I just use a single timeline instance and an event handler to control it's reversed state. Happy Tweening!!! 2 1 Link to comment Share on other sites More sharing options...
Aquasar Posted March 22, 2020 Author Share Posted March 22, 2020 This is really good Rodrigo, I been looking for an example like this! I have a question regarding the fact that you are still using useEffect to put the gsap animation in. Can you help me understand why? That could be the issues with my I am having problems. I got rid of the useEffect since I thought that might not be need as I am not requiring the animation effect to run on load. As a gsap newbie, do you know the difference between using gsap and TweenMax ? I see lots of tutorials using TweenMax and TimeLineMax and my thought it that the following imports both max versions? import { gsap } from 'gsap'; Link to comment Share on other sites More sharing options...
Rodrigo Posted March 22, 2020 Share Posted March 22, 2020 58 minutes ago, Aquasar said: I have a question regarding the fact that you are still using useEffect to put the gsap animation in. Basically in a functional component anything defined outside the return statement is created again on each re-render, therefore a reference or instance is not created and kept. In some cases if a component doesn't have a state and no props are passed to them you can keep the GSAP instances outside the state since that component will be rendered once in it's lifecycle. But if the component's state or props are updated, it will be re-rendered and you can get an error. Check the following example using the console: https://codesandbox.io/s/gsap-instance-oustide-hooks-py2c6 As you can see I'm logging the value of the variable that holds a GSAP instance. In the first click is an object, as expected. But after the second click, the component is re-rendered and is set to null, but the initial render useEffect is not ran anymore, so we get an error. An alternative is to re-create the animation on each re-render, but that is a waste of code and resources, specially if the animation doesn't depends at all in the component's state or props. Since a menu button animation (the rotation, alpha or other properties being tweened) don't depend on the state, there is no need to create the tween on every render, just in the first one and save it to the component's state. In a regular class component you can store the animation as a property of the class instance, which is far simpler, cleaner and easier to understand IMHO. The joy of using React hooks... Happy Tweening!!! 5 Link to comment Share on other sites More sharing options...
Tommy81 Posted March 23, 2020 Share Posted March 23, 2020 Hi Rodrigo, I upgraded my original project's code following step by step your simple React Modal Codesandbox example. Having reached a good point, very near the final goal, a problem left to solve has risen again: as I originally thought there is a "body overflowX problem" left to manage above the modal appearance. When the modal-underlaying page content has a grater height than the viewport's scrollbars appear in the window and if you scroll the mouse wheel the page content scrolls either, together with the modal, overflowing the viewport. To fix and prevent this we have to "lock" the DOM's body Y axis scrolling before showing the modal, and the "unlock" it again when the modal has closed. Here is how I'm trying to solve the issue (keep attention to lock() and unlock() functions and their position in the code): const Modal = props => { let body = document.querySelector('body'); let modal = null; let overlayer = null; let wrapper = null; const dispatch = useDispatch(); const modalVisible = useSelector( state => state.modal ); const [tl] = useState(gsap.timeline({ defaults: { ease: "expo.out" }, paused: true, onStart: () => lock(), })); const lock = () => { body.style = ` overflowY: hidden; `; modal.style = ` height: 0; `; } const unlock = () => { body.style = ` overflowY: visible; `; modal.style = ` height: 100vh; `; } useEffect(() => { gsap.set(modal, { height: 0 }); gsap.set(wrapper, { yPercent: -80, xPercent: -50 }); tl.to(overlayer, { autoAlpha: 0.85 }) .to( modal, { autoAlpha: 1, },0 ) .to(wrapper, { yPercent: -50, autoAlpha: 1 }) .reverse(); },); useEffect(() => { if (modalVisible) lock(); tl.reversed(!modalVisible); }, [tl,modalVisible]); const close = () => { unlock(); dispatch(toggleModal(false)); } return ReactDOM.createPortal( <div className={`${props.className} modal`} ref={e => {modal = e}}> <div className="modal-overlayer" onClick={() => close()} ref={e => {overlayer = e}} /> <div className="modal-wrapper" ref={e => {wrapper = e}}> <ModalClose className="modal-close" onClick={() => close()} /> { props.header && ( <div className="modal-header"> <h2>{props.header}</h2> </div> )} <div className="modal-content"> <div className="modal-content-wrapper"> MODAL CONTENT </div> </div> { props.footer && ( <div className="modal-footer"> FOOTER CONTENT </div> )} </div> </div>, document.body ); } Triggering the modal opening I get this error: TypeError: Cannot set property 'style' of null lock src/components/Modal.js:59 56 | body.style = ` 57 | overflowY: hidden; 58 | `; > 59 | modal.style = ` | ^ 60 | height: 0; 61 | `; 62 | } View compiled Timeline.onStart src/components/Modal.js:51 48 | ease: "expo.out" 49 | }, 50 | paused: true, > 51 | onStart: () => lock(), | ^ 52 | })); 53 | 54 | const lock = () => { What am I doing wrong? Where's the problem to trigger the lock() function inside the Timeline's onStart() callback? Thank you very much ^^ Link to comment Share on other sites More sharing options...
Rodrigo Posted March 23, 2020 Share Posted March 23, 2020 Basically the error is that the modal variable is null, therefore it doesn't have a style property. Your code seems quite convoluted IMHO. Why you need the modal height to be 0 at startup? Using opacity: 0 and visibility: hidden in the CSS declaration is enough to prevent the modal from being accessible to the user, that's why we later use autoAlpha: 1 in GSAP, that sets the visibility to visible when the animation starts and the tweens the opacity value up to 1. Finally if you want you can use the classList property to add/remove a class from the body element in the useEffect call: useEffect(() => { tl.reversed(!props.show); if (props.show) { document.body.classList.add("no-scroll"); } else { document.body.classList.remove("no-scroll"); } }, [props.show]); The CSS would look like this: .no-scroll { overflow: hidden !important; } I updated the sample in codesanbox to reflect those changes. Happy Tweening!!! 5 Link to comment Share on other sites More sharing options...
Tommy81 Posted March 24, 2020 Share Posted March 24, 2020 Quote I updated the sample in codesanbox to reflect those changes. Excuse me Rodrigo, where do I find the sample? The older link seems to be another sample about another issue. Quote Finally if you want you can use the classList property to add/remove a class from the body element in the useEffect call: I'm trying to understand why... 😚 Here is the code sections where I use the classList property, all of them inside my Modal component: const [tl] = useState(gsap.timeline({ /* other stuff here... */ onStart: () => document.body.classList.remove("no-scroll"), })); useEffect(() => { if (modalVisible) document.body.classList.add("no-scroll"); tl.reversed(!modalVisible); },[modalVisible]); const close = () => { document.body.classList.remove("no-scroll"); dispatch(toggleModal(false)); } This the result in the DOM after clicking the modal opening trigger button: <body cz-shortcut-listen="true" class=""> 🤬 ^^ Link to comment Share on other sites More sharing options...
Tommy81 Posted March 24, 2020 Share Posted March 24, 2020 Quote I'm trying to understand why... 😚 Understood... ^^ Link to comment Share on other sites More sharing options...
Tommy81 Posted March 24, 2020 Share Posted March 24, 2020 Quote Excuse me Rodrigo, where do I find the sample? The older link seems to be another sample about another issue. Excuse me Rodrigo and Aquasar! I thought this was to thread I opened!!! I'll continue in the right thread... 😅😅😅 Link to comment Share on other sites More sharing options...
OSUblake Posted March 24, 2020 Share Posted March 24, 2020 On 3/22/2020 at 2:52 PM, Aquasar said: As a gsap newbie, do you know the difference between using gsap and TweenMax ? I see lots of tutorials using TweenMax and TimeLineMax and my thought it that the following imports both max versions? TweenLite, TweenMax, TimelineLite, and TimelineMax is the old syntax. gsap is the new syntax, so use that instead. 5 Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now