Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Tabs] Automatically page scrolls to top when switching between some tabs #9592

Closed
hoangthau opened this issue Jan 25, 2018 · 53 comments · Fixed by #22265
Closed

[Tabs] Automatically page scrolls to top when switching between some tabs #9592

hoangthau opened this issue Jan 25, 2018 · 53 comments · Fixed by #22265
Assignees
Labels
area: material/tabs P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent

Comments

@hoangthau
Copy link

hoangthau commented Jan 25, 2018

Bug, feature request, or proposal:

We found a bug. We had top content and a tab group below this content. And then we scrolled a little bit to bottom. We switched between some tabs. Now, page scrolled to top automatically.

It only occurs when we have top content on this page.

What is the expected behavior?

Page should keep scroll position when switching some tabs

What is the current behavior?

Page scrolls to top when switching some tabs

What are the steps to reproduce?

-Step 1: Scroll a little bit to bottom on page
-Step 2: Click on Tab 3
The page will scroll to top.

Demo here: https://stackblitz.com/edit/angular-wjtrxy

What is the use-case or motivation for changing an existing behavior?

A bug

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

Angular 5.0.3 - Material 5.0.2 - Window 10 - Typescript 2.4.2

Is there anything else we should know?

Thanks a lot!

@hoangthau hoangthau changed the title [mat-tabs] Automatically scroll to top page when switching between 2 tabs [Tabs] Automatically page scrolls to top when switching between some tabs Jan 25, 2018
@andrewseguin andrewseguin self-assigned this Jan 25, 2018
@andrewseguin andrewseguin added the P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent label Jan 25, 2018
@DanielPinkpank
Copy link

I have the same Issue.
And I noticed that the height of the "top content" seems to have an influence on this problem.
If you change the height to 900px for example, it doesn't scroll to top anymore.

@praveenpuglia

This comment has been minimized.

@anshumansworld

This comment has been minimized.

@umimehar

This comment has been minimized.

@umimehar
Copy link

umimehar commented Mar 7, 2018

Well the reason is when you change the tab, the next tab not have that much height of its inner contents so the parent height also decrease. thats why the its scroll to top. try the parent element height fixed like 1000px and then change tab, it will not scroll up. 👍

@umimehar
Copy link

umimehar commented Mar 7, 2018

https://stackblitz.com/edit/angular-wjtrxy-g4ksm6

Add a min-height="800px" to parent elemnt of mat-tab-group. :)

@hoangthau
Copy link
Author

Thanks @umimehar. That's a good workaround.

@TolgaBA

This comment has been minimized.

@luchoman08
Copy link

luchoman08 commented Jun 13, 2018

.fill-available {
  min-height: min-height: stretch;
}

With this class called in the main container of the page worked for me, only in chrome not in mozilla.

@luchoman08
Copy link

luchoman08 commented Jun 13, 2018

Better -webkit-fill-available (tested on google chrome). Here is an example https://stackblitz.com/edit/angular-wjtrxy-x9shma

@Lakston
Copy link

Lakston commented Jun 28, 2018

None of the suggested solutions completely fix the problem for me, I still end up with some weird scrolling behaviors with a pixel min-height, stretch, fill-available...

edit: I modified @luchoman08 's stackblitz to reflect the problem:
https://stackblitz.com/edit/angular-wjtrxy-utgzn1

I removed some content from tab1 so now if you go to tab4 for example, scroll down a bit, then click tab1, the scrolling bug is still there, switch between those 2 for extra sea sickness effects.

@imransilvake

This comment has been minimized.

@KlausHans
Copy link

Still a problem with Angular 6.1 and Angular Material 6.4.

@shifatul-i
Copy link

shifatul-i commented Aug 25, 2018

min-height property don't work with MS Edge, still jumps to the top.

@RobChangCA
Copy link

Still a problem with angular 7.1.4 and material 7.2

@aszorenyi

This comment has been minimized.

@GSchutz
Copy link

GSchutz commented Feb 5, 2019

another hack approach, is to set min-height and padding/margin bottom for short content and to keep a footer in place.

min-height: 450px;
padding-bottom: 200px;
margin-bottom: -200px;

@ludarous
Copy link

ludarous commented Apr 9, 2019

"min-height" solution did not work for me. It works, but i had to set min-height to meaningless value. I wrote directive that works for me. You may have to change document.documentElement to your scrolling element. It is not extra clean solution, but it works.

import {MatTabChangeEvent, MatTabGroup} from '@angular/material';

@Directive({
  selector: '[mat-tab-scroll-fix]'
})
export class MatTabScrollFixDirective implements AfterViewInit {

  constructor(private matTabGroup: MatTabGroup) {

  }

  private scrollPosition: number;
  private tabChanging: boolean;

  ngAfterViewInit(): void {
    const scrollHandler = (event) => {
      if (this.tabChanging) {
        document.documentElement.scrollTop = this.scrollPosition;
      }
      this.scrollPosition = document.documentElement.scrollTop;
    };

    window.addEventListener('scroll', scrollHandler);

    this.matTabGroup.selectedTabChange.subscribe((tabChangeEvent: MatTabChangeEvent) => {
      this.tabChanging = false;
      document.documentElement.scrollTop = this.scrollPosition;
    });

    this.matTabGroup.selectedIndexChange.subscribe((index: number) => {
      this.tabChanging = true;
    });
  }
}

@runningdavid

This comment has been minimized.

@BobaFett58
Copy link

Still have this problem. Angular 8

@lxREAPERxl
Copy link

lxREAPERxl commented Jun 4, 2019

I might have a solution for this. Its a crude solution, but is seems to be working, at least for me it does :) So let me explain:

You need a parent component, which has the entire mat-tab-group code, and child components, for each individual mat-tab body (I suppose you can do without parent/child as well).

Parent component HTML:

<mat-tab-group (selectedTabChange)="forceScrollPosition($event)">
  <mat-tab>
    <app-tab-child-0
    [scrollPosition]="scrollChild0" 
    [canUpdateScroll]="updateChild0Scroll"
    (scrollChanged)="scrollChanged($event)">
    </app-tab-child-0>
  </mat-tab>
  <mat-tab>
    <app-tab-child-1 
    [scrollPosition]="scrollChild1" 
    [canUpdateScroll]="updateChild1Scroll"
    (scrollChanged)="scrollChanged($event)">
    </app-tab-child-1>
  </mat-tab>
</mat-tab-group>

Parent component TS:

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-tab-parent',
  templateUrl: './app-tab-parent.html',
  styleUrls: ['./app-tab-parent.scss']
})
export class ParentComponent implements OnInit {

  // Hold scroll positions for each child (can be also array of values, it doesnt matter)
  scrollChild0: number = 0;
  scrollChild1: number = 0;
  
  // Permission for child component to emit scroll change
  updateChild0Scroll: boolean = true; //The FIRST tab displayed should have this attribute set to ,,true"
  updateChild1Scroll: boolean = false;
  
  // There are several ways to trigger method in child, I prefer this one
  @ViewChild(TabChild0Component) private tabChild0Component: TabChild0Component;
  @ViewChild(TabChild1Component) private tabChild0Component: TabChild1Component;
  
  
  //Here set new scroll position for specific child tab
  public scrollChanged(event: any) {
      //example
      if(event.index === 0) {
          this.scrollChild0 = event.value;
      }
      else if ...... {
          ....
      }
  }
  
  // When selected tab is changed, revalidate permissions and force child to load latest scroll
  public forceScrollPosition(event: any) {
      //example
      this.updateChild0Scroll= event.index === 0;
      this.updateChild1Scroll= event.index === 1;
      ....
      // now you need to identify selected tab and force child component to load last saved scroll
      if(event.index === 0) {
          this.tabChild0Component.loadScroll();
      }
      else if ...... {
          ....
      }
  }
}

Child component TS:

import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-tab-child-0',
  templateUrl: './app-tab-child-0.html',
  styleUrls: ['./app-tab-child-0.scss']
})
export class TabChild0Component implements OnInit {
  @Input() scrollPosition: number;
  @Input() canUpdateScroll: boolean;
  
  @Output() scrollChanged = new EventEmitter();
  
  // This path targets mat tab body. the ,,#mat-tab-content-0-0" needs to be changes, depending on 
  // child index position in the tabs (#mat-tab-content-0-0, #mat-tab-content-0-1, ...)
  private readonly elementPath = "#mat-tab-content-0-0 .mat-tab-body-content.ng-trigger.ng-trigger-translateTab";
  
  
  ngOnInit() {
     let element = document.querySelector(this.elementPath);
      let base = this;
  
      if(element) {
        element.addEventListener('scroll', function() {
          // Check if this child is currently focused tab, otherwise it emits 0 by default and defeats the whole 
          // purpose
          if(base.canUpdateScroll) {
            base.scrollChanged.emit(document.querySelector(base.elementPath).scrollTop);
          }
        });
      }
  }
  
   /** Load last known scroll position */
  public loadScroll() {
     let element = document.querySelector(this.elementPath);
     if(element) {
       document.querySelector(this.elementPath).scrollTop = this.scrollPosition;
     }
  }    
}

Again, sorry for the crude code, but I hope it can give you at least some basic idea of my solution. If it will be helpful to anyone, well, I am glad I could help :)

@VeeRHunter
Copy link

https://stackblitz.com/edit/angular-wjtrxy-g4ksm6

Add a min-height="800px" to parent elemnt of mat-tab-group. :)

Thank you, I solved scroll to top of page issue using this code 👍

@perotom
Copy link

perotom commented Aug 6, 2019

Especially bad with variable tab content as min-height and padding hacks are not working.

@noamichael
Copy link

It would be great to get an update on this.

@skaiser
Copy link

skaiser commented Oct 8, 2019

I was able to use min-height: 100% on the parent with no other styling.

@britvik
Copy link

britvik commented Apr 3, 2020

Made a small directive that retains the scroll position when you change tabs.

import { Directive, ElementRef, OnDestroy } from '@angular/core'
import { fromEvent, Subscription } from 'rxjs'

@Directive({
  selector: '[xxxScrollRetainer]'
})
export class ScrollRetainerDirective implements OnDestroy {
  private changes: MutationObserver
  private lastScrollPosition = 0
  private subscription: Subscription

  constructor(private elementRef: ElementRef) {
    this.changes = new MutationObserver(() => this.rollbackScrollPosition())
    this.changes.observe(this.elementRef.nativeElement, { childList: true, subtree: true })
    this.subscription = fromEvent(window, 'scroll').subscribe(() => {
      this.lastScrollPosition = window.pageYOffset
    })
  }

  private rollbackScrollPosition() {
    window.scrollTo(window.pageXOffset, this.lastScrollPosition)
  }

  ngOnDestroy(): void {
    this.subscription.unsubscribe()
    this.changes.disconnect()
  }

}

@leojkwan
Copy link

The issue is unrelated to UI tab modules, I can confirmed because the same scrolling happened with ngbTab. The issue, for me at least, is storing tab state in the query params.

You can implement custom scroll restoration behavior by adapting the enabled behavior as in the following example.
https://angular.io/api/router/ExtraOptions#scrollPositionRestoration

Setting scrollPositionRestoration: 'enabled', solves the issue, but screws up expected behavior in my app everywhere else. Use the custom check option the link above mentions to hack your edge case together.

For context, the min-height does not solve it for me on Angular 9.

@pookdeveloper

This comment has been minimized.

@pookdeveloper
Copy link

pookdeveloper commented May 13, 2020

The min height is depend the size of the window no always is needed 800px o 500px etc..
I do that:
image
Then:
image

@jneuhaus20
Copy link

The problem is the little placeholder element before sliding in the next tab. It has a min-height of 1px, and even though it's in and out (and the next tab in the DOM to the side) before you can see it, the scroll change still happens.

If it instead latched the height of the outgoing tab, behavior would be as most people expect.

@pookdeveloper
Copy link

@jneuhaus20 I dosen't see that, can you send a screenshot ?

@olatignies
Copy link

@britvik works for me. Thanks.

@DavidTheProgrammer
Copy link

@britvik Which component do you attach your directive to? I attached it to mat-tab-group that didn't work. Infact, the page didn't load.

@britvik
Copy link

britvik commented Jun 11, 2020

@DavidTheProgrammer You can attach it to any element (e.g. div) that contains the tab group.

@asyahril

This comment has been minimized.

@etay2000

This comment has been minimized.

@Brooksie82
Copy link

I'm also having this issue. I've tried @britvik's Directive (doesn't fix for me... fromEvent doesn't appear to fire), I've also tried min-height on containing div. Works in Chrome & Edge but not Firefox.

@Drillman
Copy link

Drillman commented Aug 19, 2020

I've made this directive based on @ludarous answer, to make it work with Angular CDK scrolling. It work on my side, but not the smoothest option.

import { AfterViewInit, Directive, OnInit, OnDestroy } from '@angular/core';
import { MatTabGroup, MatTabChangeEvent } from '@angular/material/tabs';
import { ScrollDispatcher, CdkScrollable } from '@angular/cdk/scrolling';
import { Subscription } from 'rxjs';

@Directive({
  selector: '[ScrollRetainer]'
})
export class ScrollRetainerDirective implements AfterViewInit, OnInit, OnDestroy {

  constructor(private matTabGroup: MatTabGroup, private scrollDispatcher: ScrollDispatcher) {}

  private container: CdkScrollable;
  private scrollSub: Subscription;
  private tabSub: Subscription;

  ngOnInit(): void {
    this.scrollSub = this.scrollDispatcher.scrolled().subscribe(
      (el: CdkScrollable) => {
        this.container = el;
      });
  }

  ngAfterViewInit(): void {
    this.tabSub = this.matTabGroup.selectedTabChange.subscribe((tabChangeEvent: MatTabChangeEvent) => {
      this.container.scrollTo({
        top: this.container.measureScrollOffset('top'),
        left: 0,
        behavior: 'auto'
      });
    });
  }

  ngOnDestroy(): void {
    this.scrollSub.unsubscribe();
    this.tabSub.unsubscribe();
  }
}

@owen26
Copy link

owen26 commented Aug 20, 2020

after tweak the min-height solution a few times, I realised the number is not totally random

if you set min-height to any number, it will prevent any subsequent tab clicks but not the first one after page load/refresh, in fact it'll jump to window top and then scroll to tab top, that's even worse.

if you set min-height to a number larger than the distance from top of the page to top of the nav tab, then it will prevent jumping for all tab clicks including the first click, that's why in a few previous examples in this thread, setting a min-height: 800px fixes it. But it doesn't have to be 800, it just needs to be larger than the distance mentioned above.

So if you have a project with predicted layout, setting min-height might be the quickest hacky way to fix this.

@gokberknur
Copy link

This issue happening on my angular 9 application, but in different scenario.

I have modal pop-up, when i close the modal, page automatically scrolls up in internet explorer.

previously with angular 8 and material 7.3.3 I was able to fix this issue by adding window.scroll() event after modal event.

but after updating angular 9 and material 9. window.scroll() is not working anymore.

I removed window.scroll() event and in chrome and other browsers scroll behaviour working as I expected.. but not in internet explorer, so I am not sure if it is related to window.scroll() compability with IE or is material breaking something.

anyone figured out how to solve this issue?

@Omzig
Copy link

Omzig commented Sep 29, 2020

Still happens in 9.1.12! Please fix this. It was driving me nuts till I used the work around, but work arounds are difficult to remember. my html now has bug comments in it. ugg

@PTC-JoshuaMatthews

This comment has been minimized.

@saithis
Copy link

saithis commented Dec 7, 2020

We had the same problem and fixed it with this workaround:

@Directive({
    // tslint:disable-next-line: directive-selector
    selector: 'mat-tab-group[scrollFix]'
})
export class PsMatTabGroupScrollFixDirective implements AfterViewInit, OnDestroy {

    private matTabGroupEl: HTMLElement = this.el.nativeElement;
    private animationSub = Subscription.EMPTY;

    constructor(private matTabGroup: MatTabGroup, private el: ElementRef) {

    }

    public ngAfterViewInit(): void {
        const orig = this.matTabGroup._handleClick.bind(this.matTabGroup);
        this.matTabGroup._handleClick = (tab, tabHeader, index) => {
            if (!tab.disabled) {
                this.matTabGroupEl.style.minHeight = this.matTabGroupEl.clientHeight + 'px';
            }

            return orig(tab, tabHeader, index);
        }
        this.animationSub = this.matTabGroup.animationDone.subscribe(() => {
            this.matTabGroupEl.style.minHeight = 'unset';
        });
    }

    public ngOnDestroy() {
        this.animationSub.unsubscribe();
    }
}

Unfortunatelly it overrides the _handleClick method, which is not in the public api. So it can break at any update, but at least it works for now.

@etay2000

This comment has been minimized.

@Splaktar
Copy link
Member

Splaktar commented Jan 10, 2021

Here's an updated demo that reproduces this issue with 11.0.3.

And another demo that demonstrates that this behavior still exists with the MDC-based tabs in @angular/material-experimental.

@ukrukarg
Copy link
Contributor

Same happening to me, for now I'm solving this using tips provided above, but proper fix would be awesome.

andrewseguin pushed a commit that referenced this issue Apr 27, 2021
* fix(material/tabs): stop scrolling on tab change

Adds min-height to the mat-tab-group wrapper, so page height is preserved
as tabs change, and page doesn't scroll up.

Fixes #9592

* fix(material/tabs): stop scrolling on tab change

Adds min-height to the mat-tab-group wrapper, so page height is preserved
as tabs change, and page doesn't scroll up.

Fixes #9592

* fix(material/tabs): stop scrolling on tab change …

Adds min-height to the mat-tab-group wrapper, so page height is preserved
as tabs change, and page doesn't scroll up.

Fixes #9592

(cherry picked from commit c12f168)
andrewseguin pushed a commit that referenced this issue Apr 27, 2021
* fix(material/tabs): stop scrolling on tab change

Adds min-height to the mat-tab-group wrapper, so page height is preserved
as tabs change, and page doesn't scroll up.

Fixes #9592

* fix(material/tabs): stop scrolling on tab change

Adds min-height to the mat-tab-group wrapper, so page height is preserved
as tabs change, and page doesn't scroll up.

Fixes #9592

* fix(material/tabs): stop scrolling on tab change …

Adds min-height to the mat-tab-group wrapper, so page height is preserved
as tabs change, and page doesn't scroll up.

Fixes #9592
andrewseguin pushed a commit that referenced this issue Apr 27, 2021
* fix(material/tabs): stop scrolling on tab change

Adds min-height to the mat-tab-group wrapper, so page height is preserved
as tabs change, and page doesn't scroll up.

Fixes #9592

* fix(material/tabs): stop scrolling on tab change

Adds min-height to the mat-tab-group wrapper, so page height is preserved
as tabs change, and page doesn't scroll up.

Fixes #9592

* fix(material/tabs): stop scrolling on tab change …

Adds min-height to the mat-tab-group wrapper, so page height is preserved
as tabs change, and page doesn't scroll up.

Fixes #9592

(cherry picked from commit c12f168)
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators May 28, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: material/tabs P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
Projects
None yet
Development

Successfully merging a pull request may close this issue.