Skip to content

Commit 1a976aa

Browse files
committed
fix(header): prevent flickering upon page transition on iOS
1 parent 3b80473 commit 1a976aa

File tree

2 files changed

+109
-16
lines changed

2 files changed

+109
-16
lines changed

core/src/components/header/header.ios.scss

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@
6565
* since it needs to blend in with the header above it.
6666
*/
6767
.header-collapse-condense ion-toolbar {
68-
--background: var(--ion-background-color, #fff);
69-
7068
z-index: 0;
7169
}
7270

@@ -93,6 +91,28 @@
9391
transition: all 0.2s ease-in-out;
9492
}
9593

94+
/**
95+
* Large title toolbar should just use the content background
96+
* since it needs to blend in with the header above it.
97+
*/
98+
.header-collapse-condense ion-toolbar,
99+
/**
100+
* Override styles applied during the page transition to prevent
101+
* header flickering.
102+
*/
103+
.header-collapse-condense-inactive.header-transitioning:not(.header-collapse-condense) ion-toolbar {
104+
--background: var(--ion-background-color, #fff);
105+
}
106+
107+
/**
108+
* Override styles applied during the page transition to prevent
109+
* header flickering.
110+
*/
111+
.header-collapse-condense-inactive.header-transitioning:not(.header-collapse-condense) ion-toolbar {
112+
--border-style: none;
113+
--opacity-scale: 1;
114+
}
115+
96116
.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-title,
97117
.header-collapse-condense-inactive:not(.header-collapse-condense) ion-toolbar.in-toolbar ion-buttons.buttons-collapse {
98118
opacity: 0;

core/src/utils/transition/index.ts

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,34 +18,51 @@ const focusController = createFocusController();
1818

1919
// TODO(FW-2832): types
2020

21+
/**
22+
* Executes the main page transition.
23+
* It also manages the lifecycle of header visibility (if any)
24+
* to prevent visual flickering in iOS. The flickering only
25+
* occurs for a condensed header that is placed above the content.
26+
*
27+
* @param opts Options for the transition.
28+
* @returns A promise that resolves when the transition is complete.
29+
*/
2130
export const transition = (opts: TransitionOptions): Promise<TransitionResult> => {
2231
return new Promise((resolve, reject) => {
2332
writeTask(() => {
24-
beforeTransition(opts);
25-
runTransition(opts).then(
26-
(result) => {
27-
if (result.animation) {
28-
result.animation.destroy();
33+
const transitioningInactiveHeader = getIosIonHeader(opts);
34+
beforeTransition(opts, transitioningInactiveHeader);
35+
runTransition(opts)
36+
.then(
37+
(result) => {
38+
if (result.animation) {
39+
result.animation.destroy();
40+
}
41+
afterTransition(opts);
42+
resolve(result);
43+
},
44+
(error) => {
45+
afterTransition(opts);
46+
reject(error);
2947
}
30-
afterTransition(opts);
31-
resolve(result);
32-
},
33-
(error) => {
34-
afterTransition(opts);
35-
reject(error);
36-
}
37-
);
48+
)
49+
.finally(() => {
50+
// Ensure that the header is restored to its original state.
51+
setHeaderTransitionClass(transitioningInactiveHeader, false);
52+
});
3853
});
3954
});
4055
};
4156

42-
const beforeTransition = (opts: TransitionOptions) => {
57+
const beforeTransition = (opts: TransitionOptions, transitioningInactiveHeader: HTMLElement | null) => {
4358
const enteringEl = opts.enteringEl;
4459
const leavingEl = opts.leavingEl;
4560

4661
focusController.saveViewFocus(leavingEl);
4762

4863
setZIndex(enteringEl, leavingEl, opts.direction);
64+
// Prevent flickering of the header by adding a class.
65+
setHeaderTransitionClass(transitioningInactiveHeader, true);
4966

5067
if (opts.showGoBack) {
5168
enteringEl.classList.add('can-go-back');
@@ -278,6 +295,36 @@ const setZIndex = (
278295
}
279296
};
280297

298+
/**
299+
* Add a class to ensure that the inactive header (if any)
300+
* does not flicker during the transition. By adding the
301+
* transitioning class, we ensure that the header has
302+
* the necessary styles to prevent the following flickers:
303+
* 1. When entering a page with a condensed header, the
304+
* inactive header should never be visible. However,
305+
* it briefly renders the background color while
306+
* the transition is occurring.
307+
* 2. When leaving a page with a condensed header, the
308+
* inactive header has an opacity of 0 and the pages
309+
* have a z-index which causes the entering page to
310+
* briefly show it's content underneath the leaving page.
311+
*
312+
* @param header The header element to modify.
313+
* @param isTransitioning Whether the transition is occurring.
314+
*/
315+
const setHeaderTransitionClass = (header: HTMLElement | null, isTransitioning: boolean) => {
316+
if (!header) {
317+
return;
318+
}
319+
320+
const transitionClass = 'header-transitioning';
321+
if (isTransitioning) {
322+
header.classList.add(transitionClass);
323+
} else {
324+
header.classList.remove(transitionClass);
325+
}
326+
};
327+
281328
export const getIonPageElement = (element: HTMLElement) => {
282329
if (element.classList.contains('ion-page')) {
283330
return element;
@@ -291,6 +338,32 @@ export const getIonPageElement = (element: HTMLElement) => {
291338
return element;
292339
};
293340

341+
/**
342+
* Retrieves the ion-header element from a page based on the
343+
* direction of the transition.
344+
*
345+
* @param opts Options for the transition.
346+
* @returns The ion-header element or null if not found or not in 'ios' mode.
347+
*/
348+
const getIosIonHeader = (opts: TransitionOptions): HTMLElement | null => {
349+
const enteringEl = opts.enteringEl;
350+
const leavingEl = opts.leavingEl;
351+
const direction = opts.direction;
352+
const mode = opts.mode;
353+
354+
if (mode !== 'ios') {
355+
return null;
356+
}
357+
358+
const element = direction === 'back' ? leavingEl : enteringEl;
359+
360+
if (!element) {
361+
return null;
362+
}
363+
364+
return element.querySelector('ion-header');
365+
};
366+
294367
export interface TransitionOptions extends NavOptions {
295368
progressCallback?: (ani: Animation | undefined) => void;
296369
baseEl: any;

0 commit comments

Comments
 (0)