Jump to content
Search Community

Multiple timelines for the same property in ScrollTrigger

Jose Pio test
Moderator Tag

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

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

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

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

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