CedGrvl Posted November 1, 2019 Share Posted November 1, 2019 Hello, I have a simple question to ask and if you could enlighten me that would be cool. I have a web page with several sections, each section has different logos and texts These logos and texts are animated as soon as the section is visible. The animation is different depending on the size of the screen and of course on the section. My question is not necessarily about how to animate with GSAP but rather how to specify these "states". Let me explain. Which of these two models is better? in terms of performance? code organization? logic? function animeAllELement(){ const mql = window.matchMedia('screen and (min-width: 992px)'); const mql2 = window.matchMedia('screen and (min-width: 576px) and (max-width: 991px)'); let pages = document.getElementsByClassName('js-page'); // Store all sections for (let i=0; i<pages.length; i++){ let positionPages = pages[i].getBoundingClientRect().top; let hrefPages = pages[i].dataset.href; if(positionPages === 0 && hrefPages === "#home" && mql.matches){ let tl = new TimelineMax(); // ... // break ? } if(positionPages === 0 && hrefPages === "#other"){ let tl = new TimelineMax(); // ... // break ? } // ect ect } } window.addEventListener('scroll', () => requestAnimationFrame(animeAllELement)); requestAnimationFrame(animeAllELement) Or maybe function animeAllELement(){ const mql = window.matchMedia('screen and (min-width: 992px)'); const mql2 = window.matchMedia('screen and (min-width: 576px) and (max-width: 991px)'); function element1(){ if(mql.matches){ let tl = new TimelineMax(); // ... return tl; } if(mql2.matches){ let tl = new TimelineMax(); // ... return tl; } }; function element2(){ if(mql.matches){ let tl = new TimelineMax(); // ... return tl; } if(mql2.matches){ let tl = new TimelineMax(); // ... return tl; } }; let pages = document.getElementsByClassName('js-page'); // Store all sections for (let i=0; i<pages.length; i++){ let positionPages = pages[i].getBoundingClientRect().top; let hrefPages = pages[i].dataset.href; if(positionPages === 0 && hrefPages === "#home"){ let masterTimeline = new TimelineMax(); masterTimeline.add(element1()) masterTimeline.add(element2().pause()) // break ? } if(positionPages === 0 && hrefPages === "#other"){ let masterTimeline = new TimelineMax(); masterTimeline.add(element1().pause()) masterTimeline.add(element2()) // break ? } } } window.addEventListener('scroll', () => requestAnimationFrame(animeAllELement)); requestAnimationFrame(animeAllELement) Maybe I'm getting lost. Thank you for your time. Link to comment Share on other sites More sharing options...
ZachSaucier Posted November 1, 2019 Share Posted November 1, 2019 Hey CedGrvl. Sorry to say, but neither approach is very good. The main reason for that is because every time the scroll event fires you'd be creating new tweens and timelines for every section of the page. That can't be good What you should be doing instead is only recreating the tweens and timelines on resize if that's even necessary. Otherwise your tweens and timelines should be fine because the page dimensions are the same as they were when it was initialized. Now, to answer your question about which approach of the two is better. I think your first approach is missing some pseudo-code because it's not equivalent to the second approach in terms of the theoretical logic. It's missing conditional checks and creating one of two timelines for each section based on the viewport size. Assuming that is just a mistake and that the logic for that is supposed to be there, I would probably use a variant of your second approach. In general it is good to put the building of complex timelines into their own functions so that your code is more modular, as "Writing Smarter Animation Code" by our very own Carl says. However, the way that you have it setup, with functions within functions, isn't optimal because those functions are recreated every time and functions are somewhat expensive. Rewriting your psuedo-code, I'd probably do something along the lines of this: let masterTL, mobileMasterTL = new TimelineMax({paused: true}), tabletMasterTL = new TimelineMax({paused: true}), desktopMasterTL = new TimelineMax({paused: true}); // keep track of the furthest position that we have been to (to not go backwards) let furthestPos = 0; // for desktops const mql = window.matchMedia('screen and (min-width: 992px)'); // for tablets const mql2 = window.matchMedia('screen and (min-width: 576px) and (max-width: 991px)'); // our sections const pages = document.getElementsByClassName('js-page'); // Keep track of our offsets and hrefs const offsets = []; // Setup our page function init() { updateOffsets(); setupTimelines(); checkSwitchMasterTL(); window.addEventListener('resize', () => requestAnimationFrame(handleResize)); window.addEventListener('scroll', () => requestAnimationFrame(handleScroll)); } // Update each element's recorded offset top position on page resize function updateOffsets() { for(let i = 0; i < pages.length; i++) { let myObject = { "positionPage": pages[i].getBoundingClientRect().top, "hrefPages": pages[i].dataset.href } offsets[i] = myObject; } } // Create the timelines in each section for each section // Do this for each section... function initFirstSection() { let myHREF = offsets[0]["hrefPages"]; // setup the desktop animations let dtl = new TimelineMax(); // ... desktopMasterTL.addLabel("start" + myHREF); desktopMasterTL.add(dtl); desktopMasterTL.addLabel("end" + myHREF); // setup the tablet animations let ttl = new TimelineMax(); // ... tabletMasterTL.addLabel("start" + myHREF); tabletMasterTL.add(ttl); tabletMasterTL.addLabel("end" + myHREF); // setup the mobile animations let mtl = new TimelineMax(); // ... mobileMasterTL.addLabel("start" + myHREF); mobileMasterTL.add(mtl); mobileMasterTL.addLabel("end" + myHREF); }; // Create timelines for the second section function initSecondSection() { // ... } // Call all of our section timeline setup functions function setupTimelines() { // Init all of our sections' timelines initFirstSection(); initSecondSection(); // .... } // Switch between the timelines based on the current viewport width function checkSwitchMasterTL() { // desktop if(mql.matches) { masterTL = desktopMasterTL; tabletMasterTL.progress(0).pause(); mobileMasterTL.progress(0).pause(); } // tablet else if(mql2.matches) { masterTL = tabletMasterTL; desktopMasterTL.progress(0).pause(); mobileMasterTL.progress(0).pause(); } // mobile else { masterTL = mobileMasterTL; desktopMasterTL.progress(0).pause(); tabletMasterTL.progress(0).pause(); } } // Update our offsets and see if we need to switch timelines function handleResize(e) { updateOffsets(); checkSwitchMasterTL(); } // Check to see if we need to fire any animations function handleScroll(e) { // Iterate backwards through our offsets, tweening the furthest one down the page // if the offset is in view let viewportBottom = scrollY + innerHeight; for(let i = offsets.length - 1; i > 0; i--) { let scrollPos = offsets[i]["positionPage"]; if(scrollPos > scrollY && scrollPos < viewportBottom && furthestPos < scrollY) { let myHREF = offsets[i]["hrefPages"]; // We use tweenFromTo to play a section of our master timeline and make sure // that the rest of the page is setup the way we need it to be. // tweenFromTo docs: https://greensock.com/docs/v2/TimelineMax/tweenFromTo() masterTL.tweenFromTo("start" + myHREF, "end" + myHREF); // Update our furthest Y position variable furthestPos = scrollY; } } } requestAnimationFrame(init); The advantages of an approach like this: You do most of the work when setting up the page. All of the tweens and timelines are created at that point. The only things that you're doing on resize are checking to see if you need to switch between timelines and updating the offset positions of your sections. On scroll all you're doing is checking the scroll position and playing the relevant animation (if necessary). It's very modular. It's easily extendable - all you need to add per section is an initFirstSection equivalent function and call that function inside of setupTimelines. Note that this code is completely untested other than for basic syntax errors Some other notes: You might not need the mobile timeline logic. I just included it based on your media point. I figured it's better to add it and take it away if need be than have to not have it and have to add it later. Generally speaking you should put variables that don't change outside of functions that are called multiple times. That saves the computer from having to do the same work every time. You should consider using overwrite: "all" on your tweens since you have multiple timelines affecting the same element. You've got to be a little careful about how you structure the animations. What are you expecting break to do? I think you're wanting return there instead. But if you use if statements, it's not much more processing to just let it check the other conditionals. Sorry for the long post - hopefully it's very helpful! 4 Link to comment Share on other sites More sharing options...
CedGrvl Posted November 1, 2019 Author Share Posted November 1, 2019 Thank you for that clear and precise answer and thank you for the time you spent on it. I'll analyze all this, understand it, apply it and give you feedback. Thank you again! 1 Link to comment Share on other sites More sharing options...
CedGrvl Posted November 6, 2019 Author Share Posted November 6, 2019 Hello I did two codepens, the first one trying to use the track you gave me but I have to miss something. I tried to be more specific about how the site works. I got a result on the second codepen but the animations when they are on screen, have an infinite animation (up to off screen) I tried to pause the offscreen sections but without any result I looked at overwrite and clearProps but I don't know how to implement them properly See the Pen LYYdePj?editors=1010 by CedGrvl (@CedGrvl) on CodePen See the Pen QWWmmqJ by CedGrvl (@CedGrvl) on CodePen Thx Again Link to comment Share on other sites More sharing options...
ZachSaucier Posted November 6, 2019 Share Posted November 6, 2019 Hey CedGrvl. It looks like you disabled scrolling for your demos? In which case the handleScroll and updateOffsets functions are probably not helpful for you. Are you requiring that users use navigation buttons like is true in your demo? It would be good for you to describe your end goal in more detail as now I don't know what you're wanting. And for future notice, if you're changing the functionality of how sites work like this, it would be good to include that in your first post so that we're on the same page from the start Link to comment Share on other sites More sharing options...
CedGrvl Posted November 6, 2019 Author Share Posted November 6, 2019 Thank you for these tips, which are all the more logical I'll do a new post with more details and a better chronology. I could just edit my first post for sure. Let's start over on good lows:) Link to comment Share on other sites More sharing options...
OSUblake Posted November 6, 2019 Share Posted November 6, 2019 I def wouldn't use rAF like this, especially with scroll. You could be building up a huge queue, which will hurt performance. window.addEventListener('scroll', () => requestAnimationFrame(checkSectionPos)); You can make sure there is only 1 rAF call per frame like this. // can use for cancelAnimationFrame(requestId); var requestId = null; window.addEventListener("scroll", requestUpdate); function requestUpdate() { // ignore any requests if rAF has already been called if (!requestId) { requestId = requestAnimationFrame(checkSectionPos); } } function checkSectionPos() { ... // clear id to allow more requests requestId = null; } For scrolling, I like to use the Intersection Observer. See the Pen 6fd214ecd74e7091ec7b609bb0270f97 by osublake (@osublake) on CodePen Simple media queries. See the Pen WKpmxN by osublake (@osublake) on CodePen 6 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