Skip to content

fix(colorarea): remove layout thrashing and cache selectors #5550

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

tjgupta
Copy link
Contributor

@tjgupta tjgupta commented Jun 18, 2025

Remove layout invalidation from ColorArea and cache selectors

Description

This change removes layout invalidation/recalculation from the ColorArea when adjusting the current color, either with the mouse or with keyboard input. The change also caches the selectors for the queries so they don't need to be recalculated every time they're accessed.

Motivation and context

The layout thrashing had some negative performance impact, most noticeable in Safari on Intel based Macs.

Related issue(s)

Screenshots (if appropriate)

Before:
Screenshot 2025-06-17 at 11 27 45 PM

After:
Screenshot 2025-06-18 at 11 12 54 AM


Author's checklist

  • I have read the CONTRIBUTING and PULL_REQUESTS documents.
  • I have reviewed at the Accessibility Practices for this feature, see: Aria Practices
  • I have added automated tests to cover my changes.
  • I have included a well-written changeset if my change needs to be published.
  • I have included updated documentation if my change required it.

Reviewer's checklist

  • Includes a Github Issue with appropriate flag or Jira ticket number without a link
  • Includes thoughtfully written changeset if changes suggested include patch, minor, or major features
  • Automated tests cover all use cases and follow best practices for writing
  • Validated on all supported browsers
  • All VRTs are approved before the author can update Golden Hash

Manual review test cases

  • Descriptive Test Statement

    1. Go here
    2. Do this action
    3. Expect this result
  • Descriptive Test Statement

    1. Go here
    2. Do this action
    3. Expect this result

Device review

  • Did it pass in Desktop?
  • Did it pass in (emulated) Mobile?
  • Did it pass in (emulated) iPad?

@tjgupta tjgupta requested a review from a team as a code owner June 18, 2025 15:28
Copy link

changeset-bot bot commented Jun 18, 2025

🦋 Changeset detected

Latest commit: 06d7213

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 84 packages
Name Type
@spectrum-web-components/color-area Patch
@spectrum-web-components/bundle Patch
documentation Patch
@spectrum-web-components/eslint-plugin Patch
@spectrum-web-components/accordion Patch
@spectrum-web-components/action-bar Patch
@spectrum-web-components/action-button Patch
@spectrum-web-components/action-group Patch
@spectrum-web-components/action-menu Patch
@spectrum-web-components/alert-banner Patch
@spectrum-web-components/alert-dialog Patch
@spectrum-web-components/asset Patch
@spectrum-web-components/avatar Patch
@spectrum-web-components/badge Patch
@spectrum-web-components/breadcrumbs Patch
@spectrum-web-components/button-group Patch
@spectrum-web-components/button Patch
@spectrum-web-components/card Patch
@spectrum-web-components/checkbox Patch
@spectrum-web-components/clear-button Patch
@spectrum-web-components/close-button Patch
@spectrum-web-components/coachmark Patch
@spectrum-web-components/color-field Patch
@spectrum-web-components/color-handle Patch
@spectrum-web-components/color-loupe Patch
@spectrum-web-components/color-slider Patch
@spectrum-web-components/color-wheel Patch
@spectrum-web-components/combobox Patch
@spectrum-web-components/contextual-help Patch
@spectrum-web-components/dialog Patch
@spectrum-web-components/divider Patch
@spectrum-web-components/dropzone Patch
@spectrum-web-components/field-group Patch
@spectrum-web-components/field-label Patch
@spectrum-web-components/help-text Patch
@spectrum-web-components/icon Patch
@spectrum-web-components/icons-ui Patch
@spectrum-web-components/icons-workflow Patch
@spectrum-web-components/icons Patch
@spectrum-web-components/iconset Patch
@spectrum-web-components/illustrated-message Patch
@spectrum-web-components/infield-button Patch
@spectrum-web-components/link Patch
@spectrum-web-components/menu Patch
@spectrum-web-components/meter Patch
@spectrum-web-components/modal Patch
@spectrum-web-components/number-field Patch
@spectrum-web-components/overlay Patch
@spectrum-web-components/picker-button Patch
@spectrum-web-components/picker Patch
@spectrum-web-components/popover Patch
@spectrum-web-components/progress-bar Patch
@spectrum-web-components/progress-circle Patch
@spectrum-web-components/radio Patch
@spectrum-web-components/search Patch
@spectrum-web-components/sidenav Patch
@spectrum-web-components/slider Patch
@spectrum-web-components/split-view Patch
@spectrum-web-components/status-light Patch
@spectrum-web-components/swatch Patch
@spectrum-web-components/switch Patch
@spectrum-web-components/table Patch
@spectrum-web-components/tabs Patch
@spectrum-web-components/tags Patch
@spectrum-web-components/textfield Patch
@spectrum-web-components/thumbnail Patch
@spectrum-web-components/toast Patch
@spectrum-web-components/tooltip Patch
@spectrum-web-components/top-nav Patch
@spectrum-web-components/tray Patch
@spectrum-web-components/underlay Patch
@spectrum-web-components/custom-vars-viewer Patch
@spectrum-web-components/story-decorator Patch
@spectrum-web-components/vrt-compare Patch
@spectrum-web-components/base Patch
@spectrum-web-components/grid Patch
@spectrum-web-components/opacity-checkerboard Patch
@spectrum-web-components/reactive-controllers Patch
@spectrum-web-components/shared Patch
@spectrum-web-components/styles Patch
@spectrum-web-components/theme Patch
@spectrum-web-components/truncated Patch
example-project-rollup Patch
example-project-webpack Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

Branch preview

Review the following VRT differences

When a visual regression test fails (or has previously failed while working on this branch), its results can be found in the following URLs:

If the changes are expected, update the current_golden_images_cache hash in the circleci config to accept the new images. Instructions are included in that file.
If the changes are unexpected, you can investigate the cause of the differences and update the code accordingly.

Copy link

github-actions bot commented Jun 18, 2025

Tachometer results

Chrome

color-area permalink

basic-test

Version Bytes Avg Time vs remote vs branch
npm latest 610 kB 128.46ms - 130.52ms - faster ✔
4% - 6%
5.58ms - 8.22ms
branch 587 kB 135.56ms - 137.21ms slower ❌
4% - 6%
5.58ms - 8.22ms
-
Firefox

color-area permalink

basic-test

Version Bytes Avg Time vs remote vs branch
npm latest 610 kB 200.30ms - 207.62ms - faster ✔
1% - 7%
2.71ms - 13.93ms
branch 587 kB 208.03ms - 216.53ms slower ❌
1% - 7%
2.71ms - 13.93ms
-

Comment on lines 565 to 576
if (this.x !== this.inputX.valueAsNumber) {
this.colorController.color.set(
's',
this.inputX.valueAsNumber * 100
);
}
if (this.y !== this.inputY.valueAsNumber) {
this.colorController.color.set(
'v',
(1 - this.inputY.valueAsNumber) * 100
);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not entirely clear on why we're doing this here again, as we already set these in the various setters above. Also, the y value is setting the value differently than above (line 574), which seems incorrect. I don't see any side effects to removing this altogether.

Copy link
Contributor

@Rajdeepc Rajdeepc Jun 19, 2025

Choose a reason for hiding this comment

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

Removing these lines delegates full responsibility for color state updates to the x and y property setters which introduces a tighter coupling between property updates and state synchronisation.
If, for any reason, the input elements and the internal color state become unsynchronised, the component may exhibit inconsistent behavior—potentially leading to visual regressions.
This constitutes a breaking change in the component's internal state management logic and can have both visual and functional regressions.Your tests are failing bcz of the removal of this block.

We would need to a thorough audit of the all the state updates to ensure we are covering every aspects. We can add this as a enhancement but I would not block this PR due to this optimisation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed this part of the change for now, but as we started to discuss separately, I think it's worth considering whether it makes sense to have roughly the same state stored and updated in multiple places (and accidentally also in multiple ways).

In my opinion it's better to have only one representation of the color value stored in one piece of reactive state, and one way to update it. This state should be used to render the handle position (rather than querying the DOM) for best performance. The same state could also be used to derive the x/y values for the a11y range inputs.

I suspect (only a theory as of yet) that the fact that the color is represented both as a color in the ColorController, and sort of also as x and y coordinates in this ColorArea component (and that they're updated multiple times in different ways) are a source of some strange behavior, especially on slower browsers/devices or when the browser is under some additional load. See the end of this video for what I mean...

Screen.Recording.2025-06-19.at.10.01.19.AM.mov

@caseyisonit
Copy link
Contributor

@tjgupta can you please include detailed testing instructions? Also should this cause a visual change at all, I'm seeing a large number of VRT failures?

caveat: please don't update the golden hash until a second reviewer gives a thumbs up if visual changes are expected

@caseyisonit
Copy link
Contributor

Also can you added an automated test if possible to cover this code?

@Rajdeepc
Copy link
Contributor

Thanks for your contribution @tjgupta . Can you please add a script to set x and y rapidly like below.

const colorArea = document.querySelector('sp-color-area');
for (let i = 0; i <= 1; i += 0.01) {
    colorArea.x = i;
    colorArea.y = 1 - i;
}

Let's observe for layout (reflow/paint) spikes.

Comment on lines 565 to 576
if (this.x !== this.inputX.valueAsNumber) {
this.colorController.color.set(
's',
this.inputX.valueAsNumber * 100
);
}
if (this.y !== this.inputY.valueAsNumber) {
this.colorController.color.set(
'v',
(1 - this.inputY.valueAsNumber) * 100
);
}
Copy link
Contributor

@Rajdeepc Rajdeepc Jun 19, 2025

Choose a reason for hiding this comment

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

Removing these lines delegates full responsibility for color state updates to the x and y property setters which introduces a tighter coupling between property updates and state synchronisation.
If, for any reason, the input elements and the internal color state become unsynchronised, the component may exhibit inconsistent behavior—potentially leading to visual regressions.
This constitutes a breaking change in the component's internal state management logic and can have both visual and functional regressions.Your tests are failing bcz of the removal of this block.

We would need to a thorough audit of the all the state updates to ensure we are covering every aspects. We can add this as a enhancement but I would not block this PR due to this optimisation.

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.

[Bug]: Layout thrashing in ColorArea component
3 participants