Jump to content
GreenSock

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

Multiple timelines for the same property in ScrollTrigger

Recommended Posts

Hello GSAP community! How's everybody? I would like to know if you could help me. I have two timelines that are triggered by ScrollTrigger when I am within the range of the start attribute. The onEnter and onLeaveBack events of the same ScrollTrigger instance work with timeline.play () and timeline.reverse () respectively, the problem starts when the two timelines are fired and loses the smoothness of the transition.

 

Example: When I move from div.p3 to div.p1 tl2 is interrupted, it is interrupted by tl1 and overwrites everything. I don't quite understand if it could be fixed with invalidate or overwrite.

 

Thank you very much in advance!

 

Here I attach my codepen:

 

<body>
  <div id="box"></div>
  <div class="p1 pages"></div>
  <div class="p2 pages"></div>
  <div class="p3 pages"></div>
</body>

 

const box = document.getElementById('box')
const tl1 = gsap.timeline({paused: true})
const tl2 = gsap.timeline({paused: true})

tl1.to(box, {
  duration: 3,
  x: 350,
  y: 30,
  rotation: 45,
  ease: "expo.inOut"
})

tl2.to(box, {
  duration: 3,
  x: 20,
  y: 20,
  rotation: -125,
  ease: "expo.inOut"
})

ScrollTrigger.create({
  trigger: ".p2",
  start: "0 top",
  markers: true,
  onEnter: () => tl1.play(),
  onLeaveBack: () => tl1.reverse()
})

ScrollTrigger.create({
  trigger: ".p3",
  start: "0 top",
  markers: true,
  onEnter: () => tl2.play(),
  onLeaveBack: () => tl2.reverse()
})

 

PD: Excuse my bad English.

See the Pen PoZVEva by cpiocova (@cpiocova) on CodePen

Link to comment
Share on other sites

Hey Jose and welcome to the GreenSock forums.

 

There are several ways to handle this sort of thing, the best depends on the effect that you want.

 

If you want it to be smooth (no jumps) no matter what you have a couple of options:

  • Use a single timeline to animate between the various states. Inside of the callbacks you animate the timeline's progress to a certain location. You should probably use overwriting on that tween. It'd look something like this:
    // helper function
    function getProgressOfLabel(tl, label) {
      return tl.labels[label] / tl.totalDuration();
    }
    
    // in the ScrollTrigger
    onEnter: () => gsap.to(tl, {progress: getProgressOfLabel(tl, "myLabel"), ease: "none", overwrite: "auto"})

     

  • Create tweens when you need to use them (instead of using ones created in the past). That way they can use the current value as the start value. You should use overwriting on that tween. This is probably the method that I'd use. It'd look something like this:
    onEnter: () => gsap.to(box, {
      duration: 3,
      x: 350,
      y: 30,
      rotation: 45,
      ease: "expo.inOut",
      overwrite: "auto"
    })

     

  • Like 1
Link to comment
Share on other sites

Thanks for helping me Zach. The first one I do not understand very well, I apply it, but I have no results yet, there are no tween. The second option is very good but I have a somewhat long timeline and you would have to build your reverse as well.

 

Is that I have a mesh in position fixed, which rotates and moves its position as I scroll. If I scroll slowly, so that the tween duration ends before triggering the onEnter and onLeaveBack callbacks, it works otherwise, it doesn't work. But what I want is that if I scroll very fast, the timelines run smoothly.

 

Thanks Zach and sorry for the inconvenience!

Link to comment
Share on other sites

To me it seems that this is primarily a logical issue: what do you want to happen when there's conflict? 

  • Like 1
Link to comment
Share on other sites

Here I attach the screenshots of the behaviors.

1: Without Scroll Trigger:

values1-opt.png

 

 

2: ScrollTrigger onEnter (Complete First Timeline):

values2-opt.png

 

3: ScrollTrigger onEnter (Complete second timeline):

values3-opt.png

 

 

from 1 to 2 is fine.

from 2 to 3, this fine.

the problem is when I scroll quickly from 1 to 3 or from 3 to 1

 

 

IN brief moments I will edit the post and attach a video that exemplifies the expected result and the result obtained.

 

Thanks in advance Zach !!

 

 

Link to comment
Share on other sites

To do that I still recommend the second approach. You could use a timeline instead of a tween. Unfortunately it's hard for us to be able to help as we can't see the actual project.

Link to comment
Share on other sites

Hi! Here I uploaded to YouTube the videos of the expected and obtained behavior.

 

The two timelines modify the properties of the same object so there is a problem when the two timelines are fired at the same moment (When I scroll faster for the duration of each timeline)

 

Expected Behavior:

 

Obtained Behavior:

 

Link to comment
Share on other sites

I understand what you're wanting. I provided insight as to how you can fix this (the second method in my first post). But we can't help you implement it into your project since you haven't shared any code for it.

Link to comment
Share on other sites

Here is an excerpt from where I have the conflict. Thanks for the support Zach and sorry for my slowness!

 

ironScroll.js  Here are the timelines called in Main.js

export const iron1 = (model, material, tl, node) => {
const aboutTitle = node.querySelectorAll(".about-info-line > div")
const paragraph = node.querySelector(".about-info p")
  tl.to(
    material,
    {
      duration: 1.5,
      ease: "expo.inOut",
    },
    "iron"
  )

  tl.to(
    material.emissive,
    {
      duration: 1.5,
      r: 1,
      g: 1,
      b: 0,
      ease: "expo.inOut",
    },
    "iron"
  )
  tl.to(
    model.position,
    {
      duration: 2,
      x: 2.6,
      y: -8.77,
      z: -8.47,
      ease: "expo.inOut",
    },
    "iron"
  )
  tl.to(
    model.rotation,
    {
      duration: 2,
      x: 0.39,
      y: -1.09,
      ease: "expo.inOut",
    },
    "iron"
  )
  tl.from(
    [...aboutTitle],
    {
      duration: 1,
      y: 44,
      ease: "power3.out",
      stagger: 0.15,
    },
    "iron+=.6"
  )
  tl.from(
    paragraph,
    {
      duration: 1,
      y: 20,
      ease: "power3.out",
    },
    "iron+=1.4"
  )
  tl.to(
    paragraph,
    {
      opacity: 1,
      duration: 1,
      ease: "power3.out",
    },
    "iron+=1.4"
  )
  tl.to(
    [...aboutTitle],
    {
      duration: 0,
      opacity: 1,
    },
    "iron"
  )
}

export const iron2 = (model, material, tl, node) => {
  tl.to(
    model.rotation,
    {
      duration: 2,
      x: 0,
      y: -2.34,
      z: -0.05,
      ease: "expo.inOut",
    },
    "iron"
  )
  tl.to(
    model.position,
    {
      duration: 2,
      x: 3.47,
      y: -7.1,
      z: -13.27,
      ease: "expo.inOut",
    },
    "iron"
  )
}

 

 

Main.js 

ironGeo.current and ironMat.current are the objects that the timeline will interpolate

 

import {iron1, iron2} from './ironScroll'

const animIron1 = gsap.timeline({ paused: true })
const animIron2 = gsap.timeline({ paused: true })

const contactWrapper = document.querySelector(".contact-wrapper")
iron1(ironGeo.current, ironMat.current, animIron1, contactWrapper)
iron2(ironGeo.current, ironMat.current, animIron2, contactWrapper)

ScrollTrigger.create({
  trigger: ".sp-1",
  start: "0 top",
  markers: true,
  onEnter: () => animIron1.play(),
  onLeaveBack: () => animIron1.reverse(),
})

ScrollTrigger.create({
  trigger: ".sp-2",
  start: "0 top",
  markers: true,
  onEnter: () => animIron2.play(),
  onLeaveBack: () => animIron2.reverse(),
})
Link to comment
Share on other sites

I would make a function that creates a new animation. Something like this:

export const animIron2 = (model, material, node) => {
  const tl = gsap.timeline({defaults: {overwrite: "auto"}});
  tl.to(
    model.rotation,
    {
      duration: 2,
      x: 0,
      y: -2.34,
      z: -0.05,
      ease: "expo.inOut",
    },
    0
  )
  tl.to(
    model.position,
    {
      duration: 2,
      x: 3.47,
      y: -7.1,
      z: -13.27,
      ease: "expo.inOut",
    },
    0
  )
}

Then call that function when you need it:

ScrollTrigger.create({
  trigger: ".sp-2",
  start: "0 top",
  markers: true,
  onEnter: animIron2
})

 

  • Like 1
Link to comment
Share on other sites

Hello, good day! Thanks Zach. But I still have not been able to solve because with this solution I can make the transition from div.sp1 to div.sp2 but I cannot reverse (from div.sp2 to div.sp1)

Link to comment
Share on other sites

20 minutes ago, Jose Pio said:

I cannot reverse

Just add the same animation in the onEnterBack:

ScrollTrigger.create({
  trigger: ".sp-2",
  start: "0 top",
  markers: true,
  onEnter: animIron2,
  onEnterBack: animIron2
})

 

  • Like 1
Link to comment
Share on other sites

4 hours ago, ZachSaucier said:

Just add the same animation in the onEnterBack:


ScrollTrigger.create({
  trigger: ".sp-2",
  start: "0 top",
  markers: true,
  onEnter: animIron2,
  onEnterBack: animIron2
})

 

 

Thanks Zach, it already works with this solution. The only problem is that I have to write more code (2 animation logics: one with the animation logic for Enter and one with the animation logic for Leave. While with the timeline I only created 1 animation logic and I I was in charge of doing play and reverse but it was impossible for me to work and I was left with the uncertainty if there will be any way to do it with the timeline). But it works this way, thank you very much !!

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.
×