Jump to content
Search Community

Targeting nth-of-type for GSAP and Intersection Observer project

le_kiki test
Moderator Tag

Recommended Posts

Hey everyone,

 

I recently asked a question to which @ZachSaucier gave an awesome response and helped me discover Intersection Observer.

Now I am trying to work a piece of code using Intersection Observer to create a parallax gallery.

 

Here is a basic CodePen example

 

I started targeting the .scrolled-element:nth-of-type(2n) which works well to target every second instance of .scrolled-element

 

Here is my code:

 

// Setup Intersection Observer and GSAP homepage

const observerElements = document.querySelectorAll('.trigger-element');

const observerOptions = {
  root: null,
  rootMargin: '0px 0px',
  threshold: 0
};

observerElements.forEach(el => {
  
  const scrolled = el.querySelectorAll('.scrolled-element:nth-of-type(2n)');
  el.tl = gsap.timeline({paused: true});
  
  el.tl
    .to(scrolled, {y: -200, ease: 'power2.inOut'})
  
  el.observer = new IntersectionObserver(entry => {
    if (entry[0].intersectionRatio > 0) {
      gsap.ticker.add(el.progressTween)
    } else {    
      gsap.ticker.remove(el.progressTween)
    }
  }, observerOptions);
  
  el.progressTween = () => {
    // Get scroll distance to bottom of viewport.
    const scrollPosition = (window.scrollY + window.innerHeight);
    // Get element's position relative to bottom of viewport.
    const elPosition = (scrollPosition - el.offsetTop);
    // Set desired duration.
    const durationDistance = (window.innerHeight + el.offsetHeight);
    // Calculate tween progresss.
    const currentProgress = (elPosition / durationDistance);
    // Set progress of gsap timeline.     
    el.tl.progress(currentProgress);
  }

  el.observer.observe(el);
});

 

I then wanted to start targeting more of the same selector so added them like so:

 

const scrolled1 = el.querySelectorAll('.scrolled-element:nth-of-type(1n)');
  const scrolled2 = el.querySelectorAll('.scrolled-element:nth-of-type(2n)');
  const scrolled3 = el.querySelectorAll('.scrolled-element:nth-of-type(3n)');
  const scrolled4 = el.querySelectorAll('.scrolled-element:nth-of-type(4n)');
  el.tl = gsap.timeline({paused: true});
  
  el.tl
    .to(scrolled1, {y: -150, ease: 'power2.inOut'})
    .to(scrolled2, {y: -100, ease: 'power2.inOut'})
    .to(scrolled3, {y: -200, ease: 'power2.inOut'})
    .to(scrolled4, {y: -250, ease: 'power2.inOut'})

 

The reason I need the nth-of-type is because this ties in to a piece of code where my client can repeat content, therefore repeating the same bits of code and using the same classes over and over again to build a gallery of content/images. I'd like to set it up so that I can define every 2nd, 3rd, 4th etc item to move a specific amount with different Y values.

 

Except using the above code, it bugs out and sometimes won't animate past the first element, so I'm wondering if this is the best way to even add multiples of selectors? 

 

If using nth-of-type isn't the best path to go down, I am all ears to other ideas too :) 

 

Many thanks in advance for any assistance!

See the Pen BaoPQNX by kiaramelissa (@kiaramelissa) on CodePen

Link to comment
Share on other sites

6 hours ago, le_kiki said:

I'd like to set it up so that I can define every 2nd, 3rd, 4th etc item to move a specific amount with different Y values.
...
using the above code, it bugs out and sometimes won't animate past the first element, so I'm wondering if this is the best way to even add multiples of selectors? 

This is because you have multiple tweens affecting the same element. For example, the 4th element in the list will be selected by scrolled1, scrolled2 and scrolled4! GSAP will then attempt to apply 3 tweens to that element, so obviously there will be some conflict. 

 

What you should do instead is a consistent multiplier with an additive. For example something like this:

const scrolled1 = el.querySelectorAll('.scrolled-element:nth-of-type(4n)');
const scrolled2 = el.querySelectorAll('.scrolled-element:nth-of-type(4n + 1)');
const scrolled3 = el.querySelectorAll('.scrolled-element:nth-of-type(4n + 2)');
const scrolled4 = el.querySelectorAll('.scrolled-element:nth-of-type(4n + 3)');

That way each element is only selected once and thus only has one tween affecting the same properties at any given time. 

  • Like 3
Link to comment
Share on other sites

Thank you for that @ZachSaucier ... that makes sense, but I still keep wondering if using the nth-of-type isn't the best method 🤔 It keeps jumping and skipping some boxes so I just tested and played with a few things some more and changed my code 

 

Check out the below:

 

const observerElements = document.querySelectorAll('.scrolled-element');

const observerOptions = {
  root: null,
  rootMargin: '0px 0px',
  threshold: 0
}

observerElements.forEach(el => {
  var scrolled = gsap.timeline({ paused: true });
  scrolled.to(el, {y: -100, ease: 'none'}, -0.5);
  scrolled.to(el, {y: -50, ease: 'none'}, -0.5);
  scrolled.to(el, {y: -200, ease: 'none'}, -0.5);
  
  var elObserver = new IntersectionObserver(
    entry => {
      if (entry[0].intersectionRatio > 0) {
        gsap.ticker.add(elProgressTween)
      } else {    
        gsap.ticker.remove(elProgressTween)
      }
    },
    observerOptions
  );
  
  var elProgressTween = () => {
    const scrollPosition = (window.scrollY + window.innerHeight);
    const elPosition = (scrollPosition - el.offsetTop);
    const durationDistance = (window.innerHeight + el.offsetHeight);
    const currentProgress = (elPosition / durationDistance);   
    scrolled.progress(currentProgress);
  }
  elObserver.observe(el);
});

 

I'd love to pick your brain about what I've achieved above if that is cool with you?

 

I removed the trigger-element and instead am just directly observing the scrolled-element. Is this ok practice? Do you foresee any issues arising from this? I am not entirely sure I understood the need for the trigger-element in any case, but most documentation or examples seems to have it

 

I also removed the nth-of-type and instead added some additional scrolled.to(el, {y: -100, ease: 'none'}, -0.5); with varying Y values to the forEach loop. 

This seems to now behave quite close to what I am after. What exactly is happening here? Is this targeting the 1st, then 2nd, then 3rd .scrolled-element ?

 

They seem to still jump and flicker from time to time so something still isn't functioning right..

 

Here is the updated CodePen with the code above: 

Link to comment
Share on other sites

13 hours ago, le_kiki said:

directly observing the scrolled-element. Is this ok practice? Do you foresee any issues arising from this?

Yes, it's often fine.

 

13 hours ago, le_kiki said:

I am not entirely sure I understood the need for the trigger-element in any case, but most documentation or examples seems to have it

Without knowing what demos you're referencing it's hard for me to say.

 

13 hours ago, le_kiki said:

They seem to still jump and flicker from time to time so something still isn't functioning right..

This is again because you have conflicting tweens, only for a different reason. This time it's because you have a position parameter of -0.5 on each tween, which means it should start half a second before the timeline starts playing. Since all three tweens have that and affect the same properties of the same elements, they are in conflict with each other. What are you trying to accomplish there? 

 

With something like the below where there's no conflict (because the tweens are sequenced) it shouldn't flicker:

var scrolled = gsap.timeline({ paused: true });
scrolled.to(el, {y: -100, ease: 'none'});
scrolled.to(el, {y: -50, ease: 'none'});
scrolled.to(el, {y: -200, ease: 'none'});

You might be interested in using GSAP's defaults so you don't have to repeat the same property and value in each tween:

var scrolled = gsap.timeline({ paused: true, defaults: { ease: 'none' } });
scrolled.to(el, {y: -100});
scrolled.to(el, {y: -50});
scrolled.to(el, {y: -200});

Or even using GSAP's keyframes:

var scrolled = gsap.to(el, { // A tween!
  paused: true, 
  defaults: { ease: 'none' }, 
  keyframes: [
    {y: -100},
    {y: -50},
    {y: -200}
  ]
});

I talk about these techniques and others in my article on animating efficiently (I highly recommend it).

  • Like 2
  • Thanks 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.
×
×
  • Create New...