Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve rounding #715

Merged
merged 6 commits into from Nov 17, 2018
Merged

Improve rounding #715

merged 6 commits into from Nov 17, 2018

Conversation

atomiks
Copy link
Collaborator

@atomiks atomiks commented Nov 16, 2018

Fixes #713

It appears that Math.round() is always preferred except with placements top and bottom (not a variation) and where Math.floor() is preferred (and only on the left and right values)...

See the playground: https://codepen.io/anon/pen/Pxjdoo

@FezVrasta
Copy link
Member

Should the playground include already your fix?

I see some misalignment:

image

@atomiks
Copy link
Collaborator Author

atomiks commented Nov 16, 2018

Yeah, line 1243...

Perhaps we need to take into account the odd/even width then.

When I resize the window using PR code:

  • pop = even, ref = odd === perfect
  • pop = even, ref = even === jittery / sometimes incorrect position

When I resize the window always using Math.round():

  • pop = even, ref = odd === jittery / sometimes incorrect position
  • pop = even, ref = even === perfect

Can you reproduce that?

@FezVrasta
Copy link
Member

Yes I can repro

@atomiks
Copy link
Collaborator Author

atomiks commented Nov 16, 2018

I think this is the pattern:

  • If the reference & popper are the same oddness in width, then use Math.round()
  • If they're different oddness in width, then use Math.floor()

This still only applies to horizontal placement and the left/right values

@atomiks
Copy link
Collaborator Author

atomiks commented Nov 16, 2018

Check the CodePen when the above is taken into account, I can't see jitters anymore

@FezVrasta
Copy link
Member

top and bottom are working fine as they are already?

@atomiks
Copy link
Collaborator Author

atomiks commented Nov 16, 2018

It appears so yes. Horizontally it appears flush with the reference, and you can change the code at the very bottom to left / left-end as an example too.

However I just noticed that pop = odd, ref = odd doesn't like Math.round() (but doesn't jitter). Another edge case? 😓

@FezVrasta
Copy link
Member

Please, let's make sure the behavior is consistent across browsers also.

@atomiks
Copy link
Collaborator Author

atomiks commented Nov 16, 2018

This might be getting stranger by the second..

If both are odd width, then we need

left: toInteger(bothOddWidth && !isVariation ? popper.left - 1 : popper.left),

I'm going to check Firefox and Safari now

@FezVrasta
Copy link
Member

FezVrasta commented Nov 16, 2018

Considering the number of edge cases you are finding I think it would make sense to write some tests to make sure everything is working as expected.

@atomiks
Copy link
Collaborator Author

atomiks commented Nov 16, 2018

On Firefox it looks like the element is positioned on a half pixel, no matter what if we use a full pixel then it can't be positioned correctly. My suggestion there would be not to round if the screen is HiDPI (so there won't be blurriness). If you don't round at all, FF appears perfect, same with Safari.

Something like window.devicePixelRatio >= 2 ? dontRound : round

@FezVrasta
Copy link
Member

this thing is getting complex 😅

may we limit this PR to the most basic use case (non HiDPI screens) and think about the HiDPI screens in a new one?

@atomiks
Copy link
Collaborator Author

atomiks commented Nov 16, 2018

Yeah it's kinda crazy for something that should be simple..

FWIW here is what it would look like to check for that:

const isVertical = ['left', 'right'].indexOf(data.placement) !== -1;
const isVariation = data.placement.indexOf('-') !== -1;
const sameWidthOddness = reference.width % 2 === popper.width % 2;
const bothOddWidth = reference.width % 2 === 1 && popper.width % 2 === 1;
const isHiDPIScreen = window.devicePixelRatio >= 2;
const noRound = v => v;

// We don't need to round if the device has a high pixel density
const horizontalToInteger = isHiDPIScreen
  ? noRound
  : isVertical || isVariation || sameWidthOddness
  ? Math.round
  : Math.floor;
const verticalToInteger = isHiDPIScreen ? noRound : Math.round;

const offsets = {
  left: horizontalToInteger(
    bothOddWidth && !isVariation && !isHiDPIScreen
      ? popper.left - 1
      : popper.left
  ),
  top: verticalToInteger(popper.top),
  bottom: verticalToInteger(popper.bottom),
  right: horizontalToInteger(popper.right)
};

And yes I agree it needs tests due to all the logic 😅

Edit: Aand Chrome prefers to be rounded.... 🤣 so it needs:

  • Device pixel density check
  • User agent check
  • Popper + reference width check
  • Placement variation check

@FezVrasta
Copy link
Member

if you add the tests I'll merge it 😜

@atomiks
Copy link
Collaborator Author

atomiks commented Nov 16, 2018

Will try to write some tests in a sec

@atomiks
Copy link
Collaborator Author

atomiks commented Nov 17, 2018

@FezVrasta are the tests appropriate?

@FezVrasta
Copy link
Member

Thanks yes they look solid!

@FezVrasta FezVrasta merged commit 44be8de into floating-ui:master Nov 17, 2018
@atomiks atomiks deleted the fix-rounding branch November 17, 2018 11:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants