Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit bd8d065

Browse files
authoredApr 30, 2024··
fix(ios): large title transition accounts for back button with no text (#29327)
Issue number: resolves #28751 --------- <!-- 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. --> The large title transition does not account for back buttons with no text value. We assume that the [`.button-text` element ](https://github.com/ionic-team/ionic-framework/blob/bfaf528e61fd82c9106e3c5060921fb79d97156a/core/src/components/back-button/back-button.tsx#L168) is always defined, but that is not the case when `text=""` on the back button. As a result, devs were getting errors because we tried to get the bounding box of a undefined. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> - Revised the large title logic to only grab values from the back button text if the back button text element is actually defined There should be **no behavior change** when the back button text element is defined. ## 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/docs/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: `7.8.5-dev.11713282122.18cabf98` ⚠️ Reviewers: Please test this in the sample application on the linked issue. Please be sure to test the following conditions: 1. When the back button text is defined 2. When the back button text is not defined 3. With the default font scale 4. With a larger font scale
1 parent 4d09890 commit bd8d065

File tree

1 file changed

+110
-77
lines changed

1 file changed

+110
-77
lines changed
 

‎core/src/utils/transition/ios.transition.ts

Lines changed: 110 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,10 @@ const createLargeTitleTransition = (
7575
const leavingLargeTitleBox = leavingLargeTitle.getBoundingClientRect();
7676
const enteringBackButtonBox = enteringBackButton.getBoundingClientRect();
7777

78-
const enteringBackButtonTextEl = shadow(enteringBackButton).querySelector('.button-text')!;
79-
const enteringBackButtonTextBox = enteringBackButtonTextEl.getBoundingClientRect();
78+
const enteringBackButtonTextEl = shadow(enteringBackButton).querySelector('.button-text');
79+
80+
// Text element not rendered if developers pass text="" to the back button
81+
const enteringBackButtonTextBox = enteringBackButtonTextEl?.getBoundingClientRect();
8082

8183
const leavingLargeTitleTextEl = shadow(leavingLargeTitle).querySelector('.toolbar-title')!;
8284
const leavingLargeTitleTextBox = leavingLargeTitleTextEl.getBoundingClientRect();
@@ -88,6 +90,7 @@ const createLargeTitleTransition = (
8890
leavingLargeTitle,
8991
leavingLargeTitleBox,
9092
leavingLargeTitleTextBox,
93+
enteringBackButtonBox,
9194
enteringBackButtonTextEl,
9295
enteringBackButtonTextBox
9396
);
@@ -106,8 +109,10 @@ const createLargeTitleTransition = (
106109
const enteringLargeTitleBox = enteringLargeTitle.getBoundingClientRect();
107110
const leavingBackButtonBox = leavingBackButton.getBoundingClientRect();
108111

109-
const leavingBackButtonTextEl = shadow(leavingBackButton).querySelector('.button-text')!;
110-
const leavingBackButtonTextBox = leavingBackButtonTextEl.getBoundingClientRect();
112+
const leavingBackButtonTextEl = shadow(leavingBackButton).querySelector('.button-text');
113+
114+
// Text element not rendered if developers pass text="" to the back button
115+
const leavingBackButtonTextBox = leavingBackButtonTextEl?.getBoundingClientRect();
111116

112117
const enteringLargeTitleTextEl = shadow(enteringLargeTitle).querySelector('.toolbar-title')!;
113118
const enteringLargeTitleTextBox = enteringLargeTitleTextEl.getBoundingClientRect();
@@ -119,6 +124,7 @@ const createLargeTitleTransition = (
119124
enteringLargeTitle,
120125
enteringLargeTitleBox,
121126
enteringLargeTitleTextBox,
127+
leavingBackButtonBox,
122128
leavingBackButtonTextEl,
123129
leavingBackButtonTextBox
124130
);
@@ -147,8 +153,8 @@ const animateBackButton = (
147153
backDirection: boolean,
148154
backButtonEl: HTMLIonBackButtonElement,
149155
backButtonBox: DOMRect,
150-
backButtonTextEl: HTMLElement,
151-
backButtonTextBox: DOMRect,
156+
backButtonTextEl: HTMLElement | null,
157+
backButtonTextBox: DOMRect | undefined,
152158
largeTitleEl: HTMLIonTitleElement,
153159
largeTitleTextBox: DOMRect
154160
) => {
@@ -158,31 +164,35 @@ const animateBackButton = (
158164
const ICON_ORIGIN_X = rtl ? 'left' : 'right';
159165

160166
const CONTAINER_ORIGIN_X = rtl ? 'right' : 'left';
167+
let WIDTH_SCALE = 1;
168+
let HEIGHT_SCALE = 1;
161169

162-
/**
163-
* When the title and back button texts match
164-
* then they should overlap during the page transition.
165-
* If the texts do not match up then the back button text scale adjusts
166-
* to not perfectly match the large title text otherwise the
167-
* proportions will be incorrect.
168-
* When the texts match we scale both the width and height to account for
169-
* font weight differences between the title and back button.
170-
*/
171-
const doTitleAndButtonTextsMatch = backButtonTextEl.textContent?.trim() === largeTitleEl.textContent?.trim();
172-
173-
const WIDTH_SCALE = largeTitleTextBox.width / backButtonTextBox.width;
174-
175-
/**
176-
* We subtract an offset to account for slight sizing/padding
177-
* differences between the title and the back button.
178-
*/
179-
const HEIGHT_SCALE = (largeTitleTextBox.height - LARGE_TITLE_SIZE_OFFSET) / backButtonTextBox.height;
180-
181-
const TEXT_START_SCALE = doTitleAndButtonTextsMatch
182-
? `scale(${WIDTH_SCALE}, ${HEIGHT_SCALE})`
183-
: `scale(${HEIGHT_SCALE})`;
170+
let TEXT_START_SCALE = `scale(${HEIGHT_SCALE})`;
184171
const TEXT_END_SCALE = 'scale(1)';
185172

173+
if (backButtonTextEl && backButtonTextBox) {
174+
/**
175+
* When the title and back button texts match then they should overlap during the
176+
* page transition. If the texts do not match up then the back button text scale
177+
* adjusts to not perfectly match the large title text otherwise the proportions
178+
* will be incorrect. When the texts match we scale both the width and height to
179+
* account for font weight differences between the title and back button.
180+
*/
181+
const doTitleAndButtonTextsMatch = backButtonTextEl.textContent?.trim() === largeTitleEl.textContent?.trim();
182+
WIDTH_SCALE = largeTitleTextBox.width / backButtonTextBox.width;
183+
/**
184+
* Subtract an offset to account for slight sizing/padding differences between the
185+
* title and the back button.
186+
*/
187+
HEIGHT_SCALE = (largeTitleTextBox.height - LARGE_TITLE_SIZE_OFFSET) / backButtonTextBox.height;
188+
189+
/**
190+
* Even though we set TEXT_START_SCALE to HEIGHT_SCALE above, we potentially need
191+
* to re-compute this here since the HEIGHT_SCALE may have changed.
192+
*/
193+
TEXT_START_SCALE = doTitleAndButtonTextsMatch ? `scale(${WIDTH_SCALE}, ${HEIGHT_SCALE})` : `scale(${HEIGHT_SCALE})`;
194+
}
195+
186196
const backButtonIconEl = shadow(backButtonEl).querySelector('ion-icon')!;
187197
const backButtonIconBox = backButtonIconEl.getBoundingClientRect();
188198

@@ -292,12 +302,11 @@ const animateBackButton = (
292302
top: '0px',
293303
[CONTAINER_ORIGIN_X]: '0px',
294304
})
295-
.keyframes(CONTAINER_KEYFRAMES);
296-
297-
enteringBackButtonTextAnimation
298-
.beforeStyles({
299-
'transform-origin': `${TEXT_ORIGIN_X} top`,
300-
})
305+
/**
306+
* The write hooks must be set on this animation as it is guaranteed to run. Other
307+
* animations such as the back button text animation will not run if the back button
308+
* has no visible text.
309+
*/
301310
.beforeAddWrite(() => {
302311
backButtonEl.style.setProperty('display', 'none');
303312
clonedBackButtonEl.style.setProperty(TEXT_ORIGIN_X, BACK_BUTTON_START_OFFSET);
@@ -307,6 +316,12 @@ const animateBackButton = (
307316
clonedBackButtonEl.style.setProperty('display', 'none');
308317
clonedBackButtonEl.style.removeProperty(TEXT_ORIGIN_X);
309318
})
319+
.keyframes(CONTAINER_KEYFRAMES);
320+
321+
enteringBackButtonTextAnimation
322+
.beforeStyles({
323+
'transform-origin': `${TEXT_ORIGIN_X} top`,
324+
})
310325
.keyframes(TEXT_KEYFRAMES);
311326

312327
enteringBackButtonIconAnimation
@@ -329,8 +344,9 @@ const animateLargeTitle = (
329344
largeTitleEl: HTMLIonTitleElement,
330345
largeTitleBox: DOMRect,
331346
largeTitleTextBox: DOMRect,
332-
backButtonTextEl: HTMLElement,
333-
backButtonTextBox: DOMRect
347+
backButtonBox: DOMRect,
348+
backButtonTextEl: HTMLElement | null,
349+
backButtonTextBox: DOMRect | undefined
334350
) => {
335351
/**
336352
* The horizontal transform origin for the large title
@@ -353,59 +369,76 @@ const animateLargeTitle = (
353369
* title and the back button due to padding and font weight.
354370
*/
355371
const LARGE_TITLE_TRANSLATION_OFFSET = 8;
372+
let END_TRANSLATE_X = rtl
373+
? `-${window.innerWidth - backButtonBox.right - LARGE_TITLE_TRANSLATION_OFFSET}px`
374+
: `${backButtonBox.x + LARGE_TITLE_TRANSLATION_OFFSET}px`;
356375

357376
/**
358-
* The scaled title should (roughly) overlap the back button.
359-
* This ensures that the back button and title overlap during
360-
* the animation. Note that since both elements either fade in
361-
* or fade out over the course of the animation, neither element
362-
* will be fully visible on top of the other. As a result, the overlap
363-
* does not need to be perfect, so approximate values are acceptable here.
377+
* How much to scale the large title up/down by.
364378
*/
365-
const END_TRANSLATE_X = rtl
366-
? `-${window.innerWidth - backButtonTextBox.right - LARGE_TITLE_TRANSLATION_OFFSET}px`
367-
: `${backButtonTextBox.x - LARGE_TITLE_TRANSLATION_OFFSET}px`;
379+
let HEIGHT_SCALE = 0.5;
368380

369381
/**
370-
* The top of the scaled large title
371-
* should match with the top of the
372-
* back button text element.
373-
* We subtract 2px to account for the top padding
374-
* on the large title element.
382+
* The large title always starts full size.
375383
*/
376-
const LARGE_TITLE_TOP_PADDING = 2;
377-
const END_TRANSLATE_Y = `${backButtonTextBox.y - LARGE_TITLE_TOP_PADDING}px`;
384+
const START_SCALE = 'scale(1)';
378385

379386
/**
380-
* In the forward direction, the large title should start at its
381-
* normal size and then scale down to be (roughly) the size of the
382-
* back button on the other view. In the backward direction, the
383-
* large title should start at (roughly) the size of the back button
384-
* and then scale up to its original size.
385-
*
386-
* Note that since both elements either fade in
387-
* or fade out over the course of the animation, neither element
388-
* will be fully visible on top of the other. As a result, the overlap
389-
* does not need to be perfect, so approximate values are acceptable here.
387+
* By default, we don't worry about having the large title scaled to perfectly
388+
* match the back button because we don't know if the back button's text matches
389+
* the large title's text.
390390
*/
391+
let END_SCALE = `scale(${HEIGHT_SCALE})`;
392+
393+
// Text element not rendered if developers pass text="" to the back button
394+
if (backButtonTextEl && backButtonTextBox) {
395+
/**
396+
* The scaled title should (roughly) overlap the back button. This ensures that
397+
* the back button and title overlap during the animation. Note that since both
398+
* elements either fade in or fade out over the course of the animation, neither
399+
* element will be fully visible on top of the other. As a result, the overlap
400+
* does not need to be perfect, so approximate values are acceptable here.
401+
*/
402+
END_TRANSLATE_X = rtl
403+
? `-${window.innerWidth - backButtonTextBox.right - LARGE_TITLE_TRANSLATION_OFFSET}px`
404+
: `${backButtonTextBox.x - LARGE_TITLE_TRANSLATION_OFFSET}px`;
405+
406+
/**
407+
* In the forward direction, the large title should start at its normal size and
408+
* then scale down to be (roughly) the size of the back button on the other view.
409+
* In the backward direction, the large title should start at (roughly) the size
410+
* of the back button and then scale up to its original size.
411+
* Note that since both elements either fade in or fade out over the course of the
412+
* animation, neither element will be fully visible on top of the other. As a result,
413+
* the overlap does not need to be perfect, so approximate values are acceptable here.
414+
*/
415+
416+
/**
417+
* When the title and back button texts match then they should overlap during the
418+
* page transition. If the texts do not match up then the large title text scale
419+
* adjusts to not perfectly match the back button text otherwise the proportions
420+
* will be incorrect. When the texts match we scale both the width and height to
421+
* account for font weight differences between the title and back button.
422+
*/
423+
const doTitleAndButtonTextsMatch = backButtonTextEl.textContent?.trim() === largeTitleEl.textContent?.trim();
424+
425+
const WIDTH_SCALE = backButtonTextBox.width / largeTitleTextBox.width;
426+
HEIGHT_SCALE = backButtonTextBox.height / (largeTitleTextBox.height - LARGE_TITLE_SIZE_OFFSET);
427+
428+
/**
429+
* Even though we set TEXT_START_SCALE to HEIGHT_SCALE above, we potentially need
430+
* to re-compute this here since the HEIGHT_SCALE may have changed.
431+
*/
432+
END_SCALE = doTitleAndButtonTextsMatch ? `scale(${WIDTH_SCALE}, ${HEIGHT_SCALE})` : `scale(${HEIGHT_SCALE})`;
433+
}
391434

392435
/**
393-
* When the title and back button texts match
394-
* then they should overlap during the page transition.
395-
* If the texts do not match up then the large title text scale adjusts
396-
* to not perfectly match the back button text otherwise the
397-
* proportions will be incorrect.
398-
* When the texts match we scale both the width and height to account for
399-
* font weight differences between the title and back button.
436+
* The midpoints of the back button and the title should align such that the back
437+
* button and title appear to be centered with each other.
400438
*/
401-
const doTitleAndButtonTextsMatch = backButtonTextEl.textContent?.trim() === largeTitleEl.textContent?.trim();
402-
403-
const WIDTH_SCALE = backButtonTextBox.width / largeTitleTextBox.width;
404-
const HEIGHT_SCALE = backButtonTextBox.height / (largeTitleTextBox.height - LARGE_TITLE_SIZE_OFFSET);
405-
406-
const START_SCALE = 'scale(1)';
407-
408-
const END_SCALE = doTitleAndButtonTextsMatch ? `scale(${WIDTH_SCALE}, ${HEIGHT_SCALE})` : `scale(${HEIGHT_SCALE})`;
439+
const backButtonMidPoint = backButtonBox.top + backButtonBox.height / 2;
440+
const titleMidPoint = (largeTitleBox.height * HEIGHT_SCALE) / 2;
441+
const END_TRANSLATE_Y = `${backButtonMidPoint - titleMidPoint}px`;
409442

410443
const BACKWARDS_KEYFRAMES = [
411444
{ offset: 0, opacity: 0, transform: `translate3d(${END_TRANSLATE_X}, ${END_TRANSLATE_Y}, 0) ${END_SCALE}` },

0 commit comments

Comments
 (0)
Please sign in to comment.