@@ -198,6 +198,7 @@ export class MatDrawer implements AfterViewInit, OnDestroy {
198198 private _renderer = inject ( Renderer2 ) ;
199199 private readonly _interactivityChecker = inject ( InteractivityChecker ) ;
200200 private _doc = inject ( DOCUMENT ) ;
201+ private _isAnimating = false ;
201202 _container ? = inject < MatDrawerContainer > ( MAT_DRAWER_CONTAINER , { optional : true } ) ;
202203
203204 private _focusTrap : FocusTrap | null = null ;
@@ -450,9 +451,16 @@ export class MatDrawer implements AfterViewInit, OnDestroy {
450451 case 'first-tabbable' :
451452 afterNextRender (
452453 ( ) => {
453- const hasMovedFocus = this . _focusTrap ! . focusInitialElement ( ) ;
454+ // If we try to capture focus mid-animation, we can end up shifting the page,
455+ // if the drawer starts off from the end so prevent scrolling in this case.
456+ // This should mostly happen in edge cases where the drawer is toggled rapidly.
457+ const focusOptions : FocusOptions | undefined = this . _isAnimating
458+ ? { preventScroll : true }
459+ : undefined ;
460+
461+ const hasMovedFocus = this . _focusTrap ! . focusInitialElement ( focusOptions ) ;
454462 if ( ! hasMovedFocus && typeof element . focus === 'function' ) {
455- element . focus ( ) ;
463+ element . focus ( focusOptions ) ;
456464 }
457465 } ,
458466 { injector : this . _injector } ,
@@ -587,20 +595,22 @@ export class MatDrawer implements AfterViewInit, OnDestroy {
587595 this . _getContent ( ) ?. _drawerToggled ( this ) ;
588596
589597 if ( this . _container ?. _transitionsEnabled ) {
590- // Note: it's important to set this as early as possible,
591- // otherwise the animation can look glitchy in some cases.
592- this . _setIsAnimating ( true ) ;
593-
594- // Previously we dispatched this in a `transitionrun` event, but it might not fire
595- // if the element is hidden (see #32992). Since this event is load-bearing for the
596- // margin calculations, we need it to fire consistently.
597- setTimeout ( ( ) => this . _animationStarted . next ( ) ) ;
598+ if ( this . _isAnimating ) {
599+ this . _setIsAnimating ( false ) ;
600+ this . _simulateAnimation ( ) ;
601+ } else {
602+ // Note: it's important to set this as early as possible,
603+ // otherwise the animation can look glitchy in some cases.
604+ this . _setIsAnimating ( true ) ;
605+
606+ // Previously we dispatched this in a `transitionrun` event, but it might not fire
607+ // if the element is hidden (see #32992). Since this event is load-bearing for the
608+ // margin calculations, we need it to fire consistently.
609+ setTimeout ( ( ) => this . _animationStarted . next ( ) ) ;
610+ }
598611 } else {
599612 // Simulate the animation events if animations are disabled.
600- setTimeout ( ( ) => {
601- this . _animationStarted . next ( ) ;
602- this . _animationEnd . next ( ) ;
603- } ) ;
613+ this . _simulateAnimation ( ) ;
604614 }
605615
606616 this . _elementRef . nativeElement . classList . toggle ( 'mat-drawer-opened' , isOpen ) ;
@@ -625,7 +635,17 @@ export class MatDrawer implements AfterViewInit, OnDestroy {
625635
626636 /** Toggles whether the drawer is currently animating. */
627637 private _setIsAnimating ( isAnimating : boolean ) {
628- this . _elementRef . nativeElement . classList . toggle ( 'mat-drawer-animating' , isAnimating ) ;
638+ if ( isAnimating !== this . _isAnimating ) {
639+ this . _isAnimating = isAnimating ;
640+ this . _elementRef . nativeElement . classList . toggle ( 'mat-drawer-animating' , isAnimating ) ;
641+ }
642+ }
643+
644+ private _simulateAnimation ( ) {
645+ setTimeout ( ( ) => {
646+ this . _animationStarted . next ( ) ;
647+ this . _animationEnd . next ( ) ;
648+ } ) ;
629649 }
630650
631651 _getWidth ( ) : number {
0 commit comments