带有Hashtag的Angular2路由到页面锚点


120

我希望在Angular2页面上添加一些链接,单击这些链接将跳到该页面内的特定位置,就像普通的标签一样。因此链接将类似于

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

等等

我认为我不需要HashLocationStrategy,因为我对正常的Angular2方式很满意,但是如果直接添加,链接实际上将跳转到根目录,而不是在同一页上的某个地方。任何方向表示赞赏,谢谢。

Answers:


130

更新资料

现在支持

<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
@invot用字符串(例如什么变量123是在问题)假设路由路径期望的参数等{ path: 'users/:id', ....}
君特Zöchbauer

2
如果要滚动到锚点,请参见以下文章:github.com/angular/angular/issues/6595
Pere Pages

12
注:这还不滚动
马丁科尔

2
是的,它可以与setTimeout一起使用,如果我发现更好的解决方案,我会让你知道
Amr Ibrahim

1
对于那些挣扎滚动到数字般的ID,记住,01或者100没有有效的CSS选择器。您可能需要添加字母或其他内容以使其成为有效的选择器。因此,您仍然会01像片段一样通过,但是该对象id必须是类似的东西d01,然后document.querySelector('#d'+id)才能匹配。
流行

52

尽管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 /#anchor的/ faq / faq /#anchor。这是正确的方法还是在routerlink中引用当前页面的方法更优雅?另外,document.querySelector("#" + tree.fragment);给我一个无效的选择器错误。您确定这是正确的吗?谢谢
Maurice

2
当您再次单击相同的链接时,它将不起作用。如果用户单击相同的锚点链接,有人能做这项工作<a [routerLink]="['/faq']" fragment="section6">吗?
少年梅耶

@JuniorM你有没有解决这个问题?我遇到了同样的问题。
松饼人


1
这需要更多的曝光。这是IMO的一个更好的答案。大多数人都想跳到该部分。
iamtravisw

44

对不起,回答太晚了;Angular Routing文档中有一个预定义的功能,该功能可帮助我们使用带有标签的路由将其定位到页面锚,即 anchorScrolling:'enabled'

第1步: -首先导入RouterModule在app.module.ts文件: -

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

步骤2:-转到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名称片段匹配来创建一个section / 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/


非常感谢。干净,简洁且有效!
Belmiris

2
是的,谢谢,对于与角7页负载自动scoll,只需要添加scrollPositionRestoration: 'enabled',下anchorScrolling选项:)
的Mickaël

这会将散列正确地附加到我的网址末尾,但不会锚定到具有相同名称的div。我不确定我缺少什么。我遵循了上面的三个步骤。
Oaklandrichie

@oaklandrichie:可以在这里共享jsfiddle / stackblitz代码。我可以帮你
NAHEED谢里夫

这个答案肯定应该被接受,就像魅力!
亲吻科潘尼

25

有点晚了,但是我发现这是可行的答案:

<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 )给我一个错误,因为它需要选择器而不是字符串。
莫里斯

1
@Maurice对我来说有效:(element.scrollIntoView()不传递element给函数)。要使其平滑,请使用:element.scrollIntoView({block: "end", behavior: "smooth"})
B.先生

智能感知在这里显示,内onAnchorClick(),我们必须通过一个布尔值,scrollIntoView: if (element) { element.scrollIntoView(true); }。现在,我可以在同一链接上单击两次并滚动作品
JuniorMayhé18年

18

以前的答案都没有对我有用。经过最后的努力,我尝试了模板:

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

在我的.ts中:

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

并且它可以为内部链接按预期工作。这实际上并没有使用锚标记,因此它根本不会触及该URL。


1
简单易用
WasiF

6

上面的解决方案对我不起作用...这个做到了:

首先,准备MyAppComponentngAfterViewChecked()中自动滚动...

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

}

没有评论或解释的
不赞成

这对我有用!为什么要使用ngAfterViewChecked而不是ngInit?
Antoine Boisier-Michaud

感谢@ AntoineBoisier-Michaud的积极反馈。ngInit并不总是会在我的项目中触发... ngAfterViewChecked会执行。你能支持这个解决方案吗?谢谢。
JavierFuentes

6

所有其他答案将适用于Angular版本<6.1。但是,如果您拥有最新版本,则无需进行这些丑陋的修改,因为Angular已解决了该问题。

这是发布的链接

您需要做的就是scrollOffset使用RouterModule.forRootmethod 的第二个参数设置。

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

2
这对外部链接有用吗?从另一个网站说,我点击www.abc.com#
sectionToScrollTo

如果您广泛使用* ngIf,anchorScrolling不起作用,因为它会跳到更早的时间:-(
Jojo.Lechelt

我唯一遇到的问题是时间安排-在某些元素的样式完全渲染之前,它倾向于跳到锚点,从而导致定位不正确。如果您可以添加延迟,那就太好了:)
Charly

5

将其用于路由器模块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">

4

在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轻松放入名为scrolllTo的指令中。网址为<a scrollTo="test1" [routerLink]="['./']">转到“测试1”部分</a>
约翰·彼得斯,

3

由于fragment属性仍然不提供锚点滚动,因此此解决方法对我有用:

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

3

加上Kalyoyan的答案,此订阅已绑定到路由器,并且将一直存在直到页面完全刷新为止。在组件中订阅路由器事件时,请确保在ngOnDestroy中退订:

import { OnDestroy } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { Subscription } from "rxjs/Rx";

class MyAppComponent implements OnDestroy {

  private subscription: Subscription;

  constructor(router: Router) {
    this.subscription = 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(element); }
        }
      }
    });
  }

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

我认为路由事件的订阅会自动删除。

3

我只是在自己的网站上进行此工作,所以我认为值得在这里发布我的解决方案。

<a [routerLink]="baseUrlGoesHere" fragment="nameOfYourAnchorGoesHere">Link Text!</a>

<a name="nameOfYourAnchorGoesHere"></a>
<div>They're trying to anchor to me!</div>

然后在组件中,确保包括以下内容:

 import { ActivatedRoute } from '@angular/router';

 constructor(private route: ActivatedRoute) { 
     this.route.fragment.subscribe ( f => {
         const element = document.querySelector ( "#" + f )
         if ( element ) element.scrollIntoView ( element )
     });
 }

我认为最好写公正element.scrollIntoView()element.scrollIntoView(true)。您的版本无法为我编译(可能是因为strictNullChecks?)。
David L.

3

阅读完所有解决方案后,我寻找了一个组件,该组件完全符合原始问题的要求:滚动到锚定链接。https://www.npmjs.com/package/ng2-scroll-to

安装它时,您将使用如下语法:

// app.awesome.component.ts
@Component({
   ...
   template: `...
        <a scrollTo href="#main-section">Scroll to main section</a>
        <button scrollTo scrollTargetSelector="#test-section">Scroll to test section</a>
        <button scrollTo scrollableElementSelector="#container" scrollYTarget="0">Go top</a>
        <!-- Further content here -->
        <div id="container">
            <section id="main-section">Bla bla bla</section>
            <section id="test-section">Bla bla bla</section>
        <div>
   ...`,
})
export class AwesomeComponent {
}

它对我来说真的很好。


使用轮子,不要再发明了;)
Yogen Rai

您是否看过该组件背后的代码?看起来非常脆弱-该项目还有14个未解决的问题-其中包括诸如element不存在,目标为null,无法滚动到element,浏览器支持问题之类的问题。
德雷奈

当您在父级组件中有子级(子级具有锚定的实体和/或锚定名称)时不起作用,它只是刷新页面
Sasha Bond

3

一种适用于没有任何查询参数的页面的简单解决方案是浏览器后退/前进,路由器和深度链接兼容。

<a (click)="jumpToId('anchor1')">Go To Anchor 1</a>


ngOnInit() {

    // If your page is dynamic
    this.yourService.getWhatever()
        .then(
            data => {
            this.componentData = data;
            setTimeout(() => this.jumpToId( window.location.hash.substr(1) ), 100);
        }
    );

    // If your page is static
    // this.jumpToId( window.location.hash.substr(1) )
}

jumpToId( fragment ) {

    // Use the browser to navigate
    window.location.hash = fragment;

    // But also scroll when routing / deep-linking to dynamic page
    // or re-clicking same anchor
    if (fragment) {
        const element = document.querySelector('#' + fragment);
        if (element) element.scrollIntoView();
    }
}

超时只是为了让页面加载受* ngIf“保护”的任何动态数据。更改路线时,也可以将其滚动到页面顶部-仅提供默认的顶部锚标签。


2

如果将那些元素ID附加到url上没有关系,则应考虑查看以下链接:

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


1

这是引用JavierFuentes答案的另一个解决方法:

<a [routerLink]="['self-route', id]" fragment="some-element" (click)="gotoHashtag('some-element')">Jump to Element</a>

在脚本中:

import {ActivatedRoute} from "@angular/router";
import {Subscription} from "rxjs/Subscription";

export class Links {
    private scrollExecuted: boolean = false;

    constructor(private route: ActivatedRoute) {} 

    ngAfterViewChecked() {
            if (!this.scrollExecuted) {
              let routeFragmentSubscription: Subscription;
              routeFragmentSubscription = this.route.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();
                      }, 0);
                  }
                }
              });
            }
          }

        gotoHashtag(fragment: string) {
            const element = document.querySelector("#" + fragment);
            if (element) element.scrollIntoView(element);
        }
}

如果用户直接登陆具有url标签的页面,则允许用户直接滚动到元素。

但是在这种情况下,我已经订阅了Fragment路由,ngAfterViewChecked但是ngAfterViewChecked()每次都被连续调用ngDoCheck,并且不允许用户滚动到顶部,因此routeFragmentSubscription.unsubscribe在视图滚动到元素后超时0毫秒后被调用。

另外,gotoHashtag方法定义为当用户专门单击锚标记时滚动到元素。

更新:

如果url具有查询字符串,则[routerLink]="['self-route', id]"锚点中将不保留查询字符串。我尝试以下解决方法相同:

<a (click)="gotoHashtag('some-element')">Jump to Element</a>

constructor( private route: ActivatedRoute,
              private _router:Router) {
}
...
...

gotoHashtag(fragment: string) {
    let url = '';
    let urlWithSegments = this._router.url.split('#');

    if(urlWithSegments.length){
      url = urlWithSegments[0];
    }

    window.location.hash = fragment;
    const element = document.querySelector("#" + fragment);
    if (element) element.scrollIntoView(element);
}

1

这个为我工作!这个ngFor可以动态锚定标签,您需要等待它们呈现

HTML:

<div #ngForComments *ngFor="let cm of Comments">
    <a id="Comment_{{cm.id}}" fragment="Comment_{{cm.id}}" (click)="jumpToId()">{{cm.namae}} Reply</a> Blah Blah
</div>

我的ts文件:

private fragment: string;
@ViewChildren('ngForComments') AnchorComments: QueryList<any>;

ngOnInit() {
      this.route.fragment.subscribe(fragment => { this.fragment = fragment; 
   });
}
ngAfterViewInit() {
    this.AnchorComments.changes.subscribe(t => {
      this.ngForRendred();
    })
}

ngForRendred() {
    this.jumpToId()
}

jumpToId() { 
    let x = document.querySelector("#" + this.fragment);
    console.log(x)
    if (x){
        x.scrollIntoView();
    }
}

不要忘了导入ViewChildrenQueryList等等。并添加一些构造函数ActivatedRoute


1

不像其他的答案,我会另外还加focus()沿scrollIntoView()。我也正在使用,setTimeout因为更改URL时它会跳到顶部。不知道是什么原因,但似乎setTimeout可以解决。

起源:

<a [routerLink] fragment="some-id" (click)="scrollIntoView('some-id')">Jump</a>

目的地:

<a id="some-id" tabindex="-1"></a>

打字稿:

scrollIntoView(anchorHash) {
    setTimeout(() => {
        const anchor = document.getElementById(anchorHash);
        if (anchor) {
            anchor.focus();
            anchor.scrollIntoView();
        }
    });
}

1

我遇到过同样的问题。解决方案:使用View port Scroller https://angular.io/api/common/ViewportScroller#scrolltoanchor

-app-routing.module.ts代码:

import { PageComponent } from './page/page.component';

const routes: Routes = [
   path: 'page', component: PageComponent },
   path: 'page/:id', component: PageComponent }
];

-组件HTML

  <a (click) = "scrollTo('typeExec')">
    <mat-icon>lens</mat-icon>
  </a>

-组件代码:

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


    export class ParametrageComponent {

      constructor(private viewScroller: ViewportScroller) {}

      scrollTo(tag : string)
      {
        this.viewScroller.scrollToAnchor(tag);
      }

    }

0

我刚刚测试了nmp中可用的非常有用的插件-ngx-scroll-to,对我来说非常有用。但是,它是为Angular 4+设计的,但是也许有人会觉得这个答案有用。


0

我尝试了大多数这些解决方案,但是遇到了问题,留下了另一个无法使用的片段,因此我做了一些与众不同的事情,可以100%工作,并且摆脱了URL中的丑陋哈希。

tl; dr这是到目前为止我所见不到的更好的方法。

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

@Component({
    selector: 'app-hero',
    templateUrl: './hero.component.html',
    styleUrls: ['./hero.component.scss']
})
export class HeroComponent implements OnInit, AfterViewChecked, OnDestroy {
    private fragment: string;
    fragSub: Subscription;

    constructor(private route: ActivatedRoute) { }

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

    ngAfterViewChecked(): void {
        try {
            document.querySelector('#' + this.fragment).scrollIntoView({behavior: 'smooth'});
            window.location.hash = "";
          } catch (e) { }
    }

    ngOnDestroy() {
        this.fragSub.unsubscribe();
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.