【问题标题】:GSAP: Managing multiple tweens and timelines at onceGSAP:一次管理多个补间和时间线
【发布时间】:2016-11-07 10:09:00
【问题描述】:

首先,我是 GSAP 的新手,所以请多多包涵。我尽力描述我正在努力解决的问题。

为了制作 SVG 文件的动画,我开始深入研究 GSAP。我为我的 SVG 文件中的不同元素创建了几个 Tweens。 一切正常,动画效果符合预期。

由于我的 SVG 中有很多元素要制作动画,因此我开始添加 TimelineLite 以更好地控制整个事情。

此时,我的脚本文件如下所示:

首先我声明了我想要动画的所有元素:

this.phone = document.querySelector('#gsap-phone-svg');
this.body = document.querySelectorAll('.gsap-phone-body');
this.body.shadow = this.phone.querySelectorAll('.gsap-phone-body-shadow');
this.body.mask = this.phone.querySelectorAll('.gsap-phone-body-mask');
this.layer = this.phone.querySelectorAll('.gsap-phone-layer');
this.screen = this.phone.querySelectorAll('.gsap-phone-screen');
this.screen.clipPath = this.phone.querySelectorAll('.gsap-phone-screen-clipPath');
.
.
.
// many more following here

比我创建了一个对象来保存我的补间:

const tweens = {};

// creating tweens

tweens.body = TweenMax.to(this.body, this.animDur/2,{
y: this.maxExpand/6*-1,
ease: this.ease
});

.
.
.

// many more following here

最后,我将所有补间添加到一个临时数组中,然后像这样将它传递到一个新的 TimelineLite() 中:

const tl = new TimelineLite();
tl.add(tweensArray, 0, "start", 0.05);

到目前为止,我猜这似乎是合乎逻辑的......现在是症结所在。您可能已经注意到或没有注意到我喜欢 20 多个元素来制作动画。这就是为什么为每个元素单独添加补间变得非常混乱。我也希望整个主要时间线是重复的。这里的问题是,我希望我的所有补间在“动画内”和“动画外”上都有不同的缓动,并且我希望在“动画外”上没有交错。

所有这些小问题让我想到了一种替代解决方案来管理我的补间和时间线的创建。

我想到的最方便的解决方案是将有关我的动画元素和时间线的所有信息存储在一个对象中:

const animation = {
   settings : {
      duration: 1.5,
      expansion: 1,
      easeIn: Elastic.easeOut.config(1, 0.5),
      easeOut: Power2.easeInOut
   },
   timelines : {
      main : {
         delay : 0,
         paused : true,
         align : 'start',
         stagger : 0.05,
      },
      test : {
         delay: 0,
         paused : true,
         align : 'start',
         stagger : 0.5
      }
   },
   items : {
      phone : {
         id : '#gsap-phone-svg',
         start : { },
         end : { },
         timeline : 'test',
      },
      body : {
         class : '.gsap-phone-body',
         start : {
            y : 0,
         },
         end : {
            y : -21,
         },
         timeline : 'test',
      },
      layer : {
         class : '.gsap-phone-layer',
         start : {
            y : 0,
         },
         end : {
            y : -62.5,
         },
         timeline : 'main',
      },
      radar : {
         class : '.gsap-phone-radar',
         start : {
            y : 0,
         },
         end : {
            y : -25,
         },
         timeline : 'main',
      },
      radarBase : {
         class : '.gsap-phone-radar-base',
         start: {
            y : 0,
         },
         end : {
            y: -16,
         },
         timeline : 'test',
      },
      ringOne : {
         class : '.gsap-phone-radar-ring-1',
         start : {
            y : 0,
         },
         end : {
            y: -25,
         },
         timeline : 'test',
      },
      ringTwo : {
         class : '.gsap-phone-radar-ring-2',
         start : {
            y : 0,
         },
         end : {
            y: -41,
         },
         timeline : 'main',
      },
      ringThree : {
         class : '.gsap-phone-radar-ring-3',
         start : {
            y : 0,
         },
         end : {
            y: -62.5,
         },
         timeline : 'main',
      },
      cancel : {
         class : '.gsap-phone-cancel',
         start : {
            y : 0,
         },
         end : {
            y: -50,
         },
         timeline : 'main',
      },
      submit : {
         class : '.gsap-phone-submit',
         start : {
            y : 0,
         },
         end : {
            y: -100,
         },
         timeline : 'main',
      }
   }
};

我写了这个“createTweens”方法来返回 GSAP Tweens

/* create tweens */
function createTweens(anim){
   const el = anim.items;
   const settings = anim.settings;
   const duration = settings.duration;
   const easeIn = settings.easeIn;
   const easeOut = settings.easeOut;
   const tweensIn = [];
   const tweensOut = [];
   let tempTween = null;

   for (const key in el){
      const curEl = el[key];
      const selector = curEl.class || el[key].id;
      const startPoint = curEl.start || '';
      const endPoint = curEl.end || '';
      const timeline = curEl.timeline || '';
      const nodes = document.querySelectorAll(selector);

      nodes.forEach(object => {
         tweensIn.push(getTween(object, endPoint, duration, easeIn, `${timeline}-in`));
         tweensOut.push(getTween(object, startPoint, duration, easeOut, `${timeline}-out`));
      });
   }

   function getTween(tw, twValues, twDur, twEase, tl){
      const vars = twValues;
      vars.paused = false;
      vars.ease = twEase;
      tempTween = TweenMax.to(tw, twDur/2, vars);
      tempTween.data = {
         timelineName : tl
      };
      return tempTween;
   }

   return tweensIn.concat(tweensOut);
}

还有一个返回时间线的函数:

/* create timelines */
function createTimelines(anim, tweens){
   const el = anim.timelines;
   const timelines = {};
   // timelines.mainIn = new TimelineLite();
   // timelines.mainOut = new TimelineLite();
   const tweensForTimelines = {};

   for(const key in el){
      const delay = el[key].delay;
      const paused = el[key].paused;
      const align = el[key].align;
      const stagger = el[key].stagger;
      const vars = {};
      vars.paused = paused;

      timelines[`${key}-in`] = new TimelineLite(vars);
      timelines[`${key}-in`].delay = delay;
      timelines[`${key}-in`].align = align;
      timelines[`${key}-in`].stagger = stagger;

      timelines[`${key}-out`] = new TimelineLite(vars);
      timelines[`${key}-out`].delay = delay;
      timelines[`${key}-out`].align = align;
      timelines[`${key}-out`].stagger = stagger;

      tweensForTimelines[`${key}-in`] = [];
      tweensForTimelines[`${key}-out`] = [];
   }

   if(Object.keys(tweensForTimelines).length !== 0){
      for(let i = 0; i < tweens.length; i++){
         const curTween = tweens[i];
         const tlTarget = curTween.data.timelineName;
         tweensForTimelines[tlTarget].push(curTween);
      }
   }


   for(const key in timelines){
      try{
         timelines[key].add(tweensForTimelines[key], timelines[key].delay, timelines[key].align, timelines[key].stagger);
         console.log(TweenMax.getTweensOf(timelines[key]));
         timelines[key].data = tweensForTimelines[key];
      } catch(e){

      }
   }

   return timelines;
}

如果我执行以下代码,它将播放我的“主要”时间线。

const tweens = createTweens(animation);
const timelines = createTimelines(animation, tweens);
timelines['main-in'].play();

到目前为止,这确实有效。但是,如果我尝试将“main-in”时间线添加到新时间线,它就不再工作了。

const anotherTimeline = new TimelineLite();
anotherTimeline.add(timelines['main-in']);
anotherTimeline.play();

为了调试这个,我试过了

TweenMax.getTweensOf(anotherTimeline);

但是所有这些返回的是一个空数组。然后我为我的“主要”时间线记录了相同的内容:

console.log(TweenMax.getTweensOf(timelines['main-in'])); 

还返回一个空数组,这让我很困惑,因为即使这个时间线似乎是空的,它也会播放我的“动画”:

timelines['main-in'].play()

我真的被困在这里,非常感谢更高级的用户或任何对此有想法的人的帮助。我希望你们能够关注我......如果没有,看看提供的codepen..

更新:Click for Codepen DEMO

提前致谢!

【问题讨论】:

    标签: javascript animation svg gsap


    【解决方案1】:

    我没有时间解析你的所有代码并制作一个完整的替代品,但它确实让我觉得有点过度设计,但我也意识到我的大脑可能只是工作方式不同,这是一个风格选择(没有好坏之分)。

    我发现最直观、可读和灵活的方法是将动画分解成块,然后放入函数中,每个函数都返回一个 TimelineLite/Max 或 TweenLite/Max,您可以嵌套在主时间轴中(如果你这么选择)。

    有点像:

    function phoneIntro() {
        var tl = new TimelineLite();
        tl.to(...)
          .to(...);
        return tl;
    }
    
    function flipPhone() {
        var tl = new TimelineLite();
        tl.to(...);
        return tl;
    }
    
    var master = new TimelineMax({repeat:-1});
    master.add(phoneIntro(), 0)
          .add(flipPhone(), "-=1"); //overlap by 1 second
          ...
    

    当然,如果您有很多元素要制作相同类型的动画,那么这种模块化方法也非常有用,因为您可以将所需的任何变量输入到执行工作的函数中你和吐回动画。

    function buildStep(element, duration, x, y) {
        return TweenMax.to(element, duration, {x:x, y:y, rotation:30});
    }
    

    希望当您创建一些模块化功能时,您可以清楚地了解事情的灵活性,您只需提供您需要的任何东西。整个方法也可以更快地编辑动画(并尝试时间等),因为在代码中找到自己的位置非常简单。 “我想让介绍的时间延长 2 秒……”只需找到 phoneIntro() 函数并在其中调整内容即可。完毕。由于您在主 TimelineMax 中将事物与相对时间串在一起,因此您对第一个模块化块所做的更改会自动推迟后续事物的时间,并且它会很好地通过。没有弄乱 20 种不同的延迟。

    另外,TweenLite.getTweensOf(anotherTimeline) 返回空数组的原因是该方法找到了该对象的补间。就像,从字面上看,如果你正在补间时间线本身(也许是它的进度),它会返回那个补间。听起来你假设它得到了一些时间线实例的补​​间 INSIDE(它没有)。但是,如果这就是您所追求的,那么它就像 anotherTimeline.getChildren() 一样简单。

    我希望这至少会有所帮助。随时在http://greensock.com/forums 的论坛中发布问题,那里有一个很棒的 GSAP 专业人士社区。这是一个学习的好地方,即使你从来没有发布过问题:)

    补间快乐!

    【讨论】:

    • 非常感谢您的详细回答。你可能是对的,我可能在这里过度设计了一点;D.我已经接受了将所有内容拆分多一点的问题,并且只具有返回补间或时间线的功能。我也已经在 Greensock 论坛上发布了这个,但还没有得到任何回应,这就是我在这里发布它的原因;D.
    • 顺便说一句,尽管这个问题可能与 svg 相关,但我在制作我在另一个主题中发布的当前动画时遇到了另一个问题。 stackoverflow.com/questions/40482500/…如果您愿意,请查看它,也许您对此有答案:)。
    • 是的,不幸的是,SVG 中的遮罩(尤其是动画)目前在各种浏览器中有点混乱(与 GSAP 完全无关)。在顶部添加一个过滤器,你就是在自找麻烦:)我希望我有一个简单的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-05
    • 2016-02-09
    • 1970-01-01
    • 1970-01-01
    • 2022-06-13
    • 1970-01-01
    相关资源
    最近更新 更多