Jump to content
Search Community

Feature Request: Wrap values (modulo)

OSUblake 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

I've been noticing lately that it would be very handy if there was a way to wrap values during a tween, just like the hours on a clock.

 

9 + 4 hours = 1.

 

CglbAmf.png

 

I made a demo that does this for positioning, but it's not very performant because it creates a lot of tweens on every update.

 

Creating something like that could be greatly simplified if there was a way for GSAP to do that internally. A modulo operator has been proposed for a future version of ECMAScript using this syntax.

-2 mod 12

So we could use the word "mod" to tell GSAP to run the value through a modulo function before applying the value. 

TweenLite.to(foo, 1, { x: "800mod600" });

So what's going to happen is that it will tween to 600, go back to 0, and tween to 200. This would make creating carousels/sliders a breeze.

var index = 0;

$("#prev").click(function() { move(-1); })
$("#next").click(function() { move(1); })

function move(steps) {
  index += steps;
  TweenLite.to(".slider", 1, { xPercent: index + "00mod500" });
}

Pretty simple! No cloning, calculating positions, or secondary updates required. And because it's position will automatically reset, you can click through the slides as fast you want.

 

The only problem I see with the mod is that it resets the value to 0. This is probably not something you want to happen for something like scaling, so there should be a way to define a base value. Maybe something like this.

TweenLite.to(foo, 1, { scale: "2mod0.5+=1" });

Thoughts?

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

  • Like 6
Link to comment
Share on other sites

Hello OSUblake  :)

 

Welcome to the GreenSock forums. Could you please make a reduced demo for us.... hey wait a minute. Sorry, I got confused as you so rarely start a topic.  :lol:

 

I think that's a pretty cool idea and could come in quite handy. We certainly see a lot of questions about sliders around here and this could make those quite a bit easier. I'll give a +1 to this idea. I'm anxious to hear what Sir Jack of the House Doyle has to say.

  • Like 3
Link to comment
Share on other sites

Thanks for chiming in with an idea/suggestion, Blake. You carry a lot of sway around here :) 

 

Unfortunately, this is a rather costly thing to wire into the core. Not only would it require extra logic to run on every...single...property on every...single...tick, but it'd have to get hard-coded into almost every plugin too. It affects all the parsing logic too. I'm very concerned about the cost:benefit ratio. Don't get me wrong - I think it's a cool idea. It's just tough to swallow the heavy cost it'd impose from a performance standpoint across the board. Every single tween would pay the price for a feature that I'd guess a tiny fraction of the user base would tap into. I'm sure animation rock stars like you would use it a fair amount, but mere mortals probably would never touch it, yet their tweens would pay the performance price. You know how much of a performance nut I am :)

 

I'll keep chewing on it and maybe I'll get struck with a clever way to implement it cheaply. Thanks again for sharing the idea.  

  • Like 2
Link to comment
Share on other sites

Well, if not the core, what about plugin? Maybe something like how the PhysicsPropsPlugin can work on any property. 

TweenLite.to(foo, 1, { mod: { x: "800mod600" }});

Something that would be even better would be able to use an optional function to transform values. Kind of like how you can use a function for cycle in staggered tweens.

TweenLite.to(foo, 1, { mod: { x: { val: 800, transform: myFunction }}}); 

Now that would give the user a lot more control over the value, although transform is probably not a good name for it.

  • Like 2
Link to comment
Share on other sites

  • 2 weeks later...

I played around with this idea a bit, Blake, and came up with this: 

https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/ModPropsPlugin.js

 

It required some minor updates to CSSPlugin too, so this would only work with GSAP 1.18.6 and later. You can load that TweenMax (uncompressed) here: 

https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/TweenMax-latest-beta.js

 

As for the syntax, I figured it might make the most sense to just have the modProps store the modifier functions, and leave the actual properties in their "normal" spot, like:

TweenLite.to(foo, 1, {
    x:800, 
    modProps: {
        x:function(value) {
            return value % 500;
        }
    }
});

Basing it on functions allows a lot of flexibility, so you could do modulus or you could do more complex rounding/snapping like: 

function(value) {
    return Math.round(value / 50) * 50; //snap to the closest 50 pixels
}

Oh, and for convenience, I do pass the target element as the 2nd parameter in case you need that :)

 

Give it a shot and let me know what you think. This plugin approach allows us to avoid impacting performance across the whole engine (good). Keep in mind that for CSS properties, most of them are gonna be strings, like "20px" or complex stuff like "10px 20px 5px rgb(0,0,0)". Transform-related stuff would be numbers.

 

Curious to hear from everyone if they think this is worthy of dropping into the main package. I'm also open to syntax suggestions. 

  • Like 2
Link to comment
Share on other sites

Oh, wow! This opens up a whole new range of possibilities!

 

First off, I love the syntax and the name. Mod looks like the word modify, which is exactly what this plugin does.

 

For anybody reading this who doesn't understand what I asked for, and Jack totally delivered on, it's basically GodMode for a tween. The plugin gives you control over the final value, which means you can do whatever you want with it.

 

Should be interesting to see what people use it for. The first idea I came up with up is to ping-pong the values. What's that going to do? Exactly what it sounds like.

 

Look what you can do with 2 large numbers and 1 tween. I'm not creating any additional tweens, restarting the tween, or invalidating the tween in any way. Straight up linear tween between two numbers. I've created an animation like that before using the ThrowPropsPlugin, and it was very involved.

 

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

  • Like 3
Link to comment
Share on other sites

Nice work, guys. Yeah, I can certainly see some possibilities with this especially for advanced guys like you two. I'm just curious about how useful it'll be to the majority of the user base out there (if it's worth including/supporting/documenting in the public package). Sometimes I worry that adding things like this might just confuse most of the people out there, and lead them to conclude that GSAP is just for super-advanced animation nerds. Then again, I love making GSAP as insanely flexible as possible so that the possibilities are endless. 

 

Aside from modulus and custom snapping logic, can you think of any other real-world uses for this? And do you feel strongly one way or the other about including it in the public package (thus documenting and supporting it)? 

Link to comment
Share on other sites

Thanks for the heads up on this Carl - it looks really interesting and with some more thought I imagine there all sorts of possibilities.

 

Out of curiosity how would this example work if you wanted the boxes to move left instead of right? I tried a few things but nothing worked.

 

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

 

I also tried it with SVG attributes using the cycle syntax which didn't work.

Link to comment
Share on other sites

Hey Chris,

 

The modulus operator % in JavaScript does not do a true modulo operation. It's a remainder operation, so it doesn't work the same way for negative values. Here's a function you can use to get the correct behavior.

function mod(num, div) {
  return ((num % div) + div) % div;
}

Here's Carl's example running backwards.

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

 

I was just thinking that it might be nice to have some helper functions like that on the plugin itself. 

// Alias so you don't have to type the full signature
var mod = ModPropsPlugin.mod;

TweenLite.to(foo, 1, {
  x:800, 
  modProps: {
    x: function(x) {
      return mod(x, 500);
    }
});

The different types of things you can do with this plugin may not seem obvious at first, so I'm going to make a couple of demos.

 

In the meantime, look at some of the behavior going on in my demo. In a normal animation you would have to create a new tween for every bounce. And notice how the easing remains constant through the entire animation. That means you can do something similar to the .updateTo() method, but with plugin values.

 

That method is what got me interested in using GSAP for a project, and I spent a lot of time trying to get it to work. Check out my first post on the GSAP forums. It only took 2 years, but I finally got something like that working.

http://greensock.com/forums/topic/10027-cant-get-updateto-to-work/?p=41042

  • Like 4
Link to comment
Share on other sites

Hey Blake,

 

On the contrary I think there are several obvious applications but I'm sure there are many more less obvious ones - the very fact that the ease on the tween continues at the correct rate is a huge feature. Not only will gamers benefit from this but animators too.

 

As always excellent work Blake - I'm going to take this for a proper spin this week - add it into some projects and see how I can apply it.

 

Btw should this work with plugins like DrawSVG?

  • Like 1
Link to comment
Share on other sites

A slightly simpler way to make the example go the opposite way is: 

//OLD: 
return x % 500;

//NEW:
return 500 - (x % 500);

You keep the x:"+=800"; no need to change it to -=

 

Regarding your question about attr and drawSVG values, no. ModPropsPlugin is just for regular values and ones that are handled by CSSPlugin. What's particularly tricky (and the reason why we don't really offer a TweenLite.get() method) is that many plugins have to juggle a bunch of values that are kinda merged into one. For example, drawSVG affects the stroke-dashArray and stroke-dashOffset. So it's not simple to just start translating that data back and forth every time the tween updates. It's entirely possible, but a bit expensive. 

 

This is also part of my hesitation for including it in the public package. I can rework all the plugins to accommodate this new functionality, but it's pretty far-reaching. Almost every plugin will have to get updated and rewired. 

  • Like 3
Link to comment
Share on other sites

If I can add my humble opinion as a Newb, while it will take me a bit of time to wrap my head around how to put this to work, adding this kind of granular and dynamic control over vars values inside a tween would be fantastic. Maybe many people might not take advantage of it, but for the ones who do I think it would add tremendous value to the GSAP package.

  • Like 2
Link to comment
Share on other sites

I thought the rainbow text demo I made in this thread was kind of interesting, so I came up with another version that uses this plugin. To change it up, I'm using sinebow values instead of HSL, which are calculated based on the x position of an element. I'm not modifying the returned value, so what I'm doing could technically be done be in the onUpdate callback, but that's not point. It's to show how you can use the plugin as a setter to dynamically change other values.

 

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

Link to comment
Share on other sites

And now for one of the coolest things you can do with the plugin... Algebraic expressions!!! I know that doesn't sound interesting, but it is. 

 

Cos

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

 

Sin

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

 

If it were possible to get the x and y value at the same, you could do rotations along the path!

  • Like 3
Link to comment
Share on other sites

Pretty cool stuff, Blake. I guess it could all be done with onUpdate, but there'd be more overhead due to the set() calls you'd be making each time (in other words, creating a new tween instance on every tick). Right? Or do you see something here that couldn't be done in an onUpdate? 

 

Thanks for creating the fun demos. Definitely helps show some of the things that can be done. 

Link to comment
Share on other sites

Everything I've shown could be done in an onUpdate, but when you have to use set() on every tick, it can get noticeably slower. And most developers do not know how to update or calculate transforms manually, so they kind of have to use set().

 

And now for another demo. This one is a little advanced, but really fun... a homing missile!

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

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