Skip to content

Commit 94c3ffc

Browse files
authored
perf(picker): avoid flicker on ios (#29101)
Issue number: N/A (see below) --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> Mike found an issue while testing the Ionic 8 beta where there's a flicker when presenting a picker in a modal on iOS. [We've seen this issue before](https://github.com/ionic-team/ionic-framework/blob/de13633a182d963876434db773aa346833f956fd/core/src/utils/forms/notch-controller.ts#L135-L144), and the reason why it's happening is there's a quirk in WebKit where the IntersectionObserver callback is fired _after_ an accelerated animation completes given a particular IO configuration. The end result is there's a delay before each picker column is scrolled to the correct place. In particular, the modal enter animation on iOS is an accelerated animation, and we use an IO to control when the picker columns [should scroll their active options into view](https://github.com/ionic-team/ionic-framework/blob/60056643a99aea9817d2cd205fd011ab5967a995/core/src/components/picker-column/picker-column.tsx#L107). ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - The root of the intersection observer is now the parent picker element which avoids the WebKit quirk. | `feature-8.0` | branch | | - | - | | <video src="https://github.com/ionic-team/ionic-framework/assets/2721089/7478512b-a691-405e-aafa-e9e377fc47ac"></video> | <video src="https://github.com/ionic-team/ionic-framework/assets/2721089/5092e050-733a-4965-8f16-317d251af46a"></video> | ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change: 1. Describe the impact and migration path for existing applications below. 2. Update the BREAKING.md file with the breaking change. 3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/.github/CONTRIBUTING.md#footer for more information. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Dev build: `8.0.0-dev.11709226349.179192de` Note: Given that this bug is due to a WebKit quirk and is timing-related, I did not add a test.
1 parent bf1701e commit 94c3ffc

File tree

1 file changed

+19
-2
lines changed

1 file changed

+19
-2
lines changed

core/src/components/picker-column/picker-column.tsx

+19-2
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,22 @@ export class PickerColumn implements ComponentInterface {
8787
* height of 0px.
8888
*/
8989
componentWillLoad() {
90+
/**
91+
* We cache parentEl in a local variable
92+
* so we don't need to keep accessing
93+
* the class variable (which comes with
94+
* a small performance hit)
95+
*/
96+
const parentEl = (this.parentEl = this.el.closest('ion-picker') as HTMLIonPickerElement | null);
97+
9098
const visibleCallback = (entries: IntersectionObserverEntry[]) => {
9199
const ev = entries[0];
92100

93101
if (ev.isIntersecting) {
94102
const { activeItem, el } = this;
95103

96104
this.isColumnVisible = true;
105+
97106
/**
98107
* Because this initial call to scrollActiveItemIntoView has to fire before
99108
* the scroll listener is set up, we need to manage the active class manually.
@@ -119,9 +128,17 @@ export class PickerColumn implements ComponentInterface {
119128
}
120129
}
121130
};
122-
new IntersectionObserver(visibleCallback, { threshold: 0.001 }).observe(this.el);
131+
/**
132+
* Set the root to be the parent picker element
133+
* This causes the IO callback
134+
* to be fired in WebKit as soon as the element
135+
* is visible. If we used the default root value
136+
* then WebKit would only fire the IO callback
137+
* after any animations (such as a modal transition)
138+
* finished, and there would potentially be a flicker.
139+
*/
140+
new IntersectionObserver(visibleCallback, { threshold: 0.001, root: this.parentEl }).observe(this.el);
123141

124-
const parentEl = (this.parentEl = this.el.closest('ion-picker') as HTMLIonPickerElement | null);
125142
if (parentEl !== null) {
126143
// TODO(FW-2832): type
127144
parentEl.addEventListener('ionInputModeChange', (ev: any) => this.inputModeChange(ev));

0 commit comments

Comments
 (0)