Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to include GreenSock plugin for ScrollMagic in a ReactJS environment?

How do I include the Greensock plugin for ScrollMagic in my ReactJS project?

The authors of ScrollMagic made plugins to incorporate libraries like GreenSock and Velocity. These work great when you simply include them in your head of your html doc like so

<script type="text/javascript" src="js/lib/greensock/TweenMax.min.js"></script>
<script type="text/javascript" src="scrollmagic/uncompressed/ScrollMagic.js"></script>
<script type="text/javascript" src="scrollmagic/uncompressed/plugins/animation.gsap.js"></script>

But when you're in ReactJS, you don't import javascript resources like this. You actually have to import them through processes like the npm command, then declare them in your react project like

import ScrollMagic from "scrollmagic"

Although I was able to import ScrollMagic into react files and start using scrollmagic, I haven't been able to import the greensock plugins. There's no documentation on how to do this. I tried to hack things apart by taking segments of code in animation.gsap.js and pasting it into the node_modules/scrollmagic/scrollmagic.js file (which isn't a good idea to be editing these files), but it either breaks the webpack compiler or it breaks my project code.

How do I use the greensock plugin for scrollmagic in a react environment?

like image 576
John Avatar asked Dec 19 '22 02:12

John


2 Answers

I was able to build a wrapper. At a high level, what I did was study the code of plugins/animation.gsap.js, pulled out those Scene extended properties that I needed, changed the name space, and have it augment the behaviour of ScrollMagic in a separate react class prior to exporting it.

Specifically, what I did was create a new file called ./ScrollMagic.js and pasted the following contents:

import ScrollMagic from 'scrollmagic';
import {TweenLite as Tween,TimelineMax as Timeline} from 'gsap';

ScrollMagic.Scene.addOption("tweenChanges", // name
  false, // default

  function (val) { // validation callback
    return !!val;
});

ScrollMagic.Scene.extend(function () {
    var Scene = this,
      _tween;

    var log = function () {
      if (Scene._log) { // not available, when main source minified
        Array.prototype.splice.call(arguments, 1, 0, "(animation.gsap)", "->");
        Scene._log.apply(this, arguments);
      }
    };

    // set listeners
    Scene.on("progress.plugin_gsap", function () {
      updateTweenProgress();
    });
    Scene.on("destroy.plugin_gsap", function (e) {
      Scene.removeTween(e.reset);
    });

    /**
     * Update the tween progress to current position.
     * @private
     */
    var updateTweenProgress = function () {
      if (_tween) {
        var
        progress = Scene.progress(),
          state = Scene.state();
        if (_tween.repeat && _tween.repeat() === -1) {
          // infinite loop, so not in relation to progress
          if (state === 'DURING' && _tween.paused()) {
            _tween.play();
          } else if (state !== 'DURING' && !_tween.paused()) {
            _tween.pause();
          }
        } else if (progress != _tween.progress()) { // do we even need to update the progress?
          // no infinite loop - so should we just play or go to a specific point in time?
          if (Scene.duration() === 0) {
            // play the animation
            if (progress > 0) { // play from 0 to 1
              _tween.play();
            } else { // play from 1 to 0
              _tween.reverse();
            }
          } else {
            // go to a specific point in time
            if (Scene.tweenChanges() && _tween.tweenTo) {
              // go smooth
              _tween.tweenTo(progress * _tween.duration());
            } else {
              // just hard set it
              _tween.progress(progress).pause();
            }
          }
        }
      }
    };

    /**
     * Add a tween to the scene.  
     * If you want to add multiple tweens, add them into a GSAP Timeline object and supply it instead (see example below).  
     * 
     * If the scene has a duration, the tween's duration will be projected to the scroll distance of the scene, meaning its progress will be synced to scrollbar movement.  
     * For a scene with a duration of `0`, the tween will be triggered when scrolling forward past the scene's trigger position and reversed, when scrolling back.  
     * To gain better understanding, check out the [Simple Tweening example](../examples/basic/simple_tweening.html).
     *
     * Instead of supplying a tween this method can also be used as a shorthand for `TweenMax.to()` (see example below).
     * @memberof! animation.GSAP#
     *
     * @example
     * // add a single tween directly
     * scene.setTween(TweenMax.to("obj"), 1, {x: 100});
     *
     * // add a single tween via variable
     * var tween = TweenMax.to("obj"), 1, {x: 100};
     * scene.setTween(tween);
     *
     * // add multiple tweens, wrapped in a timeline.
     * var timeline = new TimelineMax();
     * var tween1 = TweenMax.from("obj1", 1, {x: 100});
     * var tween2 = TweenMax.to("obj2", 1, {y: 100});
     * timeline
     *    .add(tween1)
     *    .add(tween2);
     * scene.addTween(timeline);
     *
     * // short hand to add a TweenMax.to() tween
     * scene.setTween("obj3", 0.5, {y: 100});
     *
     * // short hand to add a TweenMax.to() tween for 1 second
     * // this is useful, when the scene has a duration and the tween duration isn't important anyway
     * scene.setTween("obj3", {y: 100});
     *
     * @param {(object|string)} TweenObject - A TweenMax, TweenLite, TimelineMax or TimelineLite object that should be animated in the scene. Can also be a Dom Element or Selector, when using direct tween definition (see examples).
     * @param {(number|object)} duration - A duration for the tween, or tween parameters. If an object containing parameters are supplied, a default duration of 1 will be used.
     * @param {object} params - The parameters for the tween
     * @returns {Scene} Parent object for chaining.
     */
    Scene.setTween = function (TweenObject, duration, params) {
      var newTween;
      if (arguments.length > 1) {
        if (arguments.length < 3) {
          params = duration;
          duration = 1;
        }
        TweenObject = Tween.to(TweenObject, duration, params);
      }
      try {
        // wrap Tween into a Timeline Object if available to include delay and repeats in the duration and standardize methods.
        if (Timeline) {
          newTween = new Timeline({
            smoothChildTiming: true
          }).add(TweenObject);
        } else {
          newTween = TweenObject;
        }
        newTween.pause();
      } catch (e) {
        log(1, "ERROR calling method 'setTween()': Supplied argument is not a valid TweenObject");
        return Scene;
      }
      if (_tween) { // kill old tween?
        Scene.removeTween();
      }
      _tween = newTween;

      // some properties need to be transferred it to the wrapper, otherwise they would get lost.
      if (TweenObject.repeat && TweenObject.repeat() === -1) { // TweenMax or TimelineMax Object?
        _tween.repeat(-1);
        _tween.yoyo(TweenObject.yoyo());
      }
      // Some tween validations and debugging helpers
      if (Scene.tweenChanges() && !_tween.tweenTo) {
        log(2, "WARNING: tweenChanges will only work if the TimelineMax object is available for ScrollMagic.");
      }

      // check if there are position tweens defined for the trigger and warn about it :)
      if (_tween && Scene.controller() && Scene.triggerElement() && Scene.loglevel() >= 2) { // controller is needed to know scroll direction.
        var
        triggerTweens = Tween.getTweensOf(Scene.triggerElement()),
          vertical = Scene.controller().info("vertical");
        triggerTweens.forEach(function (value, index) {
          var
          tweenvars = value.vars.css || value.vars,
            condition = vertical ? (tweenvars.top !== undefined || tweenvars.bottom !== undefined) : (tweenvars.left !== undefined || tweenvars.right !== undefined);
          if (condition) {
            log(2, "WARNING: Tweening the position of the trigger element affects the scene timing and should be avoided!");
            return false;
          }
        });
      }

      // warn about tween overwrites, when an element is tweened multiple times
      if (parseFloat(TweenLite.version) >= 1.14) { // onOverwrite only present since GSAP v1.14.0
        var
        list = _tween.getChildren ? _tween.getChildren(true, true, false) : [_tween],
          // get all nested tween objects
          newCallback = function () {
            log(2, "WARNING: tween was overwritten by another. To learn how to avoid this issue see here: https://github.com/janpaepke/ScrollMagic/wiki/WARNING:-tween-was-overwritten-by-another");
          };
        for (var i = 0, thisTween, oldCallback; i < list.length; i++) { /*jshint loopfunc: true */
          thisTween = list[i];
          if (oldCallback !== newCallback) { // if tweens is added more than once
            oldCallback = thisTween.vars.onOverwrite;
            thisTween.vars.onOverwrite = function () {
              if (oldCallback) {
                oldCallback.apply(this, arguments);
              }
              newCallback.apply(this, arguments);
            };
          }
        }
      }
      log(3, "added tween");

      updateTweenProgress();
      return Scene;
    };

    /**
     * Remove the tween from the scene.  
     * This will terminate the control of the Scene over the tween.
     *
     * Using the reset option you can decide if the tween should remain in the current state or be rewound to set the target elements back to the state they were in before the tween was added to the scene.
     * @memberof! animation.GSAP#
     *
     * @example
     * // remove the tween from the scene without resetting it
     * scene.removeTween();
     *
     * // remove the tween from the scene and reset it to initial position
     * scene.removeTween(true);
     *
     * @param {boolean} [reset=false] - If `true` the tween will be reset to its initial values.
     * @returns {Scene} Parent object for chaining.
     */
    Scene.removeTween = function (reset) {
      if (_tween) {
        if (reset) {
          _tween.progress(0).pause();
        }
        _tween.kill();
        _tween = undefined;
        log(3, "removed tween (reset: " + (reset ? "true" : "false") + ")");
      }
      return Scene;
    };

  });

export default ScrollMagic; 

You will notice that this looks almost exactly like plugins/animation.gsap.js, with the exceptions being:

  • I added my own import lines at the top, so make sure you have npm installed scrollmagic and gsap ahead of time
  • I export the ScrollMagic object towards the end
  • plugins/animation.gsap.js had some other code that turn the plugin into a factory which I excluded because it doesn't apply here

Now I can use .setTween() in my react project. Example usage:

import React, {Component} from "react";
import ScrollMagic from "./ScrollMagic"; // my own wrapper for scrollmagic that includes greensock

export default class Home extends Component {

    componentDidMount()
    {
        var controller = new ScrollMagic.Controller();
        var item = "#whateverstuffselector";
        var scene = new ScrollMagic.Scene({triggerElement:item})
                .setTween(item, 0.5, {backgroundColor: "red", scale: 3})
                .addTo(controller);

    }

    render()
    {
        return (<div id="whateverstuffselector">stuff</div>);
    }
}
like image 101
John Avatar answered Dec 31 '22 02:12

John


After escalating this issue for 1 month I guess I found great solution. So this issue shows that in React environment we can not get animation.gsap file. This fix does not require any webpack changes except animation.gsap file itself.

  1. Find these files in "node_module" directory tree (may have different location on you PC) and import it in this way to your working JS file (App.js for example). import "../../node_modules/scrollmagic/scrollmagic/uncompressed/plugins/animation.gsap"; import "../../node_modules/scrollmagic/scrollmagic/uncompressed/plugins/debug.addIndicators";

  2. Go to animation.gsap and add these two lines of code at the beginning of file. import { TimelineMax, TweenMax, TweenLite} from "gsap/all"; import ScrollMagic from "scrollmagic";

  3. Go to debug.addIndicators and add this line of code at the beginning of file (in case if you need indicator debugger, but I strongly suggest not to skip this step). import ScrollMagic from "scrollmagic";
  4. In animation.gsap find first function and delete all "root" variables and change them to variables that I provided below. ( you should find 8 of them).

Before:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['ScrollMagic', 'TweenMax', 'TimelineMax'], factory);
    } else if (typeof exports === 'object') {
        // CommonJS
        // Loads whole gsap package onto global scope.
        require('gsap');
        factory(require('scrollmagic'), TweenMax, TimelineMax);
    } else {
        // Browser globals
        factory(root.ScrollMagic || (root.jQuery && root.jQuery.ScrollMagic), root.TweenMax || root.TweenLite, root.TimelineMax || root.TimelineLite);
    }
}

After:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['ScrollMagic', 'TweenMax', 'TimelineMax'], factory);
    } else if (typeof exports === 'object') {
        // CommonJS
        // Loads whole gsap package onto global scope.
        require('gsap');
        factory(require('scrollmagic'), TweenMax, TimelineMax);
    } else {
        // Browser globals
        factory(ScrollMagic || (jQuery && jQuery.ScrollMagic), TweenMax || TweenLite, TimelineMax || TimelineLite);
    }
}
  1. In debug.addIndicators also delete all "root" variables ( you should find 4 of them ) and change them to variables that I provided below.

Before:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['ScrollMagic'], factory);
    } else if (typeof exports === 'object') {
            // CommonJS
            factory(require('scrollmagic'));
    } else {
            // no browser global export needed, just execute
        factory(root.ScrollMagic || (root.jQuery && root.jQuery.ScrollMagic));
    }
}

After:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['ScrollMagic'], factory);
    } else if (typeof exports === 'object') {
        // CommonJS
        factory(require('scrollmagic'));
    } else {
        // no browser global export needed, just execute
        factory(ScrollMagic || (jQuery && jQuery.ScrollMagic));
    }
}

I hope this solution will work for you. In any case you can reach me for help.

like image 30
ilkinnamazov Avatar answered Dec 31 '22 02:12

ilkinnamazov