Introduction

If you have a great design for you website but you need to add those amazing animations to make things even better. I usually end up installing GreenSock and creating a class that manages all my javascript transitions per component. After a couple of projects you end up copy and pasting the same code over and over again and that just doesn’t feel right. Therefore the AbstractTransitionController was created.

What does it do?

The AbstractTransitionController is a base class that you extend to provide all the necessary transition functionality to TransitionController that is aimed on your desired framework.

Documentation

This demo only shows the basics of what is possible with the transition controller. For more detailed documentation please have a look at the GitBook page!

View full documentation

Examples

The examples uses Vue.js to render out a component. But you can do this for any other framework, the only thing you need to do is extend the AbstractTransitionController and create the a method to get your component instance based on your framework.

TransitionIn/TransitionOut Example

The main functionality of this module is to transition in and transition out your components. Below you can see an example of a component first transitioning in and when it's completed transitioning out

Looping animation example

The example uses vue.js to render out a component. But you can do this for any other framework, the only thing you need to do is extend the AbstractTransitionController and create the a method to get your component instance based on your framework.

Reset Timeline Example

You can reset the timelines of a component and provide configuration for child components that you also want to reset. In the current example the x position is random every time you press reset transition in timeline.

Events:

  • {{event.data}}

AbstractVueTransitionController.ts

import { Vue } from 'vue/types/vue';
import isElement from 'lodash/isElement';
import isString from 'lodash/isString';
import { AbstractTransitionController, TransitionDirection } from 'transition-controller';

export default abstract class AbstractVueTransitionController extends AbstractTransitionController<Vue> {
  /**
   * @protected
   * @method getComponent
   * @param {string | HTMLElement | Vue} component
   * @returns {Vue}
   */
  protected getComponent(component: string | HTMLElement | Vue): Vue {
    let instance: Vue;

    if (isElement(component)) {
      instance = this.parentController.$children.find(child => child.$el === component);
    } else if (isString(component)) {
      const key = Object.keys(this.parentController.$refs).find(key => key === component);
      instance = this.parentController.$refs[key];
    } else {
      instance = component;
    }

    if (instance === undefined) {
      throw new Error(`The requested component [${component}] does not exist`);
    }

    return instance;
  }
}

dummy-component/DummyComponentTransitionController.ts

The example uses multiple timelines per component, this is not something that is required. If no transitionIn/transitionOutId is provided the id argument can be ignored!

import { TimelineLite, TimelineMax, Elastic } from 'gsap';
import AbstractVueTransitionController from '../AbstractVueTransitionController';
import { TransitionDirection } from 'transition-controller';

export const TransitionId = {
  [TransitionDirection.IN]: {
    LEFT_TO_RIGHT: 'left-to-right',
    RIGHT_TO_LEFT: 'right-to-left',
  },
  [TransitionDirection.OUT]: {
    TO_RIGHT: 'to-right',
    TO_LEFT: 'to-left',
  },
};

export default class DummyComponentTransitionController extends AbstractVueTransitionController {
/**
   * @public
   * @method setupTransitionInTimeline
   * @param {TimelineLite | TimelineMax} timeline
   * @param {Vue} parent
   * @param {string} id
   */
  public setupTransitionInTimeline(timeline: TimelineLite | TimelineMax, parent: Vue, id: string ): void {
    switch (id) {
      case TransitionId[TransitionDirection.IN].RIGHT_TO_LEFT:
        timeline.fromTo(
          parent.$el,
          1,
          {
            xPercent: 100,
            autoAlpha: 0,
          },
          {
            xPercent: 0,
            autoAlpha: 1,
            ease: Elastic.easeOut,
          },
        );
        break;
      case TransitionId[TransitionDirection.IN].LEFT_TO_RIGHT:
        timeline.fromTo(
          parent.$el,
          1,
          {
            xPercent: -100,
            autoAlpha: 0,
          },
          {
            xPercent: 0,
            autoAlpha: 1,
            ease: Elastic.easeOut,
          },
        );
        break;
      default:
        timeline.fromTo(parent.$el, 1, { autoAlpha: 0 }, { autoAlpha: 1 });
        break;
    }
  }

  /**
   * @public
   * @method setupTransitionOutTimeline
   * @param { TimelineMax } timeline
   * @param { Vue } parent
   * @param { string } id
   */
  public setupTransitionOutTimeline(timeline: TimelineLite | TimelineMax, parent: Vue, id: string ): void {
    switch (id) {
      case TransitionId[TransitionDirection.OUT].TO_LEFT:
        timeline.to(parent.$el, 1, {
          xPercent: -100,
          autoAlpha: 0,
          ease: Elastic.easeIn,
        });
        break;
      case TransitionId[TransitionDirection.OUT].TO_RIGHT:
        timeline.to(parent.$el, 1, {
          xPercent: 100,
          autoAlpha: 0,
          ease: Elastic.easeIn,
        });
        break;
      default:
        timeline.to(parent.$el, 1, { autoAlpha: 0 });
        break;
    }
  }

  /**
   * @public
   * @method setupLoopingAnimationTimeline
   * @param {TimelineMax} timeline
   * @param {Vue} parent
   * @param {string} id
   * @description overwrite this method in the parent class
   * */
  public setupLoopingAnimationTimeline(timeline: TimelineLite | TimelineMax, parent: Vue,  id: string): void {}
}

dummy-component/index.js

import DummyComponentTransitionController from './DummyComponentTransitionController';

export default {
  name: 'DummyComponent',
  template: `<div class="panel panel-info">
                <div class="panel-heading">
                  <h3 class="panel-title">DummyComponent</h3>
                </div>
                <div class="panel-body">
                  Hi i'm a dummy component
                </div>
              </div>`,
  mounted() {
    this.transitionController = new DummyComponentTransitionController(this, {
      debug: true,
      name: 'DummyComponent',
      useTweenMax: false,
      transitionInId: TransitionId[TransitionDirection.IN].LEFT_TO_RIGHT,
      transitionOutId: TransitionId[TransitionDirection.OUT].TO_RIGHT,
    });
  },
};

Main Vue.js application

import Vue from 'vue/dist/vue.esm';
import DummyComponent from './component/dummy-component';

new Vue({
  el: '#app',
  components: {
    DummyComponent,
  },
  mounted() {
    const {transitionController} = this.$refs.DummyComponent;
    // Transition in then out
    transitionController
      .transitionIn()
      .then(() => transitionController.transitionOut())
  },
});

Looping animation Example

loop-component/LoopComponentTransitionController.ts

import { TimelineLite, TimelineMax, Elastic } from 'gsap';
import Vue from 'vue';
import AbstractVueTransitionController from '../AbstractVueTransitionController';

export const TransitionId = {
  [TransitionDirection.IN]: {},
  [TransitionDirection.OUT]: {},
  LOOP_1: 'loop-1',
  LOOP_2: 'loop-2',
};

export default class DummyComponentTransitionController extends AbstractVueTransitionController {
  /**
   * @public
   * @method setupTransitionInTimeline
   * @param {TimelineLite | TimelineMax} timeline
   * @param {Vue} parent
   * @param {string} id
   */
  public setupTransitionInTimeline(timeline: TimelineLite | TimelineMax, parent: Vue, id: string ): void {}

  /**
   * @public
   * @method setupTransitionOutTimeline
   * @param { TimelineMax } timeline
   * @param { Vue } parent
   * @param { string } id
   */
  public setupTransitionOutTimeline(timeline: TimelineLite | TimelineMax, parent: Vue, id: string ): void {}

  /**
   * @public
   * @method setupLoopingAnimationTimeline
   * @param {TimelineMax} timeline
   * @param {Vue} parent
   * @param {string} id
   * @description overwrite this method in the parent class
   * */
  public setupLoopingAnimationTimeline(timeline: TimelineLite | TimelineMax, parent: Vue,  id: string): void {
    const { button } = parent.$refs;

    switch (id) {
      case TransitionId.LOOP_1:
        timeline.fromTo(button, 2, {rotation: 0}, {rotation: 360, ease: Elastic.easeOut});
        break;
      case TransitionId.LOOP_2:
        timeline.fromTo(button, 1, {yPercent: 0}, {yPercent: 100, ease: Elastic.easeInOut});
        timeline.fromTo(button, 1, {yPercent: 100}, {yPercent: 0, ease: Elastic.easeInOut});
        break;
      default:
        timeline.fromTo(button, 1, {xPercent: 0}, {xPercent: 100, ease: Elastic.easeInOut});
        timeline.fromTo(button, 1, {xPercent: 100}, {xPercent: 0, ease: Elastic.easeInOut});
        break;
    }
  }
}

loop-component/index.js

import LoopComponentTransitionController, {TransitionId} from './LoopComponentTransitionController';
import TransitionDirection from '../../../../src/lib/enum/TransitionDirection';

export default {
  name: 'LoopComponent',
  template: '<div>
    <section>
      <button ref="button" @click="handleButtonClick" class="btn btn-primary btn-lg">Large button</button>
    </section>
    <button @click="handleStartClick()" class="btn btn-success">Start Default</button>
    <button @click="handleStartClick(TransitionId.LOOP_1)" class="btn btn-success">Start variant 1</button>
    <button @click="handleStartClick(TransitionId.LOOP_2)" class="btn btn-success">Start variant 2</button<
    <button @click="handleStopClick" class="btn btn-warning">Stop</button<
  </div>',
  mounted() {
    this.TransitionId = TransitionId;
    this.transitionController = new LoopComponentTransitionController(this, {
      name: 'LoopComponent',
      debug: true,
      useTweenMax: false,
    });
    this.transitionController.startLoopingAnimation();
  },
  methods: {
    handleButtonClick() {
      alert('Nothing here!');
    },
    handleStartClick(loop) {
      this.transitionController.startLoopingAnimation(loop);
    },
    handleStopClick() {
      this.transitionController.stopLoopingAnimation();
    },
  },
};