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

Popover: Avoid paint on popovers when scrolling #46187

Merged
merged 2 commits into from
Dec 12, 2022

Conversation

corentin-gautier
Copy link
Contributor

@corentin-gautier corentin-gautier commented Nov 30, 2022

What?

This PR changes the way popovers are positioned from top/left to translate(x, y) to avoid repaint on scroll

Why?

Changing the top/left properties triggers browser repaint which will result in poor performances

Testing Instructions

  1. Open the dev console and enable the paint flashing (under rendering)
  2. Select a block
  3. Scroll so that the block toolbar needs to be re-positioned
  4. There should not be paint on the toolbar

Fixes #45382

Screenshot

Before

Screen.Recording.2022-10-28.at.15.47.36.mov

After

Screen.Recording.2022-11-30.at.14.12.43.mov

@github-actions
Copy link

👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @corentin-gautier! In case you missed it, we'd love to have you join us in our Slack community, where we hold regularly weekly meetings open to anyone to coordinate with each other.

If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information.

@github-actions github-actions bot added the First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository label Nov 30, 2022
@corentin-gautier corentin-gautier changed the title Avoid paint on popovers Avoid paint on popovers when scrolling Nov 30, 2022
@skorasaurus skorasaurus added the [Type] Performance Related to performance efforts label Nov 30, 2022
@ciampo ciampo added the [Package] Components /packages/components label Nov 30, 2022
@@ -2,6 +2,7 @@ $arrow-triangle-base-size: 14px;

.components-popover {
z-index: z-index(".components-popover");
will-change: transform;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #46197 (comment) for a suggestion regarding how we add / remove this CSS property.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tyxla since the number of popover is limited I don't think this is a big issue, BUT it might actually not be needed since popovers seem to get a translateZ for free 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tyxla I have reverted the change since the translateZ does the job, however it seems to trigger repaint of some of the elements inside the block toolbar which is ... weird 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @corentin-gautier mentioned, technically we shouldn't need it because of the translateZ() value that framer motion already adds — in that sense, I didn't expect the browser to trigger repaints as reported in the previous message 🤷 although browser heuristics about when to create a new composite layer can change and evolve in time, so we're never really guaranteed.

Regarding the when we should add/remove this prop, it's worth mentioning that the Popover component should be rendered only when the popover is actually visible — therefore, it seems OK to me that we keep that CSS rule as always ON: it's only rendered when the popover is open.

Copy link
Contributor Author

@corentin-gautier corentin-gautier Dec 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ciampo do you recommend i add it back then ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good if you could investigate a bit more into why removing will-change: transform causes paint flashes in the toolbar, and see if we can achieve the desired result in any other way.

But if after the investigation the only guaranteed way to avoid paint flashes if by having will-change: transform always applied, then yes — I would recommend you add it back at that point

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ciampo I looked into it yesterday but couldn't find the cause, it seems some of the svgs inside the toolbar trigger repaint when the toolbar position is updated ...

There are other tricks to avoid paint like using backface-visibility: hidden; so that's an option too :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have we tried setting contain: paint on the toolbar buttons, out of curiosity?

There are other tricks to avoid paint like using backface-visibility: hidden; so that's an option too :)

Yup, although that doesn't really change the fact that we'd be "forcing" the popover to be on its own composite layer — so will-change: transform seems like a better (more semantic) choice

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ciampo using contain: paint; is .... worse 😂 It triggers paint of all the buttons (I know it's very weird ahah)

Screen.Recording.2022-12-02.at.11.40.01.mov

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷 Let's stick to wil-change: transform then! Thank you for the investigation :)

If you ever feel like digging more into this and come up with more insights, I'm always eager to see how we can improve this component further!

// to use `translateX` and `translateY` because those values would
// be overridden by the return value of the
// `placementToMotionAnimationProps` function in `AnimatedWrapper`
x: Math.round( x ?? 0 ) || undefined,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, while animating we were accepting decimal values for x and y, and now we're rounding them. This is likely the reason for a bunch of e2e tests failing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tyxla Yes it was suggested by @ciampo here : #45545 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tyxla The tests indeed fail because of this but I have no idea how to change the expected values

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

npm run test:unit:update should update the snapshots — you should then commit those changes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ciampo I ran it but there nothing to commit 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind rebasing and fixing conflicts? I'm currently unable to see that tests are failing, which makes it harder to make an informed suggestion 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tyxla I can see unit tests erroring because of the act warning — do you mind helping out here? (I'm going to be AFK in the following weeks)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing! Happy to aid with that next week.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the change here results in 0 values not being applied to update popover positions for popovers that are intended to be flush with the edge of the screen. I've opened up a potential fix over in #51320.

@ellatrix
Copy link
Member

ellatrix commented Dec 1, 2022

Looks like this is making the toolbar a lot shakier when scrolling?

@corentin-gautier
Copy link
Contributor Author

corentin-gautier commented Dec 1, 2022

Looks like this is making the toolbar a lot shakier when scrolling?

Having paint flashing enabled + the element selected in the dev tools makes it shaky, Without this it behaves normally 🙂

@jasmussen
Copy link
Contributor

Feels pretty solid to me:

Screeny.Video.2.Dec.2022.at.09.43.26.mov

Thank you for all these PRs! 👏

Copy link
Member

@tyxla tyxla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks and works well for me, thanks for the great work @corentin-gautier! 🚀

@corentin-gautier corentin-gautier force-pushed the fix/popover-paint branch 2 times, most recently from 43c04a7 to c253f6e Compare December 2, 2022 13:16
@ciampo
Copy link
Contributor

ciampo commented Dec 2, 2022

Before merging, do you mind adding a CHANGELOG entry for the changes in the @wordpress/components package, as suggested by Aaron in this other PR?

We could actually add an entry also for #46197 which was merged without one

@tyxla
Copy link
Member

tyxla commented Dec 5, 2022

@corentin-gautier tests appear to be failing for a few reasons here:

  • E2e tests - failing because the e2e tests expect x and y values to not be rounded, and we're now rounding them. It should be fine to manually adjust them to the new rounded values.
  • Unit tests - failing because of the act warnings, seems to be because there's a promise running after the popovers are fully positioned, which makes the tests fragile. A straightforward way to fix them is to unmount() at the end of the failing tests, in order to cancel any pending effects that would cause act() warnings.

Let me know if that helps!

@corentin-gautier
Copy link
Contributor Author

@tyxla Hum, yes there's a chance 😅 I now have the same result but not much more info ...

@corentin-gautier
Copy link
Contributor Author

@tyxla I can fix the test by using a rounding function on firstBlockBox

Something like this :

const roundBoundingBox = (obj) => {
	Object.keys(obj).forEach(k => {
		obj[k] = Math.round(obj[k]);
	});
	return obj;
}

But I don't know if this should be the way we fix this

@corentin-gautier corentin-gautier force-pushed the fix/popover-paint branch 2 times, most recently from 04697d0 to 4a57091 Compare December 7, 2022 16:01
@corentin-gautier
Copy link
Contributor Author

corentin-gautier commented Dec 7, 2022

@tyxla thing is : if we round the position of the popover, then it will never be strictly equal to the position of the block (which is calculated with a getBoundingClientRect and will return sub-pixel values, https://playwright.dev/docs/api/class-locator#locator-bounding-box)

From what I understand of the tests, their are testing that the dropzone (a BlockPopover) position is equal to the block (a paragraph) position.

@tyxla
Copy link
Member

tyxla commented Dec 8, 2022

@tyxla thing is : if we round the position of the popover, then it will never be strictly equal to the position of the block (which is calculated with a getBoundingClientRect and will return sub-pixel values, https://playwright.dev/docs/api/class-locator#locator-bounding-box)

From what I understand of the tests, their are testing that the dropzone (a BlockPopover) position is equal to the block (a paragraph) position.

Indeed, but then that points to a different problem - we either should change our expectations that the two positions will be equal (meaning, reconsidering the test completely instead of just rounding one of the values), or we should use the same heuristics - either both rounding or both non-rounding. Since rounding both is a no-go, should we reconsider the rounding for the popover position?

@corentin-gautier
Copy link
Contributor Author

corentin-gautier commented Dec 8, 2022

Indeed, but then that points to a different problem - we either should change our expectations that the two positions will be equal (meaning, reconsidering the test completely instead of just rounding one of the values), or we should use the same heuristics - either both rounding or both non-rounding. Since rounding both is a no-go, should we reconsider the rounding for the popover position?

As I mentioned before it was suggested by @ciampo here in reference to this

x and y can contain decimals, so unless the transform translation is placed evenly on the device's subpixel grid, then there will be blurring

I think it's reasonable to assume that the values won't be exactly the same (and by that I mean rounded, so not far from the same) because as you said we can't round the block's position but rounding the popover's position seems to be required in order to get a sharp UI

That being said it wasn't rounded before so maybe this could be the subject of another PR ?

@tyxla
Copy link
Member

tyxla commented Dec 8, 2022

That being said it wasn't rounded before so maybe this could be the subject of another PR ?

I'm inclined to agree with that. I'd love to get a second opinion from @mirka if possible.

@mirka
Copy link
Member

mirka commented Dec 9, 2022

That being said it wasn't rounded before so maybe this could be the subject of another PR ?

I'm inclined to agree with that. I'd love to get a second opinion from @mirka if possible.

You're asking whether we should leave the rounding part out of this PR? I wouldn't be opposed to that, but also it looks easy enough to alter the test assertions so it has tolerance for subpixel differences.

By the way, I think we could encapsulate the intent better if we explicitly tested for the tolerance, rather than apply rounding to the block values. So expect(Math.abs(ax-bx)).toBeLessThan(1) or something like that. Just a thought.

@tyxla
Copy link
Member

tyxla commented Dec 9, 2022

By the way, I think we could encapsulate the intent better if we explicitly tested for the tolerance, rather than apply rounding to the block values. So expect(Math.abs(ax-bx)).toBeLessThan(1) or something like that. Just a thought.

Loving that suggestion FWIW 👍 I'm happy to go with that - WDYT @corentin-gautier?

With regards to the failing unit tests - @jsnajdr mentioned he has a branch that could fix them - would you like to try cherry-picking them to your branch and seeing if it fixes the tests?

@jsnajdr
Copy link
Member

jsnajdr commented Dec 9, 2022

After #46429 is merged, you can rebase this PR on top of it and it should solve the failing unit tests (that test popover positioning).

@corentin-gautier
Copy link
Contributor Author

@tyxla @mirka what do you think of this : b50ca9f ?

I've tried keeping the tests to be one liners instead of duplicating for x and x

Copy link
Member

@tyxla tyxla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! 💯 Thanks @corentin-gautier 🙌

A potential follow-up could be creating a custom inline matcher to make the assertion more eloquent, but that's not a big deal IMHO, since your comment is useful in making the intent clear.

I'll ship this for you 🚀

Thanks again and keep up the great work 🥇

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
First-time Contributor Pull request opened by a first-time contributor to Gutenberg repository [Package] Components /packages/components [Type] Performance Related to performance efforts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Components: Popover uses top and left positioning hurting scroll performance
9 participants