Jump to content
Search Community

Animation messes up clicked quickly

Alexander75 test
Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

https://codesandbox.io/s/k0q179orqv

 

I thought i had this doing what i wanted it to...until i tried clicking the arrows quickly, when they're clicked too quickly the whole animation messes up, what is wrong with the way i'm approaching this animation? Is it something flawed with my execution of gsap? 

 

also, besides it being broken, there seems to be a pause between the time i click the down arrow and the animation actually firing, is there a way to fix this?

Link to comment
Share on other sites

It looks like you keep adding more and more tweens to the same timeline(s) on every click, at the same labels so you've got a ton of overlaps/conflicts.

 

Also keep in mind that when a to() tween begins, it reads the CURRENT value (at that time, on the first render) and records it as the "from" value, and also locks in the end (to) values, and interpolates between them. So the way you set it up, if someone clicks many times quickly, think about what'll happen to those later clicks. Let's say it's animating from 0 to 100 and then you click again when it's at 34. That's gonna be the "from" value for that tween. So it'll work fine, but then imaging what will happen when you reverse()! When it gets all the way back to its beginning, it'll be at 34...and stop. It's not a bug with GSAP at all - it's just a logic issue. 

 

You could solve it in many different ways. For example, you could seek(0) on the timeline (to get it back to the starting values) before you clear() it and re-populate it with tweens. Or you could use fromTo() tweens so that you have total control over both sides. Or there are a few other ways to address it, but I don't want to overwhelm you. 

 

A few other suggestions:

  1. use yPercent:-100 instead of y:"-100%". It's much cleaner and avoids potential ambiguities. 
  2. You could either clear() the timeline before adding new stuff or you could just kill() the old one and create a new timeline each time. Either one is fine. 

Does that help? 

  • Like 1
Link to comment
Share on other sites

Oh, and one other thing to be very careful about - it looks like you have CSS transitions applied to some things. Keep in mind that CSS transitions kick in every...single...time any value is changed. So if GSAP changes a value 60 times per second, that means CSS transitions will be like "browser, don't actually make that change yet...I'm gonna alter that over the course of ____ seconds." which can make things appear delayed. It has nothing to do with GSAP. I'd strongly recommend never animating something with GSAP and CSS simultaneously. 

 

Happy tweening!

Link to comment
Share on other sites

Thank you for your reply, i'm not entirely clear on where i'd put kill() or clear()

ive tried adding it before and after the tls like this: 

 

not sure if thats what you mean.. you said before adding new stuff.. 

 

switch (this.flag) {
case 0:
this.tl
.to(this.card0.current.firstChild, 0.7, { y: "-100%" }, "firstSlide")
.to(this.card0.current.lastChild, 0.7, { y: "-100%" }, "firstSlide")
.to(this.card1.current.firstChild, 0.7, { y: "-100%" }, "firstSlide")
.to(this.card1.current.lastChild, 0.7, { y: "-100%" }, "firstSlide")
.to(this.card2.current, 0.7, { y: "-100%" }, "firstSlide");
this.tl.play()
this.tl.kill()
this.flag += 1;

 

switch (this.flag) {
case 0:
this.tl.kill();
this.tl
.to(this.card0.current.firstChild, 0.7, { y: "-100%" }, "firstSlide")
.to(this.card0.current.lastChild, 0.7, { y: "-100%" }, "firstSlide")
.to(this.card1.current.firstChild, 0.7, { y: "-100%" }, "firstSlide")
.to(this.card1.current.lastChild, 0.7, { y: "-100%" }, "firstSlide")
.to(this.card2.current, 0.7, { y: "-100%" }, "firstSlide");
this.tl.play();
this.flag += 1;
 
 
Link to comment
Share on other sites

No no, if you kill() a timeline, it's for when you don't want to use it anymore :) So it'd be more like:

this.tl.kill();
this.tl = new TimelineLite();
this.tl.to(...);
//and you don't need to this.tl.play() because it plays by default)

 

Or, alternatively, you could clear() an reuse the same timeline instance:

this.tl.clear(); //dumps everything
this.tl.to(...); 
this.tl.restart(); //make sure it starts from the beginning

 

Link to comment
Share on other sites

I'm not quite sure what behavior you want (there are many ways to handle this), but I wonder if maybe you didn't read my full answer regarding the logic issues with when from/to values get recorded. Maybe it'd give you the behavior you want if you just seek(0) right before you clear(). Does that help? 

Link to comment
Share on other sites

I'm just trying to get it to reverse without offsetting the slides if clicked quickly.

 

I've tried your suggestion and ended up with something like this on each timeline, 

 

 

    switch (this.flag) {
      case 0:
        this.tl
          .seek(0)
          .clear()
          .to(this.card0.current.firstChild, 0.7, { y: "-100%" }, "firstSlide")
          .to(this.card0.current.lastChild, 0.7, { y: "-100%" }, "firstSlide")
          .to(this.card1.current.firstChild, 0.7, { y: "-100%" }, "firstSlide")
          .to(this.card1.current.lastChild, 0.7, { y: "-100%" }, "firstSlide")
          .to(this.card2.current, 0.7, { y: "-100%" }, "firstSlide");
        this.flag += 1;
        this.tl.restart();
        break;
      
    }

It doesn't seem to be working for me. I'm going to try to specify fromTo values like you suggested. 

Link to comment
Share on other sites

Well, there are quite a few logic issues and various ways you could handle the fast-clicking so I just took a crack at re-coding the animation parts. Codesandbox wasn't allowing me to save anything, so I'll just paste the contents of the About.js file here: 

 

import React, { Component } from "react";
import Card from "./Card";
import { TimelineLite } from "gsap";
import styled from "styled-components";

class About extends Component {
  constructor(props) {
    super(props);
    this.createCards = this.createCards.bind(this);
    this.tl = new TimelineLite({});

    this.card0 = React.createRef();
    this.card1 = React.createRef();
    this.card2 = React.createRef();
    this.button = React.createRef();
    this.flag = 0;
  }

  state = {
    aboutCards: [
      { id: "img01", theme: "one ", header: "testing...1" },
      { id: "img02", theme: "two", header: "testing..2" },
      { id: "img03", theme: "three", header: "testing..3" }
    ]
  };

  slideUp = () => {
    this.flag++;
    if (this.flag === this.state.aboutCards.length) {
      this.flag--;
      return;
    }
    var oldCard = this["card" + (this.flag - 1)].current,
      newCard = this["card" + this.flag].current,
      newPercent = -100 * this.flag;
    if (!this.tl.isActive()) {
      this.tl.clear().seek(0);
    }
    this.tl
      .to(oldCard.firstChild, 1, { yPercent: newPercent })
      .to(oldCard.lastChild, 1, { yPercent: newPercent }, "-=1")
      .fromTo(
        [newCard.firstChild, newCard.lastChild],
        1,
        { yPercent: newPercent + 100 },
        { yPercent: newPercent },
        "-=1"
      );
  };

  slideDown = () => {
    this.flag--;
    if (this.flag < 0) {
      this.flag = 0;
      return;
    }
    var oldCard = this["card" + (this.flag + 1)].current,
      newCard = this["card" + this.flag].current,
      newPercent = -100 * this.flag;
    if (!this.tl.isActive()) {
      this.tl.clear().seek(0);
    }
    this.tl
      .to(oldCard.firstChild, 1, { yPercent: newPercent })
      .to(oldCard.lastChild, 1, { yPercent: newPercent }, "-=1")
      .fromTo(
        [newCard.firstChild, newCard.lastChild],
        1,
        { yPercent: newPercent - 100 },
        { yPercent: newPercent },
        "-=1"
      );
  };

  render() {
    return (
      <Wrapper className="cardWrapper">
        {this.state.aboutCards.map(this.createCards)}
      </Wrapper>
    );
  }

  createCards(card, i) {
    return (
      <Card
        id={i}
        key={i}
        card={card}
        info={this.state.aboutCards}
        ref={{ ref1: this["card" + i], ref2: this.button }}
        slideUp={this.slideUp}
        slideDown={this.slideDown}
      />
    );
  }
}

const Wrapper = styled.div`
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
`;

export default About;

 

I hope that helps. 

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