Jump to content
Search Community

Draggable & throwProps snap position from array - IE problem

PointC test
Moderator Tag

Go to solution Solved by Diaco,

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

Hey all :),

 

I was doing a little experiment with Draggable and throwProps so the user could throw the draggable to a position and get a new panel from an info slider and I see a strange quirk in Internet Explorer.

 

I'm using three snap positions [150,350,550] and then based on the index of the final snap position, I grab a new information panel to be shown, but IE sometimes ends on a number that is not in the snap array, but rather a lengthy decimal equivalent. I'll get 349.803101087 instead of 350. This results in my index query coming back as 0 so it flips back to panel 0. 

 

I see the problem and the fix is an easy Math.round(), but I found this odd and I'm curious if it was just Internet Explorer being its usual troublesome self or why exactly it won't consistently land on a whole number since it should be coming from the snap array.

 

This is only happening in Explorer (FF and Chrome are fine) and not on every throw. I've put a console.log() in the code to show the final position so please watch that as you throw the draggable around. It might take a few attempts to produce the odd behavior.

 

Again, easy fix - just more curious what causes it.

 

Thanks. 

See the Pen gPPOOw?editors=001 by PointC (@PointC) on CodePen

Link to comment
Share on other sites

  • Solution

Hi PointC  :)

 

works correctly , pls try like this : 

 

Draggable.create("#dragControl", {
  type:"x",
  throwProps:true,
  bounds: dragStop,
  snap:arr,
  onThrowComplete: function() {
    var final = arr.indexOf(this.x);
    console.log("Final position was: " + this.x +" and the final index was:" + final);
  }
});
  • Like 3
Link to comment
Share on other sites

Hey Diaco :)

 

I need to get my brain started today.

 

It makes much more sense to use this.x instead of position().left of the dragger and that definitely makes Internet Explorer behave. 

 

I'm still somewhat curious though -  does IE just not calculate and round the left position correctly for some reason? Is x just more accurate?

 

At any rate - thank you. You rock.

 

:)

  • Like 1
Link to comment
Share on other sites

hmm, yep it's kinda one of the ie issues , don't forget you're using draggable type:'x' and subpixels instead of pixels ,

 

if you want to get draggable target left prefect pixel position , pls use type:'left' and you can get position by this this.target.offsetLeft , or via jquery .

 

but after all that's better to use this.x

  • Like 2
Link to comment
Share on other sites

It might not be related to IE at all, but due to a rounding error with floating point numbers.

 

Check out this simple math problem. What's 0.1 + 0.2? It should be 0.3, but go test that out with JavaScript.

0.1 + 0.2
// → 0.30000000000000004

0.1 + 0.2 === 0.3 
// → false

I actually ran into the same issue earlier with this

See the Pen MKKJXL by osublake (@osublake) on CodePen

. I'm checking if the answer is correct based on the rotation value which is being snapped to, but I noticed that sometimes it can be a little off. So instead of being 111, it would show up as 110.9999999999999999. The only way I could figure out to get it to round properly was to do it like this. Math.round and bit shifting didn't do anything.

var rotation = Math.floor(this.rotation + 0.5);
  • Like 1
Link to comment
Share on other sites

It was only happening in Chrome for me, but it wasn't that big of shock because I knew what the problem was. I've come across this issue before, but never had a problem trying to round it. Weird! 

 

I'm sure the answer is somewhere here in this article, but that's just too much for me to read.

http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Link to comment
Share on other sites

blake , you are right about result of 0.1+0.2 , but if you try that for a thousand times , always you will get same result .

 

in the other hand we have that issue in browsers randomly ( with ie you will get that undesired result for more times)

 

it's not about one factor ( how browsers will deal with raf , subpixels , calculation speed , tweens ease type ...etc ) , 

be sure it's about browser perfomance :)

Link to comment
Share on other sites

I know what you're saying, but I don't think this has anything to do with performance. I saw PointC's problem in Edge, which right now is probably the fastest thanks to EdgeHTML and it's JavaScript engine, Chakra.

 

After looking at this again, the problem with PointC's demo is just due to how subpixels are being calculated in IE/Edge. Using the values the browser calculates can lead to problems for use cases like this because it's really not the source of truth. The transform values that GSAP stores would be the source of truth because that's the model you are using. That's why using this.x fixes the problem.

 

In my demo I'm using what GSAP reports as the source of truth (this.rotation), but rounding errors can still show up. I just checked, and it happens in all browsers, which means that it's just related to how floating point numbers work. 

 

One way to prevent errors like this is to scale your values using a power of 10...

// Scale this...
0.1 + 0.2

// To this
1.0 + 2.0

Here's a nice little function to help with that. Just pass in some value and the number of decimal places to round to. This does not return a string like using .toFixed or .toPrecision does, and it's much faster.

function roundTo(value, place) {
  var p = Math.pow(10, place);
  return Math.round(value * p) / p;
}

roundTo(Math.PI, 3);
// → 3.142

roundTo(Math.PI, 10);
// → 3.1415926536

roundTo(43213516.654321, -5);
// → 43200000
  • Like 2
Link to comment
Share on other sites

@Blake - nice function - I'm gonna use that. :)

 

I have another project coming up that will be the same idea, but a dial instead of a horizontal dragger. (spin the dial get another slide) Since you're seeing some rounding errors on this.rotation, would you recommend just being proactive and feeding the result into that function to be on the safe side?

Link to comment
Share on other sites

Being proactive is good. I would put in some type of check to prevent this sort of thing from happening. Rounding is just one way. You could also check against some tolerance to allow for a little wiggle room. So if you wanted to check if the rotation is at 45 and you have a tolerance of 0.5 degrees, you could write that like this.

var tolerance = 0.5;

var diff = Math.abs(this.rotation - 45);

if (diff < tolerance) {
  // Do something
}
  • Like 2
Link to comment
Share on other sites

Hey Blake, really cool other way checking against the tolerence, for that wiggle room :D

 

As far as rounding, you could also do it with a bitwise shift operator, converting a float / number into a integer.

// so this to round:
var rotation = Math.floor(this.rotation + 0.5); 

// could become this to round:
var rotation = this.rotation + 0.5 >> 0;

You can see here in this example using it to round the rotation value in the onUpdate:

 

See the Pen qbbGMJ by jonathan (@jonathan) on CodePen

 

I just like the bitwise operators from back in the Flash ActionScript days, since it allows me to write less code. But that is just the lazy bones in me :)

  • Like 2
Link to comment
Share on other sites

I use bitwise operators sometimes, but I didn't want to confuse anybody. I noticed that Jack uses them in GSAP, but he uses a single pipe to chop off the decimals.

var rotation = this.rotation + 0.5 | 0;

Using them can make your code look really cryptic, but you can do some really cool things with them, like using them as an enum to toggle flags. So by shifting bits on the number 13, I can tell that an animal has claws, eats fish, and is endangered.

 

See the Pen 4c3d5870c698a95896932a0d90444584?editors=001 by osublake (@osublake) on CodePen

  • Like 1
Link to comment
Share on other sites

Sure, just when I think I'm understanding a few things around here, you two go and start talking over my head.  :-D

 

Actually, i think it's cool when you bring up things outside the everyday code around here. It forces me to fire up the Google machine and do some reading. Thanks to both of you for the extra ideas on implementing a check for these situations.

  • Like 1
Link to comment
Share on other sites

Hey PointC here are some helpful links on Bitwise operators. Some might be relating to ActionScript.. but the logic is the same for javascript

 

http://lab.polygonal.de/?p=81

http://rocha.la/JavaScript-bitwise-operators-in-practice

http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/operators.html

 

Bit-wise Operators are usually like 10-50ms faster than native javascript math methods, especially in Firefox/IE compared to Chrome.

 

http://jsperf.com/jsfvsbitnot

  • Like 2
Link to comment
Share on other sites

Great discussion, guys. Sorry I'm late to the party.

 

I did want to chime in and suggest caution with just adding 0.5 and using bitwise because that only works properly on POSITIVE values. So:

var rotation = 45.3;
console.log((rotation + 0.5) | 0);  //45 (good)
rotation = -45.3;
console.log((rotation + 0.5) | 0); //-44 (bad)

A solution would be:

var roundedValue = (rotation < 0) ? (rotation - 0.5) | 0 : (rotation + 0.5) | 0;

That's pretty much how I do it in GSAP. 

  • Like 3
Link to comment
Share on other sites

Oh yeah, forgot to bring that up. It just chops off the decimal and doesn't actually floor it. I updated the jsperf that Jonathan linked to. ParseInt is really slow. Will you really notice the difference? Maybe if you are making an animation engine like Jack, or a very action oriented game. Under normal usage, probably not. 

 

http://jsperf.com/jsfvsbitnot/30

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