Jump to content
Search Community

Timeline and Condition ( Screen size and page position)

CedGrvl test
Moderator Tag

Recommended Posts

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

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!

  • Like 4
Link to comment
Share on other sites

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

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

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

 

 

  • Like 6
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...