@@ -75,8 +75,10 @@ const createLargeTitleTransition = (
75
75
const leavingLargeTitleBox = leavingLargeTitle . getBoundingClientRect ( ) ;
76
76
const enteringBackButtonBox = enteringBackButton . getBoundingClientRect ( ) ;
77
77
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 ( ) ;
80
82
81
83
const leavingLargeTitleTextEl = shadow ( leavingLargeTitle ) . querySelector ( '.toolbar-title' ) ! ;
82
84
const leavingLargeTitleTextBox = leavingLargeTitleTextEl . getBoundingClientRect ( ) ;
@@ -88,6 +90,7 @@ const createLargeTitleTransition = (
88
90
leavingLargeTitle ,
89
91
leavingLargeTitleBox ,
90
92
leavingLargeTitleTextBox ,
93
+ enteringBackButtonBox ,
91
94
enteringBackButtonTextEl ,
92
95
enteringBackButtonTextBox
93
96
) ;
@@ -106,8 +109,10 @@ const createLargeTitleTransition = (
106
109
const enteringLargeTitleBox = enteringLargeTitle . getBoundingClientRect ( ) ;
107
110
const leavingBackButtonBox = leavingBackButton . getBoundingClientRect ( ) ;
108
111
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 ( ) ;
111
116
112
117
const enteringLargeTitleTextEl = shadow ( enteringLargeTitle ) . querySelector ( '.toolbar-title' ) ! ;
113
118
const enteringLargeTitleTextBox = enteringLargeTitleTextEl . getBoundingClientRect ( ) ;
@@ -119,6 +124,7 @@ const createLargeTitleTransition = (
119
124
enteringLargeTitle ,
120
125
enteringLargeTitleBox ,
121
126
enteringLargeTitleTextBox ,
127
+ leavingBackButtonBox ,
122
128
leavingBackButtonTextEl ,
123
129
leavingBackButtonTextBox
124
130
) ;
@@ -147,8 +153,8 @@ const animateBackButton = (
147
153
backDirection : boolean ,
148
154
backButtonEl : HTMLIonBackButtonElement ,
149
155
backButtonBox : DOMRect ,
150
- backButtonTextEl : HTMLElement ,
151
- backButtonTextBox : DOMRect ,
156
+ backButtonTextEl : HTMLElement | null ,
157
+ backButtonTextBox : DOMRect | undefined ,
152
158
largeTitleEl : HTMLIonTitleElement ,
153
159
largeTitleTextBox : DOMRect
154
160
) => {
@@ -158,31 +164,35 @@ const animateBackButton = (
158
164
const ICON_ORIGIN_X = rtl ? 'left' : 'right' ;
159
165
160
166
const CONTAINER_ORIGIN_X = rtl ? 'right' : 'left' ;
167
+ let WIDTH_SCALE = 1 ;
168
+ let HEIGHT_SCALE = 1 ;
161
169
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 } )` ;
184
171
const TEXT_END_SCALE = 'scale(1)' ;
185
172
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
+
186
196
const backButtonIconEl = shadow ( backButtonEl ) . querySelector ( 'ion-icon' ) ! ;
187
197
const backButtonIconBox = backButtonIconEl . getBoundingClientRect ( ) ;
188
198
@@ -292,12 +302,11 @@ const animateBackButton = (
292
302
top : '0px' ,
293
303
[ CONTAINER_ORIGIN_X ] : '0px' ,
294
304
} )
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
+ */
301
310
. beforeAddWrite ( ( ) => {
302
311
backButtonEl . style . setProperty ( 'display' , 'none' ) ;
303
312
clonedBackButtonEl . style . setProperty ( TEXT_ORIGIN_X , BACK_BUTTON_START_OFFSET ) ;
@@ -307,6 +316,12 @@ const animateBackButton = (
307
316
clonedBackButtonEl . style . setProperty ( 'display' , 'none' ) ;
308
317
clonedBackButtonEl . style . removeProperty ( TEXT_ORIGIN_X ) ;
309
318
} )
319
+ . keyframes ( CONTAINER_KEYFRAMES ) ;
320
+
321
+ enteringBackButtonTextAnimation
322
+ . beforeStyles ( {
323
+ 'transform-origin' : `${ TEXT_ORIGIN_X } top` ,
324
+ } )
310
325
. keyframes ( TEXT_KEYFRAMES ) ;
311
326
312
327
enteringBackButtonIconAnimation
@@ -329,8 +344,9 @@ const animateLargeTitle = (
329
344
largeTitleEl : HTMLIonTitleElement ,
330
345
largeTitleBox : DOMRect ,
331
346
largeTitleTextBox : DOMRect ,
332
- backButtonTextEl : HTMLElement ,
333
- backButtonTextBox : DOMRect
347
+ backButtonBox : DOMRect ,
348
+ backButtonTextEl : HTMLElement | null ,
349
+ backButtonTextBox : DOMRect | undefined
334
350
) => {
335
351
/**
336
352
* The horizontal transform origin for the large title
@@ -353,59 +369,76 @@ const animateLargeTitle = (
353
369
* title and the back button due to padding and font weight.
354
370
*/
355
371
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` ;
356
375
357
376
/**
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.
364
378
*/
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 ;
368
380
369
381
/**
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.
375
383
*/
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)' ;
378
385
379
386
/**
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.
390
390
*/
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
+ }
391
434
392
435
/**
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.
400
438
*/
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` ;
409
442
410
443
const BACKWARDS_KEYFRAMES = [
411
444
{ offset : 0 , opacity : 0 , transform : `translate3d(${ END_TRANSLATE_X } , ${ END_TRANSLATE_Y } , 0) ${ END_SCALE } ` } ,
0 commit comments