Angular 2 เลื่อนไปด้านบนสุดในการเปลี่ยนเส้นทาง


295

ในแอพ Angular 2 ของฉันเมื่อฉันเลื่อนหน้าลงและคลิกลิงก์ที่ด้านล่างของหน้ามันจะเปลี่ยนเส้นทางและพาฉันไปหน้าถัดไป แต่มันจะไม่เลื่อนไปที่ด้านบนของหน้า ดังนั้นหากหน้าแรกมีความยาวและหน้าที่ 2 มีเนื้อหาเพียงเล็กน้อยก็จะสร้างความประทับใจว่าหน้า 2 ไม่มีเนื้อหา เนื่องจากเนื้อหาจะปรากฏต่อเมื่อผู้ใช้เลื่อนไปที่ด้านบนของหน้า

ฉันสามารถเลื่อนหน้าต่างไปที่ด้านบนของหน้าเว็บใน ngInit ของส่วนประกอบ แต่มีวิธีแก้ปัญหาที่ดีกว่านี้ที่สามารถจัดการเส้นทางทั้งหมดในแอปของฉันโดยอัตโนมัติหรือไม่?


20
เนื่องจาก Angular 6.1 เราสามารถใช้ {scrollPositionRestoration: 'enabled'} บนโมดูลที่โหลดอย่างกระตือรือร้นหรือเพียงแค่ใน app.module และมันจะถูกนำไปใช้กับทุกเส้นทาง RouterModule.forRoot(appRoutes, { scrollPositionRestoration: 'enabled' })
Manwal

Muito obrigado sua solução funcionou perfeitamente para mim :)
Raphael Godoi

ไม่มีใครพูดถึงการมุ่งเน้น? มันสำคัญกว่าที่ผ่านมาเพื่อรองรับการเข้าถึงผู้อ่าน / หน้าจออย่างถูกต้องและถ้าคุณเพียงเลื่อนไปด้านบนโดยไม่พิจารณาโฟกัสแล้วกดปุ่มแท็บถัดไปสามารถข้ามไปที่ด้านล่างของหน้าจอ
Simon_Weaver

คำตอบ:


385

คุณสามารถลงทะเบียนฟังการเปลี่ยนแปลงเส้นทางในองค์ประกอบหลักของคุณและเลื่อนไปด้านบนเมื่อมีการเปลี่ยนแปลงเส้นทาง

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)
        });
    }
}

11
window.scrollTo(0, 0)มีความกระชับมากกว่าdocument.body.scrollTop = 0;และ IMO ที่อ่านได้ง่ายขึ้น
Mark E. Haase

10
ไม่มีใครสังเกตเห็นว่าแม้หลังจากใช้งานไปแล้วปัญหายังคงอยู่ในเบราว์เซอร์ซาฟารีของ Iphone ความคิดใด ๆ
rgk

1
@ mehaase ดูเหมือนว่าคำตอบของคุณดีที่สุด window.body.scrollTop ไม่ทำงานสำหรับฉันบน Firefox เดสก์ท็อป ขอบคุณมาก!
KCarnaille

3
สิ่งนี้ใช้งานได้สำหรับฉัน แต่จะหยุดการทำงานของปุ่ม "ย้อนกลับ" เป็นค่าเริ่มต้น การย้อนกลับควรจำตำแหน่งการเลื่อนก่อนหน้า
JackKalish

6
สิ่งนี้ได้ผล !! แม้ว่าฉันจะเพิ่ม$("body").animate({ scrollTop: 0 }, 1000);มากกว่าwindow.scrollTo(0, 0)การเลื่อนดูอย่างราบรื่นไปยังด้านบน
Manubhargav

360

เชิงมุม 6.1 และใหม่กว่า :

Angular 6.1 (เผยแพร่เมื่อวันที่ 2018-07-25) เพิ่มการสนับสนุนในตัวเพื่อจัดการกับปัญหานี้ผ่านคุณสมบัติที่เรียกว่า "เราเตอร์เลื่อนตำแหน่งคืนค่า" ดังที่อธิบายไว้ในบล็อกอย่างเป็นทางการของAngularคุณเพียงแค่เปิดใช้งานสิ่งนี้ในการกำหนดค่าเราเตอร์ดังนี้:

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

นอกจากนี้บล็อกระบุว่า "คาดว่าสิ่งนี้จะกลายเป็นค่าเริ่มต้นในการเปิดตัวครั้งใหญ่ในอนาคต" จนถึงตอนนี้ก็ไม่ได้เกิดขึ้น (จาก Angular 8.2) แต่ในที่สุดคุณไม่จำเป็นต้องทำอะไรเลยในรหัสของคุณและสิ่งนี้จะทำงานได้อย่างถูกต้องทันที

ท่านสามารถดูรายละเอียดเพิ่มเติมเกี่ยวกับคุณลักษณะนี้และวิธีการกำหนดพฤติกรรมนี้ในเอกสารอย่างเป็นทางการ

Angular 6.0 และรุ่นก่อนหน้า :

ในขณะที่ @ GuilhermeMeireles คำตอบที่ยอดเยี่ยมช่วยแก้ไขปัญหาเดิม แต่ก็นำเสนอใหม่โดยทำลายพฤติกรรมปกติที่คุณคาดหวังเมื่อคุณนำทางกลับหรือส่งต่อ (ด้วยปุ่มเบราว์เซอร์หรือผ่านทางรหัสใน) พฤติกรรมที่คาดหวังคือเมื่อคุณนำทางกลับไปที่หน้าเว็บมันควรจะเลื่อนลงไปยังตำแหน่งเดิมเมื่อคุณคลิกที่ลิงค์ แต่การเลื่อนไปด้านบนเมื่อมาถึงหน้าทุกครั้งทำให้เกิดความคาดหวังนี้อย่างชัดเจน

โค้ดด้านล่างนี้ขยายตรรกะเพื่อตรวจจับการนำทางประเภทนี้โดยการสมัครสมาชิกลำดับ PopStateEvent และข้ามตรรกะแบบเลื่อนขึ้น - ลง - บน - บนหากหน้าใหม่ที่เพิ่งมาถึงเป็นผลมาจากเหตุการณ์ดังกล่าว

หากหน้าเว็บที่คุณนำทางกลับมานั้นยาวพอที่จะครอบคลุมทั้งวิวพอร์ตตำแหน่งการเลื่อนจะถูกเรียกคืนโดยอัตโนมัติ แต่เนื่องจาก @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);
            }
        });
    }
}

2
สิ่งนี้ควรไปในส่วนประกอบของแอปโดยตรงหรือในส่วนประกอบเดียวที่ใช้ในแอพ (และแชร์โดยแอพทั้งหมด) ตัวอย่างเช่นฉันได้รวมไว้ในองค์ประกอบแถบนำทางด้านบน คุณไม่ควรรวมอยู่ในส่วนประกอบทั้งหมดของคุณ
Fernando Echeverria

3
คุณสามารถทำเช่นนั้นได้และจะทำให้รหัสนั้นเข้ากันได้กับแพลตฟอร์มอื่นที่ไม่ใช่เบราว์เซอร์มากขึ้น ดูstackoverflow.com/q/34177221/2858481สำหรับรายละเอียดการใช้งาน
Fernando Echeverria

3
หากคุณคลิกปุ่มย้อนกลับ / ไปข้างหน้าค้างไว้ในเบราว์เซอร์สมัยใหม่เมนูจะปรากฏขึ้นเพื่อให้คุณสามารถนำทางไปยังตำแหน่งอื่นนอกเหนือจากที่ผ่านมา / ถัดไปทันที วิธีนี้จะแตกถ้าคุณทำเช่นนั้น เป็นกรณีขอบสำหรับส่วนใหญ่ แต่มูลค่าการกล่าวขวัญ
adamdport


1
มีวิธีการเปิดใช้งาน "การเลื่อนตำแหน่งการเลื่อนตำแหน่งของเราเตอร์" สำหรับองค์ประกอบที่ซ้อนกันหรือbodyไม่
เปิดรับ

61

จาก Angular 6.1 คุณสามารถหลีกเลี่ยงความยุ่งยากและผ่านextraOptionsไปยังRouterModule.forRoot()พารามิเตอร์ที่สองของคุณและสามารถระบุscrollPositionRestoration: enabledให้ Angular เลื่อนขึ้นไปด้านบนเมื่อใดก็ตามที่เส้นทางเปลี่ยนไป

โดยค่าเริ่มต้นคุณจะพบสิ่งนี้ในapp-routing.module.ts:

const routes: Routes = [
  {
    path: '...'
    component: ...
  },
  ...
];

@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      scrollPositionRestoration: 'enabled', // Add options right here
    })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule { }

เอกสารอย่างเป็นทางการเชิงมุม


3
แม้ว่าคำตอบข้างต้นจะเป็นคำอธิบายที่มากกว่าฉันชอบคำตอบนี้บอกฉันว่าตรงนี้ต้องไปที่ไหน
ryanovas

32

คุณสามารถเขียนได้ง่ายขึ้นโดยใช้ประโยชน์จากfilterวิธีการที่สังเกตได้:

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

หากคุณมีปัญหาในการเลื่อนไปที่ด้านบนเมื่อใช้ Angular Material 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);
});

นอกจากนี้ Angular CDK v6.x ยังมีแพ็คเกจเลื่อนที่อาจช่วยจัดการเลื่อนได้ด้วย


2
ที่ดี! สำหรับฉันที่ทำงาน -document.querySelector('.mat-sidenav-content .content-div').scrollTop = 0;
Amir Tugi

Nice one fellas ... ที่ mtpultz & @AmirTugi การจัดการกับเรื่องนี้ในตอนนี้และคุณจับมันให้ฉันไชโย! อาจจะจบลงด้วยการนำทางด้านข้างของตัวเองเนื่องจากวัสดุ 2 ไม่เล่นได้ดีเมื่อ md-toolbar เป็นตำแหน่ง: คงที่ (ที่ด้านบน) ถ้าพวกคุณไม่มีไอเดีย .... ????
Tim Harker

อาจพบคำตอบของฉัน ... stackoverflow.com/a/40396105/3389046
Tim Harker

16

หากคุณมีการเรนเดอร์ฝั่งเซิร์ฟเวอร์คุณควรระวังไม่ให้รันโค้ดโดยใช้windowsบนเซิร์ฟเวอร์โดยที่ตัวแปรนั้นไม่มีอยู่ มันจะส่งผลในการทำลายรหัส

export class AppComponent implements OnInit {
  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')

1
คุณไม่จำเป็นต้องฉีดPLATFORM_IDในconstructorและให้ค่านี้เป็นพารามิเตอร์ในเดisPlatformBrowserวิธี?
Poul Kruijt

1
@PierreDuc ใช่คำตอบนั้นผิด isPlatformBrowserเป็นฟังก์ชั่นและจะเป็นจริงเสมอ ฉันแก้ไขมันแล้ว
Lazar Ljubenović

ขอบคุณ! ถูกต้องแล้ว! เพิ่งยืนยัน API: github.com/angular/angular/blob/…
Raptor

13

ทำได้ง่ายๆด้วยการคลิก

ใน html คอมโพเนนต์หลักของคุณทำการอ้างอิง #scrollContainer

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

ในองค์ประกอบหลัก. ts

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

องค์ประกอบที่จะเลื่อนอาจไม่ได้อยู่ในscrollContainerโหนดแรกคุณอาจต้องขุดบิตในวัตถุสำหรับฉันสิ่งที่มันได้ผลจริงๆscrollContainer .scrollable._elementRef.nativeElement.scrollTop = 0
Byron Lopez

13

เมื่อไม่นานมานี้ Angular ได้แนะนำคุณสมบัติใหม่ภายในโมดูลการกำหนดเส้นทางเชิงมุมทำให้มีการเปลี่ยนแปลงดังนี้

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

12

คำตอบที่ดีที่สุดอยู่ในการสนทนา Angular GitHub ( การเปลี่ยนเส้นทางไม่ได้เลื่อนไปด้านบนในหน้าใหม่ )

บางทีคุณอาจต้องการที่จะไปด้านบนเฉพาะในการเปลี่ยนแปลงเราเตอร์รูท (ไม่ใช่ในเด็กเพราะคุณสามารถโหลดเส้นทางที่มีการโหลดขี้เกียจใน fe a tabset)

app.component.html

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

app.component.ts

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

สินเชื่อเต็มไปยังJoniJnm ( โพสต์ต้นฉบับ )




4

หากคุณต้องการเพียงเลื่อนหน้าขึ้นไปด้านบนคุณสามารถทำได้ (ไม่ใช่วิธีที่ดีที่สุด แต่รวดเร็ว)

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

4

นี่เป็นวิธีแก้ปัญหาที่ฉันคิดขึ้นมา ฉันจับคู่ LocationStrategy กับเหตุการณ์ Router การใช้ LocationStrategy เพื่อตั้งค่าบูลีนให้รู้ว่าเมื่อใดที่ผู้ใช้กำลังสำรวจประวัติเบราว์เซอร์ ด้วยวิธีนี้ฉันไม่ต้องเก็บ URL จำนวนมากและข้อมูล y-scroll (ซึ่งทำงานได้ไม่ดีนักเนื่องจากข้อมูลแต่ละอันจะถูกแทนที่ด้วย URL) วิธีนี้จะช่วยแก้ปัญหากรณีขอบเมื่อผู้ใช้ตัดสินใจที่จะกดปุ่มย้อนกลับหรือไปข้างหน้าบนเบราว์เซอร์และย้อนกลับหรือส่งต่อหลาย ๆ หน้าแทนที่จะเป็นเพียงหน้าเดียว

PS ฉันทดสอบเฉพาะใน 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;
      }
    });
  }
}

4

โซลูชันนี้ใช้วิธีแก้ปัญหาของ @ FernandoEcheverria และ @ GuilhermeMeireles แต่จะกระชับและทำงานกับกลไกแบบป๊อปสแตทที่ Angular Router ให้ สิ่งนี้ช่วยให้สามารถจัดเก็บและกู้คืนระดับการเลื่อนของการนำทางต่อเนื่องหลายรายการ

scrollLevelsเราเก็บตำแหน่งเลื่อนแต่ละรัฐนำทางในแผนที่ เมื่อมีเหตุการณ์ popstate, ID ของรัฐที่เกี่ยวกับการถูกเรียกคืนเป็นที่จัดทำโดยเชิงมุม Router 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);
        }
      }

    });
  }

}

น่ากลัว ฉันต้องสร้างเวอร์ชันที่กำหนดเองเล็กน้อยเพื่อเลื่อน div แทนที่จะเป็น window แต่มันใช้งานได้ หนึ่งความแตกต่างที่สำคัญคือVSscrollTop scrollY
BBaysinger

4

นอกเหนือจากคำตอบที่สมบูรณ์แบบโดย@Guilherme Meirelesดังที่แสดงด้านล่างคุณสามารถปรับแต่งการใช้งานของคุณโดยการเพิ่มการเลื่อนอย่างราบรื่นดังที่แสดงด้านล่าง

 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)
            });
        }
    }

จากนั้นเพิ่มตัวอย่างข้อมูลด้านล่าง

 html {
      scroll-behavior: smooth;
    }

กับสไตล์ของคุณ


1

สำหรับ iphone / ios safari คุณสามารถใช้ setTimeout

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

ในกรณีของฉันมันยังจำเป็นต้องตั้งค่าองค์ประกอบการตัดหน้า css เป็น; height: 100vh + 1px;
tubbsy

1

สวัสดีพวกนี้ใช้ได้สำหรับฉันในเชิงมุม 4 คุณเพียงแค่ต้องอ้างอิงผู้ปกครองเพื่อเลื่อนการเปลี่ยนแปลงเราเตอร์ `

layout.component.pug

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

layout.component.ts

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

1
ให้อภัยความเกียจคร้านของฉันโดยไม่รบกวนเรียนรู้ปั๊ก แต่คุณสามารถแปลเป็น HTML ได้ไหม
CodyBugstein

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)
    }
  })
}


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);
      }        
    }
}

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);
    }
  });
}

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();
  }
}

0

window.scrollTo()ไม่ทำงานสำหรับฉันใน Angular 5 ดังนั้นฉันจึงเคยdocument.body.scrollTopชอบ

this.router.events.subscribe((evt) => {
   if (evt instanceof NavigationEnd) {
      document.body.scrollTop = 0;
   }
});

0

หากคุณกำลังโหลดส่วนประกอบต่าง ๆ ด้วยเส้นทางเดียวกันคุณสามารถใช้ ViewportScroller เพื่อให้ได้สิ่งเดียวกัน

import { ViewportScroller } from '@angular/common';

constructor(private viewportScroller: ViewportScroller) {}

this.viewportScroller.scrollToPosition([0, 0]);

0

เลื่อนหน้าต่างด้านบน
ทั้ง window.pageYOffset และ document.documentElement.scrollTop ส่งคืนผลลัพธ์เดียวกันในทุกกรณี window.pageYOffset ไม่รองรับ IE 9

app.component.ts

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

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  isShow: boolean;
  topPosToStartShowing = 100;

  @HostListener('window:scroll')
  checkScroll() {

    const scrollPosition = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;

    console.log('[scroll]', scrollPosition);

    if (scrollPosition >= this.topPosToStartShowing) {
      this.isShow = true;
    } else {
      this.isShow = false;
    }
  }

  gotoTop() {
    window.scroll({ 
      top: 0, 
      left: 10, 
      behavior: 'smooth' 
    });
  }
}

app.component.html

<style>
  p {
  font-family: Lato;
}

button {
  position: fixed;
  bottom: 5px;
  right: 5px;
  font-size: 20px;
  text-align: center;
  border-radius: 5px;
  outline: none;
}
  </style>
<p>
  Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>
<p>
  Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>
<p>
  Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>
<p>
  Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>
<p>
  Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>
<p>
  Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>
<p>
  Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>
<p>
  Lorem ipsum dolor sit, amet consectetur adipisicing elit. Minus, repudiandae quia. Veniam amet fuga, eveniet velit ipsa repudiandae nemo? Sit dolorem itaque laudantium dignissimos, rerum maiores nihil ad voluptates nostrum.
</p>

<button *ngIf="isShow" (click)="gotoTop()">👆</button>
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.