Угловой 2 прокрутка вверх при изменении маршрута



в моем приложении Angular 2, Когда я прокручиваю страницу и нажимаю ссылку в нижней части страницы, она меняет маршрут и переносит меня на следующую страницу, но не прокручивается до верхней части страницы. В результате, если первая страница длинна, а 2-я страница имеет мало содержимого, создается впечатление, что 2-й странице не хватает содержимого. Поскольку содержимое отображается только в том случае, если пользователь прокручивает до верхней части страницы.



Я могу прокрутить окно в верхней части страницы, в ngInit из но есть ли лучшее решение, которое может автоматически обрабатывать все маршруты в моем приложении?

582   18  

18 ответов:

вы можете зарегистрировать прослушиватель изменения маршрута на своем основном компоненте и прокрутить его вверх по изменениям маршрута.

import { Component, OnInit } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';

@Component({
    selector: 'my-app',
    template: '<ng-content></ng-content>',
})
export class MyAppComponent implements OnInit {
    constructor(private router: Router) { }

    ngOnInit() {
        this.router.events.subscribe((evt) => {
            if (!(evt instanceof NavigationEnd)) {
                return;
            }
            window.scrollTo(0, 0)
        });
    }
}

Угловое 6.1 и выше:

угловой 6.1 (выпущен на 2018-07-25) добавлена встроенная поддержка для решения этой проблемы, через функцию под названием "Восстановление положения прокрутки маршрутизатора". Как описано в официальном Угловое блог, вам просто нужно включить это в настройки роутера, как это:

RouterModule.forRoot(routes, {scrollPositionRestoration: 'enabled'})

кроме того, в блоге говорится: "ожидается, что это станет дефолтом в будущем крупном выпуске", поэтому вполне вероятно, что из Angular 7 on, вам не нужно будет ничего делать в своем коде, и это будет просто работать правильно из коробки.

угловой 6.0 и ранее:

в то время как отличный ответ @GuilhermeMeireles исправляет исходную проблему, он вводит новую, нарушая нормальное поведение, которое вы ожидаете при навигации назад или вперед (с помощью кнопок браузера или через местоположение в коде). Ожидаемое поведение заключается в том, что при переходе обратно на страницу она должна оставаться прокрутите вниз до того же места, где он был, когда вы нажали на ссылку, но прокрутка вверх при прибытии на каждую страницу, очевидно, нарушает это ожидание.

приведенный ниже код расширяет логику для обнаружения такого рода навигации, подписываясь на последовательность PopStateEvent Location и пропуская логику прокрутки вверх, если вновь прибывшая страница является результатом такого события.

Если страница, с которой вы переходите назад, достаточно длинная, чтобы охватить весь видовой экран, то положение прокрутки восстанавливается автоматически, но, как правильно указал @JordanNelson, если страница короче, вам нужно отслеживать исходную позицию прокрутки y и восстанавливать ее явно при возвращении на страницу. Обновленная версия кода охватывает и этот случай, всегда явно восстанавливая положение прокрутки.

import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';
import { Location, PopStateEvent } from "@angular/common";

@Component({
    selector: 'my-app',
    template: '<ng-content></ng-content>',
})
export class MyAppComponent implements OnInit {

    private lastPoppedUrl: string;
    private yScrollStack: number[] = [];

    constructor(private router: Router, private location: Location) { }

    ngOnInit() {
        this.location.subscribe((ev:PopStateEvent) => {
            this.lastPoppedUrl = ev.url;
        });
        this.router.events.subscribe((ev:any) => {
            if (ev instanceof NavigationStart) {
                if (ev.url != this.lastPoppedUrl)
                    this.yScrollStack.push(window.scrollY);
            } else if (ev instanceof NavigationEnd) {
                if (ev.url == this.lastPoppedUrl) {
                    this.lastPoppedUrl = undefined;
                    window.scrollTo(0, this.yScrollStack.pop());
                } else
                    window.scrollTo(0, 0);
            }
        });
    }
}

вы можете написать это более кратко, воспользовавшись наблюдаемым filter способ:

this.router.events.filter(event => event instanceof NavigationEnd).subscribe(() => {
      this.window.scrollTo(0, 0);
});

если у вас возникли проблемы с прокруткой вверх при использовании углового материала 2 sidenav это поможет. В окне или теле документа не будет полосы прокрутки, поэтому вам нужно получить sidenav содержание и выделите этот элемент, в противном случае попробуйте прокрутить окно по умолчанию.

this.router.events.filter(event => event instanceof NavigationEnd)
  .subscribe(() => {
      const contentContainer = document.querySelector('.mat-sidenav-content') || this.window;
      contentContainer.scrollTo(0, 0);
});

кроме того, угловой CDK v6.x имеет a скролл пакет теперь это может помочь с обработкой прокрутки.

если у вас есть рендеринг на стороне сервера, вы должны быть осторожны, чтобы не запустить код с помощью windows на сервере, где эта переменная не существует. Это приведет к взлому кода.

export class AppComponent implements {
  routerSubscription: Subscription;

  constructor(private router: Router,
              @Inject(PLATFORM_ID) private platformId: any) {}

  ngOnInit() {
    if (isPlatformBrowser(this.platformId)) {
      this.routerSubscription = this.router.events
        .filter(event => event instanceof NavigationEnd)
        .subscribe(event => {
          window.scrollTo(0, 0);
        });
    }
  }

  ngOnDestroy() {
    this.routerSubscription.unsubscribe();
  }
}

isPlatformBrowser это функция, используемая для проверки того, является ли текущая платформа, на которой отображается приложение, браузером или нет. Мы даем ему инъекции platformId.

это также можно проверить на наличие переменной windows, на всякий случай, вот так:

if (typeof window != 'undefined')

от углового 6.1, теперь вы можете избежать хлопот и пройти extraOptions на RouterModule.forRoot() в качестве второго параметра можно указать и

scrollPositionRestoration: enabled

чтобы сказать Angular прокручивать вверх всякий раз, когда маршрут меняется.

@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      scrollPositionRestoration: 'enabled',
    })
  ],
  ...

Угловые Официальные Документы

просто сделайте это легко с помощью click action

в вашем основном компоненте html сделать ссылку #scrollContainer

<div class="main-container" #scrollContainer>
    <router-outlet (activate)="onActivate($event, scrollContainer)"></router-outlet>
</div>

в основной компонент .ТС

onActivate(e, scrollContainer) {
    scrollContainer.scrollTop = 0;
}

лучший ответ находится в угловом обсуждении GitHub (изменение маршрута не прокручивается вверх на новой странице).

может быть, вы хотите перейти к началу только в корневых изменениях маршрутизатора (не в детях, потому что вы можете загружать маршруты с ленивой загрузкой в F. e. tabset)

приложение.деталь.HTML-код

<router-outlet (deactivate)="onDeactivate()"></router-outlet>

приложение.деталь.ТС

onDeactivate() {
  document.body.scrollTop = 0;
  // Alternatively, you can scroll to top by using this other call:
  // window.scrollTo(0, 0)
}

полные кредиты JoniJnm (оригинал пост)

вы можете добавить AfterViewInit привязка жизненного цикла к компоненту.

ngAfterViewInit() {
   window.scrollTo(0, 0);
}

вот решение, которое я придумал. Я связал LocationStrategy с событиями маршрутизатора. Использование LocationStrategy для установки логического значения, чтобы знать, когда пользователь в настоящее время проходит через историю браузера. Таким образом, мне не нужно хранить кучу данных URL и y-scroll (что в любом случае не работает хорошо, так как все данные заменяются на основе URL). Это также решает крайний случай, когда пользователь решает удерживать кнопку Назад или вперед в браузере и переходит назад или вперед несколько раз страницы, а не только один.

P.S. Я тестировал только последнюю версию IE, Chrome, FireFox, Safari и Opera (по состоянию на этот пост).

надеюсь, что это помогает.

export class AppComponent implements OnInit {
  isPopState = false;

  constructor(private router: Router, private locStrat: LocationStrategy) { }

  ngOnInit(): void {
    this.locStrat.onPopState(() => {
      this.isPopState = true;
    });

    this.router.events.subscribe(event => {
      // Scroll to top if accessing a page, not via browser history stack
      if (event instanceof NavigationEnd && !this.isPopState) {
        window.scrollTo(0, 0);
        this.isPopState = false;
      }

      // Ensures that isPopState is reset
      if (event instanceof NavigationEnd) {
        this.isPopState = false;
      }
    });
  }
}

это решение основано на решении @FernandoEcheverria и @GuilhermeMeireles, но оно более сжато и работает с механизмами popstate, которые предоставляет угловой маршрутизатор. Это позволяет сохранять и восстанавливать уровень прокрутки нескольких последовательных переходов.

мы сохраняем позиции прокрутки для каждого состояния навигации на карте scrollLevels. Как только происходит событие popstate, идентификатор состояния, которое должно быть восстановлено, предоставляется угловым маршрутизатором: event.restoredState.navigationId. Это затем используется для получения последнего уровня прокрутки этого состояния от scrollLevels.

если для маршрута нет сохраненного уровня прокрутки, он будет прокручиваться вверх, как и следовало ожидать.

import { Component, OnInit } from '@angular/core';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';

@Component({
    selector: 'my-app',
    template: '<ng-content></ng-content>',
})
export class AppComponent implements OnInit {

  constructor(private router: Router) { }

  ngOnInit() {
    const scrollLevels: { [navigationId: number]: number } = {};
    let lastId = 0;
    let restoredId: number;

    this.router.events.subscribe((event: Event) => {

      if (event instanceof NavigationStart) {
        scrollLevels[lastId] = window.scrollY;
        lastId = event.id;
        restoredId = event.restoredState ? event.restoredState.navigationId : undefined;
      }

      if (event instanceof NavigationEnd) {
        if (restoredId) {
          // Optional: Wrap a timeout around the next line to wait for
          // the component to finish loading
          window.scrollTo(0, scrollLevels[restoredId] || 0);
        } else {
          window.scrollTo(0, 0);
        }
      }

    });
  }

}

Если вам нужно просто прокрутить страницу вверх, вы можете сделать это (не лучшее решение, но быстро)

document.getElementById('elementId').scrollTop = 0;

для iPhone / ios safari вы можете обернуть с помощью setTimeout

setTimeout(function(){
    window.scrollTo(0, 1);
}, 0);

@Fernando Echeverria отлично! но этот код не работает в хэш-маршрутизатор или маршрутизатор ленивый. потому что они не вызывают изменения местоположения. можете попробовать это:

private lastRouteUrl: string[] = []
  

ngOnInit(): void {
  this.router.events.subscribe((ev) => {
    const len = this.lastRouteUrl.length
    if (ev instanceof NavigationEnd) {
      this.lastRouteUrl.push(ev.url)
      if (len > 1 && ev.url === this.lastRouteUrl[len - 2]) {
        return
      }
      window.scrollTo(0, 0)
    }
  })
}

С помощью Router сам вызовет проблемы, которые вы не можете полностью преодолеть, чтобы поддерживать согласованный опыт браузера. На мой взгляд, лучший способ-просто использовать пользовательский directive и пусть это сбросить прокрутку на щелчок. Хорошая вещь об этом является то, что если вы находитесь на той же url как вы нажмете на, страница будет прокручиваться обратно в верхнюю часть, а также. Это согласуется с обычными веб-сайтами. Основной directive может выглядеть так:

import {Directive, HostListener} from '@angular/core';

@Directive({
    selector: '[linkToTop]'
})
export class LinkToTopDirective {

    @HostListener('click')
    onClick(): void {
        window.scrollTo(0, 0);
    }
}

С следующее использование:

<a routerLink="/" linkToTop></a>

этого будет достаточно для большинства случаев использования, но я могу представить себе несколько вопросов, которые могут возникают из этого:

  • не работает с universal из-за использования window
  • малая скорость воздействия на обнаружение изменений, потому что он запускается при каждом щелчке
  • невозможно отключить эту директиву

это на самом деле довольно легко преодолеть эти вопросы:

@Directive({
  selector: '[linkToTop]'
})
export class LinkToTopDirective implements OnInit, OnDestroy {

  @Input()
  set linkToTop(active: string | boolean) {
    this.active = typeof active === 'string' ? active.length === 0 : active;
  }

  private active: boolean = true;

  private onClick: EventListener = (event: MouseEvent) => {
    if (this.active) {
      window.scrollTo(0, 0);
    }
  };

  constructor(@Inject(PLATFORM_ID) private readonly platformId: Object,
              private readonly elementRef: ElementRef,
              private readonly ngZone: NgZone
  ) {}

  ngOnDestroy(): void {
    if (isPlatformBrowser(this.platformId)) {
      this.elementRef.nativeElement.removeEventListener('click', this.onClick, false);
    }
  }

  ngOnInit(): void {
    if (isPlatformBrowser(this.platformId)) {
      this.ngZone.runOutsideAngular(() => 
        this.elementRef.nativeElement.addEventListener('click', this.onClick, false)
      );
    }
  }
}

это учитывает большинство случаев использования, с тем же использованием, что и основной, с преимуществом включения / отключения его:

<a routerLink="/" linkToTop></a> <!-- always active -->
<a routerLink="/" [linkToTop]="isActive"> <!-- active when `isActive` is true -->

рекламные ролики, не читайте, Если вы не хотите рекламироваться

еще одно улучшение может быть сделано, чтобы проверить, поддерживает ли браузер passive событий. Это усложнит код немного больше, и немного неясно, если вы хотите реализовать все это в своем обычае директивы/шаблоны. Вот почему я написал немного библиотека который вы можете использовать для решения этих проблем. Чтобы иметь ту же функциональность, что и выше, и с добавленным passive событие, вы можете изменить свою директиву на это, если вы используете ng-event-options библиотека. Логика находится внутри click.pnb слушатель:

@Directive({
    selector: '[linkToTop]'
})
export class LinkToTopDirective {

    @Input()
    set linkToTop(active: string|boolean) {
        this.active = typeof active === 'string' ? active.length === 0 : active;
    }

    private active: boolean = true;

    @HostListener('click.pnb')
    onClick(): void {
      if (this.active) {
        window.scrollTo(0, 0);
      }        
    }
}

Привет, ребята, это работает для меня в угловой 4. Вы просто должны ссылаться на родителя для прокрутки на изменение маршрутизатора'

макет.деталь.мопс

.wrapper(#outlet="")
    router-outlet((activate)='routerActivate($event,outlet)')

макет.деталь.ТС

 public routerActivate(event,outlet){
    outlet.scrollTop = 0;
 }`

это работало для меня лучше всего для всех изменений навигации, включая хэш-навигацию

constructor(private route: ActivatedRoute) {}

ngOnInit() {
  this._sub = this.route.fragment.subscribe((hash: string) => {
    if (hash) {
      const cmp = document.getElementById(hash);
      if (cmp) {
        cmp.scrollIntoView();
      }
    } else {
      window.scrollTo(0, 0);
    }
  });
}

основная идея этого кода состоит в том, чтобы сохранить все посещенные URL-адреса вместе с соответствующими данными scrollY в массиве. Каждый раз, когда пользователь покидает страницу (NavigationStart) этот массив обновляется. Каждый раз, когда пользователь вводит новую страницу (NavigationEnd), мы решаем восстановить позицию Y или не в зависимости от того, как мы попадаем на эту страницу. Если ссылка на какой-то странице была использована, мы прокручиваем до 0. Если были использованы функции браузера назад/вперед, мы прокручиваем до y, сохраненного в нашем массиве. Извините за мой английский :)

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Location, PopStateEvent } from '@angular/common';
import { Router, Route, RouterLink, NavigationStart, NavigationEnd, 
    RouterEvent } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';

@Component({
  selector: 'my-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {

  private _subscription: Subscription;
  private _scrollHistory: { url: string, y: number }[] = [];
  private _useHistory = false;

  constructor(
    private _router: Router,
    private _location: Location) {
  }

  public ngOnInit() {

    this._subscription = this._router.events.subscribe((event: any) => 
    {
      if (event instanceof NavigationStart) {
        const currentUrl = (this._location.path() !== '') 
           this._location.path() : '/';
        const item = this._scrollHistory.find(x => x.url === currentUrl);
        if (item) {
          item.y = window.scrollY;
        } else {
          this._scrollHistory.push({ url: currentUrl, y: window.scrollY });
        }
        return;
      }
      if (event instanceof NavigationEnd) {
        if (this._useHistory) {
          this._useHistory = false;
          window.scrollTo(0, this._scrollHistory.find(x => x.url === 
          event.url).y);
        } else {
          window.scrollTo(0, 0);
        }
      }
    });

    this._subscription.add(this._location.subscribe((event: PopStateEvent) 
      => { this._useHistory = true;
    }));
  }

  public ngOnDestroy(): void {
    this._subscription.unsubscribe();
  }
}

начиная с углового 6.1, маршрутизатор обеспечивает конфигурации под названием scrollPositionRestoration, это предназначено для удовлетворения этого сценария.

imports: [
  RouterModule.forRoot(routes, {
    scrollPositionRestoration: 'enabled'
  }),
  ...
]

Comments

    Ничего не найдено.