javascriptreactjsanimationstatepose

Animation always firing because state always changes on scroll in React


So I'm using React/Pose to animate a Showcase component like the Postmates fleet website seen here:

Here's an example of it

I'm almost there but I can't figure out the last bit. Basically, I have an array of items:

const chapters = [
  {
    content: "1",
    title: "Create your profile"
  },
  {
    content: "2",
    title: "See their rating"
  },
  {
    content: "3",
    title: "Forgot annoying requests"
  },
  {
    content: "4",
    title: "Block them"
  },
  {
    // tslint:disable-next-line:max-line-length
    content: "5",
    title: "Make your decision"
  },
  {
    content: "6",
    title: "Time elapses"
  },
  {
    content: "7",
    title: "Complete"
  }
];

And a handleScroll(e) method which does a few things:

  1. Detects when scrolling through the component
  2. When scrolling through a chapter, it updates this.state.content and this.state.title with the correct chapter content from chapters:

--

// loop through the chapters
for (let index = 0; index < chapters.length; index++) {
  const chapterHeight = componentHeight;
  topOfChapter.push(componentTopY + chapterHeight * index);
  bottomOfChapter.push(componentTopY * 2 + chapterHeight * index);

  // when scrolling through the component
  if (
    scrollY >= component.offsetTop &&
    scrollY <= component.offsetHeight + window.innerHeight
  ) {
    // when scrolling through a chapter
    if (
      scrollY >= topOfChapter[index] &&
      scrollY <= bottomOfChapter[index]
    ) {
      // tslint:disable-next-line:no-console
      console.log(`Scrolling through chapter ${index + 1}`);
      this.setState({
        animate: !this.state.animate,
        content: chapters[index].content,
        title: chapters[index].title
      });
      // tslint:disable-next-line:no-console
      console.log(topOfChapter[index], "topOfChapter[index]");
      // tslint:disable-next-line:no-console
      console.log(bottomOfChapter[index], "bottomOfChapter[index]");
    }
  } else {
    // tslint:disable-next-line:no-console
    console.log("exited the component");
  }
}

The problem is that my animation is always firing because this.state.animate is always changing on scroll.

I need to fire the animation only when the chapter changes, not all the time that is scrolling, but I cannot figure out how.

I asked a question a few hours ago, which did not have the animations, but I think the question only really makes sense in the context of animating.

Any help is really appreciated!

Codesandbox


Solution

  • https://codesandbox.io/s/youthful-leftpad-33j4y should be the effect you are looking for.

    There are number of things I changed to get this into a usable state and assumptions I had to make.


    Once the exit animation is finished, you want to start the enter animation of the next chapter. The problem you had was, you already lost track of the previous chapter, as you had already updated the state to the next chapter, whilst also triggering the animation repeatedly.

    I've resolved this by now tracking, the currentChapter (the chapter which is currently visible) and the nextChapter (the chapter you wish to transition into).


        <Text
            pose={animate ? "enter" : "exit"}
            onPoseComplete={this.onPoseComplete}
        >
    

    https://popmotion.io/pose/api/posed/#posed-props-onposecomplete

    When an animation is finished, pose will callback on onPoseComplete. If the animation was false, set the currentChapter to be the nextChapter e.g.

    When we exit Chapter 0, going into 1:

    {
       animate: false,
       currentChapter: 0,
       nextChapter: 1,
    }
    

    This starts the Exit animation on the currentChapter. When the animation finishes, onPoseComplete is called and, if the animate was false i.e. exit animation is now finished, update the state to:

    {
       animate: true,
       currentChapter: 1,
       nextChapter: 1,
    }
    

    Which will now starts the enter animation on currentChapter, which was now changed to 1.


    Now that we are tracking the chapter index, we don't need the title, content state which I've removed.

    I've simplified the handleScroll logic to what I think is equivalent. By dividing the scrollY with the window.innerHeight, we can get the index of the chapter you want visible.


      style={{ height: `${100 * chapters.length}vh` }}
    

    We know how many chapters there are, so simply multiplied the number of chapters with 100vh

    onPoseComplete = () => {
    

    As you are using create-react-app, you have transform-class-properties which allows you to create an arrow function rather than .bind.