Angular2使用Hashtag导航到页面锚点

157

我希望在我的Angular2页面上添加一些链接,当点击时,可以跳转到该页面内的特定位置,就像常规的标签一样。因此,这些链接可能类似于:

/users/123#userInfo
/users/123#userPhoto
/users/123#userLikes

我认为我不需要HashLocationStrategy,因为我对Angular2的常规方式感到满意,但是如果我直接添加<base href="/">,链接实际上会跳转到根目录,而不是同一页的其他位置。非常感谢您提供任何指导。

21个回答

182

更新

现在已经支持该功能

<a [routerLink]="['somepath']" fragment="Test">Jump to 'Test' anchor </a>

this._router.navigate( ['/somepath', id ], {fragment: 'test'});

将以下代码添加到您的组件中以实现滚动

  import {ActivatedRoute} from '@angular/router'; // <-- do not forget to import

  private fragment: string;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.fragment.subscribe(fragment => { this.fragment = fragment; });
  }

  ngAfterViewInit(): void {
    try {
      document.querySelector('#' + this.fragment).scrollIntoView();
    } catch (e) { }
  }

翻译

这是一个已知问题,可以在https://github.com/angular/angular/issues/6595上进行跟踪。


1
使用字符串定义一个变量(例如问题中的 123),假设路由路径需要一个参数,如 { path: 'users/:id', ....} - Günter Zöchbauer
2
如果您想要滚动到锚点,请参考此帖子:https://github.com/angular/angular/issues/6595 - pearpages
18
注意:这仍然无法滚动。 - Martín Coll
2
是的,它可以与setTimeout一起使用。如果我找到更好的解决方案,我会告诉你。 - Amr Ibrahim
3
对于那些难以滚动到类似数字的 ID 的人来说,请注意,01100不是有效的 CSS 选择器。你可能希望添加一个字母或其他内容来使其成为有效选择器。因此,你仍然可以将01作为片段传递,但 id需要像d01这样的东西,这样 document.querySelector('#d'+id) 就能匹配了。 - pop
显示剩余9条评论

89

很抱歉回答有点晚了。在Angular路由文档中有一个预定义的函数可以帮助我们使用哈希标记进行页面锚定,即 anchorScrolling:'enabled'

步骤1:首先在app.module.ts文件中导入RouterModule

imports:[ 
    BrowserModule, 
    FormsModule,
    RouterModule.forRoot(routes,{
      anchorScrolling: 'enabled'
    })
  ],

步骤二:前往HTML页面,创建导航并添加两个重要属性,如[routerLink]fragment,以匹配相应的Div ID

<ul>
    <li> <a [routerLink] = "['/']"  fragment="home"> Home </a></li>
    <li> <a [routerLink] = "['/']"  fragment="about"> About Us </a></li>
  <li> <a [routerLink] = "['/']"  fragment="contact"> Contact Us </a></li>
</ul>

步骤-3:- 通过将ID名称片段匹配来创建一个部分/ div:

<section id="home" class="home-section">
      <h2>  HOME SECTION </h2>
</section>

<section id="about" class="about-section">
        <h2>  ABOUT US SECTION </h2>
</section>

<section id="contact" class="contact-section">
        <h2>  CONTACT US SECTION </h2>
</section>

供参考,我通过创建一个小演示来帮助解决您的问题,以下是示例:

演示: https://routing-hashtag-page-anchors.stackblitz.io/


1
非常感谢您。代码简洁明了,而且还能正常运行! - Belmiris
4
是的,感谢您使用Angular 7自动滚动功能,只需在anchorScrolling选项下添加scrollPositionRestoration: 'enabled'即可 :) - Mickaël
2
这将哈希正确地附加到我的URL末尾,但它没有锚定到具有相同名称的div。我不确定我错过了什么。我按照上面的三个步骤进行了操作。 - oaklandrichie
@oaklandrichie:你能在这里分享jsfiddle / stackblitz的代码吗?我可以帮助你。 - Naheed Shareef
这个答案绝对应该被接受,效果很好! - Kiss Koppány
谢谢,伙计!这正是我一直在寻找的。 唯一的问题是当用户第二次点击相同的链接时(当片段已经存在于URL中)它就不起作用了。 但我已经成功找到了另一个路由选项的参数:onSameUrlNavigation: 'reload',它可以帮助解决这种情况。(与anchorScrolling: 'enabled'一起使用此参数) - Aviw

55
尽管Günter的回答是正确的,但它并未涉及"跳转至"锚点标签部分。
因此,除了:
<a [routerLink]="['somepath']" fragment="Test">Jump to 'Test' anchor </a>
this._router.navigate( ['/somepath', id ], {fragment: 'test'});

在需要“跳转到”行为的组件(父组件)中,添加以下代码:

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

class MyAppComponent {
  constructor(router: Router) {

    router.events.subscribe(s => {
      if (s instanceof NavigationEnd) {
        const tree = router.parseUrl(router.url);
        if (tree.fragment) {
          const element = document.querySelector("#" + tree.fragment);
          if (element) { element.scrollIntoView(true); }
        }
      }
    });

  }
}

请注意,这只是一个解决方法!请关注此 GitHub 问题以获取未来的更新。感谢Victor Savkin提供的解决方案!


你好,我正在制作一个FAQ页面,在页面顶部定义的问题列表中,用户可以通过点击问题跳转到答案。因此,当用户跳转到锚点时,他已经在当前页面上了。如果我想让routerLink属性起作用,我必须将"['../faq']"作为值,否则它会尝试跳转到/faq/faq/#anchor,而不是/faq/#anchor。这样做是正确的吗?还是有更优雅的方法来引用routerlink中的当前页面?另外,document.querySelector("#" + tree.fragment);给我返回了一个无效选择器错误。你确定这是正确的吗?谢谢。 - Maurice
2
当您再次单击相同的链接时,它不起作用。是否有人使用户单击相同的锚链接<a [routerLink]="['/faq']" fragment="section6">工作? - Junior Mayhé
@JuniorM 你解决了这个问题吗?我也遇到了同样的问题。 - The Muffin Man
@Muffin,可以尝试类似这样的内容:https://github.com/juniormayhe/Mailing/blob/master/Mailing.SPA/src/app/faq/faq.component.ts - Junior Mayhé
1
这需要更多的曝光。在我看来,这是一个更好的答案。大多数人都会想要跳转到这个部分。 - iamtravisw

27

有点晚了,但这是我找到的一个可行的答案:

<a [routerLink]="['/path']" fragment="test" (click)="onAnchorClick()">Anchor</a>

并且在组件中:

constructor( private route: ActivatedRoute, private router: Router ) {}

  onAnchorClick ( ) {
    this.route.fragment.subscribe ( f => {
      const element = document.querySelector ( "#" + f )
      if ( element ) element.scrollIntoView ( element )
    });
  }
以上情况在您着陆带有锚点的页面时不会自动滚动到视图,因此我在我的ngInit中使用了上述解决方案,以便它也可以处理这种情况:
ngOnInit() {
    this.router.events.subscribe(s => {
      if (s instanceof NavigationEnd) {
        const tree = this.router.parseUrl(this.router.url);
        if (tree.fragment) {
          const element = document.querySelector("#" + tree.fragment);
          if (element) { element.scrollIntoView(element); }
        }
      }
    });
  }

确保在你的组件开始时导入Router、ActivatedRoute和NavigationEnd,然后一切都应该顺利进行。

来源


4
可以!如果你想在当前页面内导航,请使用[routerLink]="['.']"。 - Raoul
1
你能再解释一下吗?这部分 document.querySelector("#" + f) 给我报错了,因为它需要一个选择器,而不是一个字符串。 - Maurice
1
@Maurice,对我来说这个方法可行:element.scrollIntoView()(不需要将 element 传递给函数)。如果想要平滑滚动,请使用以下代码:element.scrollIntoView({block: "end", behavior: "smooth"}) - Mr. B.
Intellisense在这里显示,在onAnchorClick()中,我们必须传递一个布尔值给scrollIntoView: if (element) { element.scrollIntoView(true); }。现在我可以点击两次相同的链接并且滚动起作用了。 - Junior Mayhé

23

之前的答案都对我没用。最后一招,我尝试了在我的模板中:

<a (click)="onClick()">From Here</a>
<div id='foobar'>To Here</div>

在我的 .ts 文件中使用以下代码:

onClick(){
    let x = document.querySelector("#foobar");
    if (x){
        x.scrollIntoView();
    }
}

对于内部链接,它按预期工作。实际上,这并不使用锚标签,因此完全不会触及URL。


2
简单易懂 - WasiF
x.scrollIntoView({ behavior: 'smooth', }) 的翻译:使用平滑滚动效果更佳 ;) - Nevada

10

如果在URL中添加这些元素ID不重要,您应该考虑查看此链接:

Angular 2 - 在当前页面上使用锚点链接到元素

// html
// add (click) event on element
<a (click)="scroll({{any-element-id}})">Scroll</a>

// in ts file, do this
scroll(sectionId) {
let element = document.getElementById(sectionId);

  if(element) {
    element.scrollIntoView(); // scroll to a particular element
  }
 }


这对我来说是最好的解决方案。如果您正在使用路由器,请将路由器链接添加为“./”以保留,例如<a [routerLink]="['./']" (click)="scroll('ref1')">1</a>。 - David Wilton
太棒了。干杯。 - Ramu N
工作得很好,谢谢! - undefined

8
在 HTML 文件中:
<a [fragment]="test1" [routerLink]="['./']">Go to Test 1 section</a>

<section id="test1">...</section>
<section id="test2">...</section>

在ts文件中:

export class PageComponent implements AfterViewInit, OnDestroy {

  private destroy$$ = new Subject();
  private fragment$$ = new BehaviorSubject<string | null>(null);
  private fragment$ = this.fragment$$.asObservable();

  constructor(private route: ActivatedRoute) {
    this.route.fragment.pipe(takeUntil(this.destroy$$)).subscribe(fragment => {
      this.fragment$$.next(fragment);
    });
  }

  public ngAfterViewInit(): void {
    this.fragment$.pipe(takeUntil(this.destroy$$)).subscribe(fragment => {
      if (!!fragment) {
        document.querySelector('#' + fragment).scrollIntoView();
      }
    });
  }

  public ngOnDestroy(): void {
    this.destroy$$.next();
    this.destroy$$.complete();
  }
}

querySelector 可以轻松地放入名为 scrollTo 的指令中。URL 将会是 <a scrollTo="test1" [routerLink]="['./']"> 前往测试 1 部分</a>。 - JWP
运作得很顺利 - undefined

8

app-routing.module.ts中使用此代码:

@NgModule({
  imports: [RouterModule.forRoot(routes, {
    useHash: true,
    scrollPositionRestoration: 'enabled',
    anchorScrolling: 'enabled',
    scrollOffset: [0, 64]
  })],
  exports: [RouterModule]
})

这将出现在你的HTML中:
<a href="#/users/123#userInfo">

7

之前提供的解决方案对我没有用,但下面这个就起作用了:

首先,在ngAfterViewChecked()中为MyAppComponent准备自动滚动…

import { Component, OnInit, AfterViewChecked } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';

@Component( {
   [...]
} )
export class MyAppComponent implements OnInit, AfterViewChecked {

  private scrollExecuted: boolean = false;

  constructor( private activatedRoute: ActivatedRoute ) {}

  ngAfterViewChecked() {

    if ( !this.scrollExecuted ) {
      let routeFragmentSubscription: Subscription;

      // Automatic scroll
      routeFragmentSubscription =
        this.activatedRoute.fragment
          .subscribe( fragment => {
            if ( fragment ) {
              let element = document.getElementById( fragment );
              if ( element ) {
                element.scrollIntoView();

                this.scrollExecuted = true;

                // Free resources
                setTimeout(
                  () => {
                    console.log( 'routeFragmentSubscription unsubscribe' );
                    routeFragmentSubscription.unsubscribe();
                }, 1000 );

              }
            }
          } );
    }

  }

}

然后,导航到my-app-route并发送prodID哈希标签。

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

@Component( {
   [...]
} )
export class MyOtherComponent {

  constructor( private router: Router ) {}

  gotoHashtag( prodID: string ) {
    this.router.navigate( [ '/my-app-route' ], { fragment: prodID } );
  }

}

没有评论或解释就进行负面评价...这不是一个好的做法... - JavierFuentes
这对我有用!但为什么要使用ngAfterViewChecked而不是ngInit呢? - Antoine Boisier-Michaud
感谢@AntoineBoisier-Michaud的积极反馈。ngInit在我项目中并不总是触发,所以我需要使用ngAfterViewChecked。你能给这个解决方案点赞吗?谢谢。 - JavierFuentes

6

在 Angular 版本小于 6.1 的情况下,其他答案都可以使用。但如果您拥有最新版本,则不需要执行这些丑陋的操作,因为 Angular 已经修复了该问题。

这里是问题链接

所有你需要做的就是在 RouterModule.forRoot 方法的第二个参数选项中设置scrollOffset

@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      scrollPositionRestoration: 'enabled',
      anchorScrolling: 'enabled',
      scrollOffset: [0, 64] // [x, y]
    })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule {}

2
这对外部链接有效吗?比如我从另一个网站点击www.abc.com#sectionToScrollTo。 - Sushmit Sagar
锚点滚动不起作用,如果您在使用 *ngIf 时过于频繁,因为它会提前跳转 :-( - Jojo.Lechelt
我遇到的唯一问题是时间控制——在某些元素的样式尚未完全渲染之前,它往往会跳转到锚点,导致定位偏差。如果您能添加延迟,那就太好了 :) - Charly

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接