Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
anthonygreco

Photo Cube Animation

Recommended Posts

Hi all,

 

I started working on an idea and want to stop before I go further and ask a few questions and get some criticism on best practices. I'll preface with saying that I'm only concerned with modern browsers. 

 

First, GSAP performance. Is there a better approach I could take to accomplish the same thing and would perform better?

 

Second, React with GSAP. This should probably be a separate question... I've been building react apps via `create-react-app` for a while and in the past, I had some issues using GSAP in React. Mostly, with using plugins that `require TweenLite`, requiring me to eject the `create-react-app` and customize the webpack config to resolve the alias. (As an aside, I now get around ejecting for simple things like this by using react-app-rewired).

 

There are some edge case issues in particular I'm trying to solve. When you move the mouse quickly from left to right, sometime the cube will spin too much. I've played around with some boolean checks to see if I'm overlapping tweens or something but nothing seems to help. I suspect it's based on the way I'm "snapping" the cube's most forward face to the center when the mouse moves back to the center.

 

EDIT 1... is it possible that this is related to React state? I wonder if react-gsap-enhancer would help.

 

EDIT 2... looks like the codepen may even have other issues that aren't present in my local setup. If you move the mouse too far past the cube it stops, which should only occur when the mouse is over the cube. And it's more difficult to see the real issue I'm trying to solve in the codepen.

See the Pen OgBOWX by anthonygreco (@anthonygreco) on CodePen

  • Like 1

Share this post


Link to post
Share on other sites

Sweet demo Anthony!!! always good to see you coming back with awesome stuff!!

 

For performance I would try to avoid using the elastic easing on all the thumbnails when clicking on the expanded image, I would go with a regular easeOut function, perhaps Power2 and use the elastic on the expanded being scaled down.

 

For using react create app, I can't help you. I've baked my own start packages for working with react, angular, vue, etc. so I never use those tools. Also I've never been a big fan of this bundling madness that has taken over front end development, so even today sometimes I keep GSAP outside the bundled files. This is particularly useful when you end up with a bundled file over 1MB because you can create a pre-loader with GSAP. I'm sure Blake can give you some pointers in this regard.

 

I see you're using timescale to speed up/down the animation using the pointer position. But also I saw this:

TweenMax.to(this.timeline, currentFace.progressRemaining / 2, {
  time: this.timeline.getLabelTime(currentFace.id.toString()),
  ease: 'Back.easeOut',
});

Why don't you create one TweenMax instance to rotate to each side, just like the one you already have, using relative values and a repeat:-1 with linearEaseNone in order to create an endless loop, or you can use the modifiers plugin as well, and based on the pointer position update the timescale between 0 and 1 like that you avoid code like the one above and keep it more simple. You can even put those GSAP instances in your component's state or even a redux store and just reference them.

 

Finally I don't know how the GSAP instances could be messing with the App's state. As a rule of thumb, I try to avoid GSAP updating any property of the app's state that could mean a re-rendering that ultimately will affect the instance. If the GSAP instance will update something in the state, is vital that nothing else does because GSAP won't know about that and will keep going with the stored values, unless you reset and create the instance again. For example if a Tween is changing the X position of an object and is not entirely necessary to store that value in the state then don't, in order to avoid something that could mess that up. In your sample I can't see if the GSAP instance is updating anything from the state, so I'd need for you to tell me that. This is very simple examples of some things I made with react some time ago:

See the Pen JKvraO by rhernando (@rhernando) on CodePen

In this basically GSAP just animates the DOM element and doesn't even know about the app's state.

 

See the Pen vXzmLw by rhernando (@rhernando) on CodePen

 

This is using a delayed call and the Tween updates a specific part of the state that nothing else can update. Finally with the button you can change another state property.

 

Finally this is using the modifiers plugin:

See the Pen RVLBGJ by rhernando (@rhernando) on CodePen

Hope that this helps.

 

Best,

Rodrigo.

  • Like 5

Share this post


Link to post
Share on other sites
2 hours ago, Rodrigo said:

For using react create app, I can't help you. I've baked my own start packages for working with react, angular, vue, etc. so I never use those tools. Also I've never been a big fan of this bundling madness that has taken over front end development, so even today sometimes I keep GSAP outside the bundled files. This is particularly useful when you end up with a bundled file over 1MB because you can create a pre-loader with GSAP. I'm sure Blake can give you some pointers in this regard.

 

Hehe. It really is amazing how webpack can turn 100 lines of code into 1000. I think it's better to use CDNs for libraries. That will allow caching of the libraries (for a year), and you can always fallback to a hosted version on your server if the CDN is down. Bundling libraries gives you no caching outside of your bundle, and can drastically increase the size. Some libraries like Pixi and Three are huge, weighing in at 450kb minified. They don't need anymore weight added to them. I like jsdelivr as it uses one http call and can load from other sources like npm and github.

 

But I know this isn't always possible. Some GSAP plugins aren't on CDNs, and I know the create-react-app won't compile if you try to use GSAP directly as a global. That said, I don't think you will have any problems including GSAP in a bundle and importing the plugins. There should be no need to eject the app. Changes have been to the file structure, and seems to have eliminated the need to create aliases. 

 

About your animation... I'm not sure I understand the expected behavior. It just seems to rotate however and whenever it wants. Is it supposed to follow the mouse?

  • Like 3

Share this post


Link to post
Share on other sites

Great feedback guys! I'll play around with some of the suggestions @Rodrigo made, though I'm not sure about changing:

 

TweenMax.to(this.timeline, currentFace.progressRemaining / 2, {
  time: this.timeline.getLabelTime(currentFace.id.toString()),
  ease: 'Back.easeOut',
});

 

This tween should only run when the cursor is over the cube. It's to "snap" the cube back into position so it's facing the front. So it just animates the timeline from whatever current position it's currently at to the nearest label.

 

@Rodrigo, I'm pretty sure I'm not using any state values as tween values, and instead only using state to actually track the state of the animation. So mostly just booleans and static values that help let the animation know how it should perform. (For example, I track the current active face by setting it in the onUpdate method of the individual tweens -- if the progress is under 50% I know its the current face; if it's over 50%, then the next face is the most visible.) I may integrate react-gsap-enhancer and see if it helps, but still not sure it's needed.

 

I've used delayedCall and modifiers before, but not entirely sure how they'd fit into solving my problems. Either way, I'll consider the thoughts as I'm working toward a refactor.

 

@OSUblake, agreed that CDN is the best way to go. While this animation isn't using any club plugins currently, eventually this will use things like split text and possibly even draw/morph svg. Which is why I need rewire to set TweenLite as an alias, otherwise I get:

Module not found: Can't resolve 'TweenLite' in '/path/to/my/project/src/libs/gsap-club/plugins'

 

So with rewire I can add the following and all is well. 

config.resolve.alias.TweenLite = 'gsap';


With one caveat. I will still get caught by the create react app linter on no-undef for any plugin due to the use of define.

./src/libs/gsap-club/utils/SplitText.js
  Line 551:  'define' is not defined  no-undef
  Line 552:  'define' is not defined  no-undef

I've tried adding define to my list of globals in my .eslintrc file but it doesn't seem that create react app honors this so for the moment I've just placed:

/* global define */

at the top of all of my plugins.

 

As for the expected behavior of how the animation should work, you're correct the cube faces should "follow" the mouse. So when you have the mouse to the right of the cube the cube should be spinning from left to right and vice versa; when over the cube it should snap into position so you can click the photos. The codepen works a little better in full screen mode, but it still has the issues I listed in my edit of the first message. Still a lot to do in terms of direction of the animation/interaction itself; more prototype than anything.

  • Like 1

Share this post


Link to post
Share on other sites

Hey Anthony, the delayed call was just a sample of using GSAP and modifying other state properties while the animation (delayed call) is working, nothing more. I agree with you that it has no use in your App :mrgreen:.

 

I also agree that using the react gsap enhancer shouldn't offer any upside in this particular scenario.

 

As for configuring eslint globals, why don't you declare them in the  .eslintrc.json file?

 

{
  "env": {},
  "parserOptions": {},
  "globals": {
    "PIXI": true,
    "TweenLite": true,
    "TweenMax": true,
    "TimelineLite": true,
    "TimelineMax": true,
    "Elastic": true,
    "stripes": true,
    "axios":true
  },
  "rules": {}
}

 

I always use that when working with eslint.

 

Happy Tweening!!

  • Like 1

Share this post


Link to post
Share on other sites

@Rodrigo, yeah I'm pretty sure I've played around with every possible way to configure eslint; using package.json, different file names for .eslintrc*, etc.. (http://eslint.org/docs/user-guide/configuring) but nothing I do seems to be honored by whatever create react app is set up for. (I need to eject again and dig into what's really going on) Or maybe it has to do with the fact my config is extending from react-app and airbnb. ¯\_(ツ)_/¯ If/when I figure that out I'll report back but I'm more focused on the animations for now. :)

 

I'm working on more simplified version of the cube now. I'll post an update when it's easier to reason about.

  • Like 1

Share this post


Link to post
Share on other sites

Simplified updated version focused only on the spinning of the cube. All eases are currently Linear.easeNone. Also added some overlays to display when the cube should be spinning in what direction.

 

I also noticed that the previous version had a scope issue. The restartForward and restartReverse methods didn't know about `this`, as I missed binding them when I converted my local work to codepen. (Can't seem to get babel on codepen to let me do  things like my local babel config, requiring me to add a whole lot of this.methodName.bind(this) madness in codepen, but you know ¯\_(ツ)_/¯) Either way, this simpler version should be easier to follow now.

 

See the Pen qjJQNL?editors=0010 by anthonygreco (@anthonygreco) on CodePen

 

  • Like 1

Share this post


Link to post
Share on other sites

Hi Anthony,

 

Looks nice!!!

 

Perhaps this could be useful to tween the timescale back to 0 when hovering over the cube and using an onComplete callback to snap the cube to the current face:

 

See the Pen YwOWmQ by GreenSock (@GreenSock) on CodePen

 

  • Like 2

Share this post


Link to post
Share on other sites

The best way to manage and install GSAP through npm is to maintain your own repo/package. 

But don't do that yet. The files that you download from your GSAP account don't use a flat structure, which is what we want. I need to see if @GreenSock can make a new folder so that something like this will install everything.

 

{
  "name": "gsap-react",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "gsap": "file:///Users/blake/gsap",
    "react": "^15.6.1",
    "react-dom": "^15.6.1",
    "react-scripts": "1.0.10"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}

 

Then you can import just like this...

import { TweenMax } from "gsap";
import MorphSVGPlugin from "gsap/MorphSVGPlugin";
import SplitText from "gsap/SplitText";
import Draggable from "gsap/Draggable";
import ThrowPropsPlugin from "gsap/ThrowPropsPlugin";

 

  • Like 2

Share this post


Link to post
Share on other sites

Thanks for that link @OSUblake. Good info... agreed, I'll hold off on my own repo. My current solution is patchwork, but manageable.

 

The previous simpler demo seems to be a bit better than the previous, but the edge cases still exist, they've just been harder for me to reproduce and can't seem to determine what is really going on. I've also tried another simpler approach (without speed) where I trigger the animations play(), pause() and reverse() method based on hover events:

 

See the Pen MoPMbO by anthonygreco (@anthonygreco) on CodePen

 

but if you move the mouse fast enough you can still run into edge cases where the cube is spinning in the wrong direction as well as the cube spinning erratically when it's stopping.

Share this post


Link to post
Share on other sites

The problem is that the timeline is being restarted when it lands on the front face.

this.timeline = new TimelineMax({
  paused: true,
  onComplete: this.loopForward.bind(this),
  onReverseComplete: this.loopReverse.bind(this),
});

// These should not get called in the stop phase
loopForward() {
  this.timeline.restart();
}

loopReverse() {
  this.timeline.reverse(0);
}

 

A simple flag should do the trick...

See the Pen xryvjZ?editors=0010 by osublake (@osublake) on CodePen

 

  • Like 2

Share this post


Link to post
Share on other sites

Ahhh, thanks @OSUblake, that makes sense now! I also like the way you're determining the timeline position without having to use labels and tracking the faces. :)

  • Like 1

Share this post


Link to post
Share on other sites

About GSAP with React. I don't use create-react-app, I have built my own seed that I use and customize with my needs based on the project, it's setup with gulp and I just use webpack to handle the js build process. But when I need gsap it's about as easy as npm install --save gsap and import {TweenLite} from 'gsap'. For compiling size, there are a few useful options and I usually build a vendor chunk which isn't rebuilt incrementally, so I can have a very small app package without the dependencies, and the dependencies in another one, which usually more or less equals the original package size with uglify and other optimisations ( which WP2 does pretty much out of the box by just specifying production build with webpack -p ).

 

About animation, I usually use the lifecycle methods to check if the previous and next props should trigger an animation. If yes, then I trigger it, you can do the same to cancel it. If a part of the animation should change the state, say on complete you want to tell your app state that your view is visible, then I dispatch an action ( redux, so precisely I would have a property call from my dumb view to its container which is bound to the dispatcher and takes care of that, the view knows nothing about the app state ). So the view gets props, knows it should transition from one to the other, say from visible false to true, and emits the change when finished. So my ui state is more or less the keyframes if you want. 

 

Quote

(For example, I track the current active face by setting it in the onUpdate method of the individual tweens -- if the progress is under 50% I know its the current face; if it's over 50%, then the next face is the most visible.)

 

So in that case, your view could just declare a property onFaceChange: PropTypes.func, and when you're passed 50% you call this.onFaceChange(faceNumber). Your container on top gets that value and takes care of emitting the action to the store. Whatever state solution you use, it's always good to decouple the state from the ui components, but you must know about all this. In your case though, I believe you don't want to emit that change on every update past 50%, so you should have an internal state that somehow tracks if the change has been emitted already. Say at 51% it should emit it, at 52% it shouldn't, if it goes back to 49% it should emit it again.

  • Like 3

Share this post


Link to post
Share on other sites
23 hours ago, OSUblake said:

The files that you download from your GSAP account don't use a flat structure, which is what we want. I need to see if @GreenSock can make a new folder so that something like this will install everything.

 

@OSUblake The NPM files are indeed flat. We used to have a flat structure included in the zip download, but we got several complaints about that because people would often import the bonus plugins from there, but the rest of the files from the NPM package, resulting in some duplicate code in their bundle since the bonus files were pulling dependencies from that "all-in-one" zip folder. In other words, TweenLite would get bundled from both the NPM package (in node_modules) as well as from the folder they got from the bonus zip. See what I mean? 

 

We were asked to move to only having the bonus files in a folder for NPM users, and those pulled in dependencies from the node_modules. That's what's in the latest zip. Are you suggesting we change that and go back to including all the files flat in that directory? 

Share this post


Link to post
Share on other sites
On 7/10/2017 at 1:01 PM, GreenSock said:

We were asked to move to only having the bonus files in a folder for NPM users, and those pulled in dependencies from the node_modules. That's what's in the latest zip. Are you suggesting we change that and go back to including all the files flat in that directory? 

 

@GreenSock

 

Maybe. Or put the flat directory files inside the plugin folder. The problem seems to be that people install the base files from npm, and then put the plugin files in some arbitrary folder. If people install everything from a single source like I show above, that might eliminate a lot of problems. 

  • Like 1

Share this post


Link to post
Share on other sites
On 7/17/2017 at 2:20 AM, OSUblake said:

Or put the flat directory files inside the plugin folder. The problem seems to be that people install the base files from npm, and then put the plugin files in some arbitrary folder

 

I thought that's exactly what the current solution addresses. Folks can install all the core files via NPM, and then we provide that directory with just the non-NPM stuff on its own (which has its dependencies pointed at the NPM resources). Previously, we provided ALL the resources in one flat directory and that actually CAUSED problems because it was so common for people to install the core stuff via NPM, but when they imported one of the non-NPM files, it'd suck in the local files in that folder as dependencies, thus they'd end up (in some cases) with BOTH the NPM core files AND the local ones (duplicate/redundant data). See what I mean?  I got several requests to move away from that in favor of the current solution because it seemed cleaner and avoided those problems. Maybe I'm misunderstanding what you're suggesting? 

 

 

Share this post


Link to post
Share on other sites
On 7/21/2017 at 0:33 AM, GreenSock said:

Previously, we provided ALL the resources in one flat directory and that actually CAUSED problems because it was so common for people to install the core stuff via NPM, but when they imported one of the non-NPM files, it'd suck in the local files in that folder as dependencies, thus they'd end up (in some cases) with BOTH the NPM core files AND the local ones (duplicate/redundant data). 

 

By describing the problem to you, they also revealed the solution. The npm files are redundant. 

 

You could install the folder with npm when you had all the files in the flat directory, but I doubt anybody knew that. All it needed was a package.json file, and it would install just like any other package. 

 

The only difference is that instead of doing this...

npm install --save gsap

 

You would do this...

npm install --save path-to-folder

 

And now everything is installed as a single package, eliminating the need to keep the plugins in a separate folder, or copy and paste any files into the modules folder. Getting people to stop putting the plugins in a separate folder is the only way to really make all these issues go away.

 

Otherwise, you're still going to have people raising issues caused by their build tools, like this one that came in a couple of hours ago.

 

  • Like 1

Share this post


Link to post
Share on other sites
On 7/23/2017 at 4:07 AM, OSUblake said:

You could install the folder with npm when you had all the files in the flat directory, but I doubt anybody knew that. All it needed was a package.json file, and it would install just like any other package.

 

Are you suggesting that the members-only zip file would have an extra directory with ALL of the files (flat) along with a package.json and then they run an npm install on that directory somehow (rather than pulling the files in from the remote NPMJS repo)? I didn't realize "NPM install" could pull from a local directory for installing - am I understanding you correctly?

 

The down side of that approach would also be that their project couldn't as easily pull in updates, right? With the "bonus-files-in-their-own-directory" approach, all the core stuff remains hooked up to the "normal" NPM system with updates, installs, etc. The *only* thing outside of that system would be the members-only plugins which seems appropriate. 

Share this post


Link to post
Share on other sites
On 7/24/2017 at 10:28 AM, GreenSock said:

Are you suggesting that the members-only zip file would have an extra directory with ALL of the files (flat) along with a package.json and then they run an npm install on that directory somehow (rather than pulling the files in from the remote NPMJS repo)? I didn't realize "NPM install" could pull from a local directory for installing - am I understanding you correctly?

 

That's correct. I don't think a lot of people know about that, but npm has always had the ability to install from practically any type of source. See here... 

https://docs.npmjs.com/cli/install

 

On 7/24/2017 at 10:28 AM, GreenSock said:

The down side of that approach would also be that their project couldn't as easily pull in updates, right? With the "bonus-files-in-their-own-directory" approach, all the core stuff remains hooked up to the "normal" NPM system with updates, installs, etc. The *only* thing outside of that system would be the members-only plugins which seems appropriate. 

 

For updating, yes, it might be easier for the core files, but how often does GSAP get an update? And isn't it better to update all of GSAP's source files together?

 

But the fact remains that users still have to get the bonus plugins into their project somehow. To me, installing the folder just seems easier and less error prone. And the folder doesn't have to be local. It could be on a server, or as it's own private repo with version control. This would allow other team members to install the source files without much hassle.

 

Maybe you could go back and ask some of those people what they think of this approach.

  • Like 1

Share this post


Link to post
Share on other sites

this was a lot to digest... what are the simple instructions for using customEase plugin in angular or other module base environments?

 

  1. made file called CustomEase.min.js from http://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/CustomEase.min.js?r=2
  2. saved it in the node_module/gsap (will change later)
  3. now when i import (angular TS file) i get "Module not found: Error: Can't resolve 'TweenLite'"

 

import CustomEase from "gsap/CustomEase.min";

 

results in 

 

Module not found: Error: Can't resolve 'TweenLite'

 

Ive seen many threads but I can only get it to work through <script> tags

Share this post


Link to post
Share on other sites

You definitely shouldn't be using that file you linked to - that's just for Codepen demos (it'll redirect if you try using it on a different domain). Use the CustomEase file that you get in the download from this web site - log into your account and go to the "Downloads" section of your account dashboard. That has CustomEase in there. You could drop it into your node_modules/gsap folder or just use it loose, as long as you've got GSAP installed via NPM it should work okay but I'm not familiar with every build tool out there so your mileage may vary. 

  • Like 2

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

  • Recently Browsing   0 members

    No registered users viewing this page.

×