Autoscroll in Angular 2

后端 未结 10 1142
清歌不尽
清歌不尽 2020-12-01 12:21

I\'m experiencing an issue with Angular 2 where changing from one route to another does not automatically scroll to the top of the new view. I realize that Angular 1 allowed

相关标签:
10条回答
  • 2020-12-01 12:42

    I posted this in the issue thread, but I'll post it again here.

    My team has been using what the angular team uses on this repo on angular.io. Just make a service and inject it like usual. Then, on ngAfterViewInit on each page you want this behavior, just call this.[scroll service variable name].scrollToTop(). Finally, you'll need to add this to the top of <body> in index.html: <div id="top-of-page"></div>

    Service Code:

    import { Injectable, Inject } from '@angular/core';
    import { PlatformLocation } from '@angular/common';
    import { DOCUMENT } from '@angular/platform-browser';
    import {fromEvent} from 'rxjs/observable/fromEvent';
    
    export const topMargin = 16;
    /**
     * A service that scrolls document elements into view
     */
    @Injectable()
    export class ScrollService {
    
      private _topOffset: number | null;
      private _topOfPageElement: Element;
    
      // Offset from the top of the document to bottom of any static elements
      // at the top (e.g. toolbar) + some margin
      get topOffset() {
        if (!this._topOffset) {
          const toolbar = this.document.querySelector('md-toolbar.app-toolbar');
          this._topOffset = (toolbar && toolbar.clientHeight || 0) + topMargin;
        }
        return this._topOffset;
      }
    
      get topOfPageElement() {
        if (!this._topOfPageElement) {
          this._topOfPageElement = this.document.getElementById('top-of-page') || this.document.body;
        }
        return this._topOfPageElement;
      }
    
      constructor(
          @Inject(DOCUMENT) private document: any,
          private location: PlatformLocation) {
        // On resize, the toolbar might change height, so "invalidate" the top offset.
        fromEvent(window, 'resize').subscribe(() => this._topOffset = null);
      }
    
      /**
       * Scroll to the element with id extracted from the current location hash fragment.
       * Scroll to top if no hash.
       * Don't scroll if hash not found.
       */
      scroll() {
        const hash = this.getCurrentHash();
        const element: HTMLElement = hash
            ? this.document.getElementById(hash)
            : this.topOfPageElement;
        this.scrollToElement(element);
      }
    
      /**
       * Scroll to the element.
       * Don't scroll if no element.
       */
      scrollToElement(element: Element) {
        if (element) {
          element.scrollIntoView();
    
          if (window && window.scrollBy) {
            // Scroll as much as necessary to align the top of `element` at `topOffset`.
            // (Usually, `.top` will be 0, except for cases where the element cannot be scrolled all the
            //  way to the top, because the viewport is larger than the height of the content after the
            //  element.)
            window.scrollBy(0, element.getBoundingClientRect().top - this.topOffset);
    
            // If we are very close to the top (<20px), then scroll all the way up.
            // (This can happen if `element` is at the top of the page, but has a small top-margin.)
            if (window.pageYOffset < 20) {
              window.scrollBy(0, -window.pageYOffset);
            }
          }
        }
      }
    
      /** Scroll to the top of the document. */
      scrollToTop() {
        this.scrollToElement(this.topOfPageElement);
      }
    
      /**
       * Return the hash fragment from the `PlatformLocation`, minus the leading `#`.
       */
      private getCurrentHash() {
        return this.location.hash.replace(/^#/, '');
      }
    }
    
    0 讨论(0)
  • 2020-12-01 12:46

    Newer RCs (>= RC.3) don't seem to expose a changes Observable, it probably has since been renamed to events or routerEvents.

    Their utterly "fantastic" docs don't seem to provide any information on what's doing what, so I guess you're in for a little Russian Roulette there. Or flip a coin or something.

    From this answer, it seems the events Observable returns events regarding navigation state:

    router.events.subscribe(event:Event => {
        if(event is NavigationStart) {
        }
        // NavigationEnd
        // NavigationCancel
        // NavigationError
        // RoutesRecognized   
      }
    
    0 讨论(0)
  • 2020-12-01 12:48

    For Those of you who find window.scrollTo(0,0) not working (I'm guessing because of material design sidenav but totally guessing) use the method found here: Javascript / CSS window.scrollTo(0,0) not working

    0 讨论(0)
  • 2020-12-01 12:52

    100% solution tested by me:

    constructor(router:Router){
        this.router.events.subscribe(() => {
            window.scrollTo(0, 0);
        });
    }
    
    0 讨论(0)
  • 2020-12-01 13:04

    Instead of writing code in each component, I added the following code in one place -

    <router-outlet (activate)="onActivate($event)"></router-outlet>
    
        onActivate(e) {
            window.scrollTo(0, 0);
        }
    
    0 讨论(0)
  • 2020-12-01 13:06

    update

    Currently there is no automatic way.

    See also Angular 2 typescript error when using subscribe function on new router (rc 1)

    See also https://github.com/angular/angular/issues/6595#issuecomment-244232725

    class MyAppComponent {
      constructor(router: Router) {
        router.events.subscribe(s => {
          if (s instanceof NavigationEnd) {
            const tree = router.parseUrl(router.url);
            if (tree.fragment) {
              // you can use DomAdapter
              const element = document.querySelector("#" + tree.fragment);
              if (element) { element.scrollIntoView(element); }
            }
          }
        });
      }
    }
    

    update

    In the new router V3-beta.2 you can pass a fragment with router links and router navigation

    <a [routerLink]="..." fragment="top">
    

    it should scroll to it but also adds #top to the URL (not tested myself yet)

    Update

    Original

    There is an open issue covering this https://github.com/angular/angular/issues/6595

    A workaround (mentioned in https://github.com/angular/angular/issues/6946)

    Inject the router, subscribe to route changes and invoke the scroll to top:

    >= RC.x

    router.changes.subscribe() => {
      window.scrollTo(0, 0);
    });
    

    beta

    router.events
    .filter(e => e instanceof NavigationEnd)
    .subscribe(() => {
      window.scrollTo(0, 0);
    });
    
    0 讨论(0)
提交回复
热议问题