Jump to content

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


  • Content Count

  • Joined

  • Last visited

Community Reputation

0 Newbie

About Tommy81

  • Rank
  1. Hi Rogdrigo, I solved the problem but not for the reason you highlight (indeed the logic is right) but just with the removal of firstLoad var which become usesless after last updates I integrated following your live example. Considerating your kindness and effort to help me I'll explain you why your suggestion was not hitting the problem: First of all: is not your mistake, it's only due to the fact you rightly assert that "is hard to see the real issue without looking at a live sample". You did't have the full view πŸ™‚ The project I'm working on is a React App which interacts with a local MySQL database and I would have to rewrite almost everything just to build a live example... The problem has it's origin in this fact: there are two possibile scenarios by which the modal can be closed: 1. The normal closure through the close button click 2. The modal closure as the consequence of the user interaction with the modal content, WITHOUT close button click. The flags I created have the role to manage the discrimination between these two scenarios, these tho different logic flows. In my application's Redux store I've reserved a reducer which works on the modal node to manage the flags to discriminate these two different modal closure scenarios/flows. The application, at a data level, works with "stores" which can be link to a "banner". In the app there are two modals, one to create a new banner and one to select and link a banner to a store. The first its been called "banner_creation", the last "banner_select". Every modal has its relative flag in the Redux store to manage the type 2 scenario with more another flag to manage the type 1 scenario. The type 1 flag just gets a "_close" suffix. the result in the Redux store is: modal: { banner_creation: false, banner_creation_close: false, banner_select: false, banner_select_close: false, } The type 2 scenario flag, banner_creation for example, manages the banner creation modal visibility status while the type 1 flag manages the relative modal button clicked status. The moment I wrote complaining to not understand why It was again not working I was not referring to the modal closure but to the full execution and the two different logic flows. This was what actually happened: const onReverseCompleteCallback = () => { if (!modalVisible && !closeButtonClicked && !firstLoad) { console.log("Modal: "+props.name+" | tl reversed: modal closed by input"); props.onCloseComplete(); } if (modalVisible && closeButtonClicked && !firstLoad) { console.log('Modal: '+props.name+" | tl reversed: modal closed by close button"); dispatch(toggleModal(name)); } }; When the modal did close reversing the tweening the specific callback was rightly triggered but no condition was hit because firstLoad was alway true (I'm not going to explain why now).... ^^ Removing the firstLoad var all works fine!!!! πŸ˜€πŸ˜… Thank you very much Rodrigo for your help and patience πŸ™‚
  2. // REACT import React, { useEffect, useState, } from 'react'; import ReactDOM from 'react-dom'; // REDUX import { useDispatch, useSelector, } from 'react-redux'; import { toggleModal, } from '../redux/actions'; // STYLED COMPONENTS import styled from 'styled-components/macro'; import { transparentize } from 'polished'; // GSAP import { gsap } from 'gsap'; // Typing import PropTypes from 'prop-types'; // STYLES import { absolute, fit, } from '../styled/mixin'; // Components import ModalClose from './ModalClose'; // Vars const zID = 2; // Main component const Modal = props => { const closeButtonClicked = useSelector(state => state.modal[`${props.name}_close`]); const dispatch = useDispatch(); const modalVisible = useSelector( state => state.modal[props.name] ); const name = props.name; let firstLoad = true; let modal = null; let overlayer = null; let wrapper = null; const onReverseCompleteCallback = () => { if (!modalVisible && !closeButtonClicked && !firstLoad) { console.log("Modal: "+props.name+" | tl reversed: modal closed by input"); props.onCloseComplete(); } if (modalVisible && closeButtonClicked && !firstLoad) { console.log('Modal: '+props.name+" | tl reversed: modal closed by close button"); dispatch(toggleModal(name)); } if (firstLoad) firstLoad = false; }; const [tl] = useState(gsap.timeline({ defaults: { duration: 0.3, ease: "expo.out", transformOrigin: "center", }, onReverseComplete: onReverseCompleteCallback, paused: true, })); useEffect(() => { 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(() => { tl.eventCallback( 'onReverseComplete', onReverseCompleteCallback, [closeButtonClicked,firstLoad,modalVisible,props] ); if (modalVisible) { console.log('Modal: '+props.name+" | modalVisible > "+modalVisible); document.body.classList.add("no-scroll"); tl.reversed(!modalVisible); if (closeButtonClicked) tl.reverse(); } else { console.log("Modal: "+props.name+" | modalVisible > "+modalVisible); document.body.classList.remove("no-scroll") if (!closeButtonClicked) tl.reverse(); else dispatch(toggleModal(`${props.name}_close`)); } },[tl,closeButtonClicked,modalVisible]); const close = () => { // console.log('Modal: '+props.name+" close button clicked"); dispatch(toggleModal(`${props.name}_close`)); } 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-wrapper"> <div className="modal-content"> {props.children} </div> </div> { props.footer && ( <div className="modal-footer"></div> )} </div> </div>, document.body ); } // Styles component const Styled = styled(Modal)` ${fit()} opacity: 0; position: fixed; left: 0; top: 0; visibility: hidden; z-index: ${zID}; .modal { &-content { overflow-x: hidden; overflow-y: auto; &-wrapper { padding: ${props => props.theme.pad.modal}; } } &-header { padding: ${props => { const p = props.theme.pad.modal.split('px')[0]; return `${p*2}px ${p}px ${p}px`; }} } &-overlayer { ${absolute({ type: 'leftTop' })} ${fit()} background: ${props => transparentize(0.5,props.theme.bg.modal_overlayer)}; opacity: 0; visibility: hidden; z-index: ${zID+1}; } &-wrapper { background: ${props => props.theme.bg.modal_wrapper}; border-radius: ${props => props.theme.radius.modal_wrapper}; left: 50%; max-height: ${props => props.theme.h.modal_wrapper}; top: 50%; opacity: 0; position: absolute; visibility: hidden; width: ${props => props.theme.w.modal_wrapper}; z-index: ${zID+2}; } } `; // Typing Modal.propTypes = { className: PropTypes.string.isRequired, children: PropTypes.any.isRequired, footer: PropTypes.any, header: PropTypes.string, onCloseComplete: PropTypes.func, } // Default Modal.defaultProps = { onCloseComplete: Function } // Exports export default Styled; Here is the full code...
  3. @Rodrigo: Yes I know, I was only trying let understand my intent conceptually 😊 I had already tried the eventCallback() approach but it failed... The live example your shared seems to be perfect for me but, again, I read it and compared it with my code but, again, it is still not working... can't understand WHY... It seems all right to me... I'm going to change the component to a class one as a last chance but I would prefer to make the hooks version works... ^^ Soon I'm going to share my real code but in this moment I've got internet connection problems and the code share tool is not going to load...
  4. @OSUblake: Thank you Blake for answering. I had already tried you solution but without success.
  5. Hi Zach, I logged and re-logged "flagA" and "flagB" (meaning the real vars in my real code) for hours making, several tests..., changes... the only explanation I can give is the one I reported: flagA and flagB appear to be "freezed" at their first/initial values in the Redux Store... I've tried, add(), call(). registerEffect()... I'm not going anywhere... I'm stucked... and frustrated...
  6. Hi there, the following is the extremely trimmed and simplified conceptual snippet of from a real project of mine where I'm having what it seem to be a scope problem. const My Component = () => { const flagA = useSelector(state => state.flagA); const flagB = useSelector(state => state.flagB); const [tl] = useState(gsap.timeline({ defaults: { // defaults setting here }, paused: true, // here after the actual callback setting onReverseComplete = () => { if (flagA) { // do A stuff here } if (flagB) { // do B stuff here } }, })); useEffect(() => { // tl tweening elements initial settings here // tl tweening code here },[]); // This is where I think I should define the onReverseComplete callback useEffect(() => { tl.onReverseComplete = () => { if (flagA) { // do A stuff here } if (flagB) { // do B stuff here } } },[flagA,flagB]); // Render stuff here return(); } As you can see the component has two (boolean) vars, flagA and flagB which come from a Redux Store. On the original and actual project's code the onReverseComplete() callback is set in the useState() definition. The problem is that in this way the first time the callback is defined the function scope takes the initial flagA and flagB values and never updates but flagA and flagB values change through the application and I need to trigger different effects inside the onReverseComplete() callback according to flagA and flagB values. In the code you can also find a fake useEffect() definition where i suspect I must define the onReverseComplete() callback but I'm not understanding how and if it is possibile. I'm not understanding that reading GSAP docs. Maybe with the Timeline add() method? If yes: how I should do that? Thank you for answering ^^
  7. Excuse me Rodrigo and Aquasar! I thought this was to thread I opened!!! I'll continue in the right thread... πŸ˜…πŸ˜…πŸ˜…
  8. Understood... ^^
  9. Excuse me Rodrigo, where do I find the sample? The older link seems to be another sample about another issue. 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=""> 🀬 ^^
  10. 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 ^^
  11. Hi Rodrigo, first of alla thanks a lot for answering! It's very strange you can't reach the repo, I tried just before responding, assuring me to be not logged-in in my Bitbucket account and the link worked correctly... The fact you haven't see the full code code has caused some feedbacks which need more clarifications from me. Lets do it. Here is the essential App component code: // Main component const App = props => { const [modal,openModal] = useState(false); return ( <div className="app"> <CommonStyles /> <Button display="block" label="Open modal" onClick={()=> openModal(true)} width="100%" /> <Modal modal={modal} openModal={val => openModal(val)} /> </div> ); } // Exports export default App; As you can see Modal component has a openModal prop which is used just the toggle the modal flag in the App component state from which in turn depends te modal prop of Modal component. modal flag and openModal hook function are created with the useState hook. modal flag is false at start, changed to true clicking the "Open modal" button and then set back to false in the Timeline.onReverseComplete() callback: const tl = gsap.timeline({ defaults: { ease: "expo.out", }, onReverseComplete: () => { unlock(); props.openModal(false); }, }); Clarified this: The modalVisible flag and the setModalVisible method to change it you suggested to use are present in my code with the modal state and the openModal() hook function which handles it, all in App component. Do you see problems to trigger to modal flag change back to false inside the Timeline.onReverseComplete() callback as I've done, maybe? About this statement of yours: You should create the GSAP instance in the initial render with: useEffect(() => { // create GSAP instance here }, []) I've done that in the first place, I swear!!! A collegue of mine suggested me to move it outside... In the meantime I'm goint to: Try the Timeline.reversed() approach in place of the Timeline.onReverseComplete() callback to Move my Modal component modal() method logic inside the useEffect hook as you suggested Keep in touch
  12. Hi there everybody, At this git repository https://Tommy81@bitbucket.org/Tommy81/react-gsap-reverse.git I've isolated the code coming from a real React project I'm working on. I'm not understanding why the .modal-wrapper DOM element in apparently not correctly animated by GSAP Timeline reverse method. When you click on the "Open Modal" button, the modal DOM elements (.modal, .modal-overlayer and .moda-wrapper) are correctly animated. When you click the modal close button (.modal-close) .modal-wrapper it seems to me the .modal-wrapper element in not correctly animated because when you click again on "Open Modal" button from second time on, the modal is now showing again... I suspect the problem should be related to the way I set the initial Timeline and css settings... Down here I give a restricted code preview of the git I shared: INITIAL CSS html, body { box-sizing: border-box; height: 100%; width: 100%; } body { background: white; color: black; padding: 50px; } .modal { height: 0; left: 0; overflow: hidden; position: fixed; top: 0; width: 100%; } .modal-overlayer { position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%); height: 100%; width: 100%; background: rgba(0,0,0,0.5); opacity: 0; z-index: 0; } .modal-wrapper { position: absolute; background: white; border-radius: 5px; left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%); max-height: 80%; opacity: 1; width: 80%; z-index: 1; } REACT & GSAP ESSENTIAL CODE const Modal = props => { const body = document.querySelector('body'); const container = document.querySelector('.modal'); const overlayer = document.querySelector('.modal-overlayer'); const wrapper = document.querySelector('.modal-wrapper'); const tl = gsap.timeline({ defaults: { ease: "expo.out", }, onReverseComplete: () => { unlock(); props.openModal(false); }, }); tl.to(overlayer,{ duration: 0.5, opacity: 1, }); tl.from(wrapper,{ duration: 0.8, opacity: 0, yPercent: '-=30', }); const lock = () => { body.style = ` overflow: hidden; position: relative; `; container.style = ` height: 100%; `; } const unlock = () => { body.style = ` overflow: unset; position: unset; `; container.style = ` height: 0; `; } const modal = status => { if (status === 'open') { lock(); tl.play(); } if (status === 'close') { tl.reverse(); } } useEffect(() => { if (props.modal) modal('open'); }); return(/* render stuff here */) } Thank you very who's gonna help me... ^^