如何在AngularJS中处理锚点哈希链接


318

你们中有谁知道如何在AngularJS中很好地处理锚点哈希链接吗?

我为简单的常见问题解答页面添加了以下标记

<a href="#faq-1">Question 1</a>
<a href="#faq-2">Question 2</a>
<a href="#faq-3">Question 3</a>

<h3 id="faq-1">Question 1</h3>
<h3 id="faq-2">Question 2</h3>
<h3 id="fa1-3">Question 3</h3>

单击上面的任何链接时,AngularJS会拦截并将我路由到一个完全不同的页面(在我的情况下是404页,因为没有路由与这些链接匹配。)

我的第一个想法是创建一个匹配“ / faq /:chapter ” 的路由,并在相应的控制器中检查$routeParams.chapter匹配的元素之后,然后使用jQuery向下滚动到它。

但是随后AngularJS再次对我大喊大叫,无论如何仍只是滚动到页面顶部。

那么,这里有人在过去做过类似的事情并且知道解决方案吗?

编辑:切换到html5Mode应该可以解决我的问题,但是无论如何我们都必须支持IE8 +,所以我担心这不是一个可以接受的解决方案:/


我认为有棱角的建议改为使用ng-href=""
搭载

10
我认为ng-href仅在url包含需要绑定到ng-model的动态数据时才适用。我有点想知道是否将hashPrefix分配给locationProvider,如果它会忽略指向ID的链接:docs.angularjs.org/guide/dev_guide.services.$location
Adam

亚当在ng-href用法上是正确的。
Rasmus


这也是新的角度的问题:stackoverflow.com/questions/36101756/...
phil294

Answers:


375

您正在寻找$anchorScroll()

这是(糟糕的)文档。

这是来源。

基本上,您只需注入它并在控制器中调用它,它将把您滚动到ID为的任何元素 $location.hash()

app.controller('TestCtrl', function($scope, $location, $anchorScroll) {
   $scope.scrollTo = function(id) {
      $location.hash(id);
      $anchorScroll();
   }
});

<a ng-click="scrollTo('foo')">Foo</a>

<div id="foo">Here you are</div>

这是一个演示的朋克

编辑:与路由一起使用

像往常一样设置您的角度路由,然后只需添加以下代码。

app.run(function($rootScope, $location, $anchorScroll, $routeParams) {
  //when the route is changed scroll to the proper element.
  $rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
    $location.hash($routeParams.scrollTo);
    $anchorScroll();  
  });
});

并且您的链接如下所示:

<a href="#/test?scrollTo=foo">Test/Foo</a>

这是一个Plunker,展示了使用route和$ anchorScroll进行滚动

甚至更简单:

app.run(function($rootScope, $location, $anchorScroll) {
  //when the route is changed scroll to the proper element.
  $rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
    if($location.hash()) $anchorScroll();  
  });
});

并且您的链接如下所示:

<a href="#/test#foo">Test/Foo</a>

5
添加路由时可能会出现问题:如果添加ngView,则每次更改url的哈希值都会触发路由重载...在您的示例中,没有路由,并且url不能反映当前项目...但是,感谢您指向$ anchorScroll
Valentyn Shybanov

1
@blesh,调用location.hash(X)更改页面,因为路由控制了视图。
dsldsl

24
此解决方案使我的整个应用程序重新呈现。

2
@dsldsl && @OliverJosephAsh:$ location.hash()将不会重新加载页面。如果是这样,那还会有其他事情发生。这与加载页面时所花的时间相同,您会发现它没有变化。 如果您想滚动到当前页面上的锚标签而不重新加载路线,则只需执行常规链接<a href="#foo">foo</a>。我的代码示例显示了如何在routechange上滚动到一个id。
Ben Lesh 2013年

4
@blesh:我建议您在滚动到所需部分后也删除哈希,这样,URL不会被确实不应该存在的内容污染。使用这个: $location.search('scrollTo', null)
kumarharsh

168

就我而言,我注意到如果修改,路由逻辑就会开始$location.hash()。以下技巧有效。

$scope.scrollTo = function(id) {
    var old = $location.hash();
    $location.hash(id);
    $anchorScroll();
    //reset to old to keep any additional routing logic from kicking in
    $location.hash(old);
};

5
非常感谢,即使使用@blesh的.run(...)解决方案,路由逻辑也绝对拒绝其行为,并对它进行排序。
ljs

8
您的“保存旧哈希”技巧绝对是救生员。它可以防止页面重新加载,同时保持路由干净。真棒的想法!
ThisLanham

2
好一个。但是在实施解决方案后,URL不会更新目标ID的值。
Mohamed Hussain 2014年

1
我有和Mohamed一样的经历……它确实停止了重新加载,但是显示了无哈希路由($ anchorScroll没有作用)。1.2.6毫米
2014年

3
我用$location.hash(my_id); $anchorScroll; $location.hash(null)。它可以防止重新加载,而我不必管理old变量。
MFB 2015年

53

target="_self"创建链接时无需更改任何路由或其他任何需要使用的东西

例:

<a href="#faq-1" target="_self">Question 1</a>
<a href="#faq-2" target="_self">Question 2</a>
<a href="#faq-3" target="_self">Question 3</a>

idhtml元素中使用该属性,如下所示:

<h3 id="faq-1">Question 1</h3>
<h3 id="faq-2">Question 2</h3>
<h3 id="faq-3">Question 3</h3>

无需使用注释中指出的## ;-)


1
这对我不起作用,但是这里的解决方案有效:stackoverflow.com/a/19367249/633107
Splaktar,2016年

6
感谢您的解决方案。但是target =“ _ self”就足够了。不需要加倍#
Christophe P

5
target =“ _ self”绝对是最好的答案(如克里斯托夫·P所指出的,不需要加倍#)。无论是否打开或关闭Html5Mode,它都可以工作。
纽曼

3
简单而完整。为我工作,而无需添加另一个角度依赖性。
adeady

4
这是正确的解决方案。无需涉及有角度的$ anchorScroll服务。请参阅文档以获取标签:https
Yiling 2016年

41
<a href="##faq-1">Question 1</a>
<a href="##faq-2">Question 2</a>
<a href="##faq-3">Question 3</a>

<h3 id="faq-1">Question 1</h3>
<h3 id="faq-2">Question 2</h3>
<h3 id="faq-3">Question 3</h3>

太棒了!到目前为止,最简单的解决方案是什么,但是知道如何链接到单独页面上的锚点吗?(即/ products#books)
8bithero'5

我认为这与AngularJS中的解决方案(/ products ## books)相同
lincolnge 2014年

1
根据我的经验,href =“ ##”仅在注入$ anchorScroll时有效。
Brian Park

9
这似乎相对简单,但不起作用:-(
brykneval 2015年

4
我添加了target =“ _ self”,它对于页面内的所有类型的导航都非常有用(阅读滑块,转到不同的部分等等)。感谢您分享这个伟大而最简单的技巧。
Kumar Nitesh 2015年

19

如果您始终知道路线,则可以像这样简单地添加锚点:

href="#/route#anchorID

route当前的角度路线在哪里,并且anchorID<a id="anchorID">页面上的某个位置匹配


1
这会触发正常的AngularJS路由更改,因此不建议使用。就我而言,这是非常直观的,因为重新加载了“常见问题/帮助”页面中的YouTube视频。
罗宾·安德森

9
@RobinWassén-Andersson通过reloadOnSearch: false在您的路线配置中指定该路线,角度不会触发路线更改,而只会滚动到ID。结合a标记中指定的完整路由,我想说这是最简单,最直接的解决方案。
Elise 2014年

谢谢。这对我有帮助。我没有在我的应用程序中使用任何自定义路由,因此使用href =“#/#anchor-name”效果很好!
乔丹

14

$anchorScroll 可以做到这一点,但是在最新版本的Angular中有更好的方法来使用它。

现在,$anchorScroll将哈希作为可选参数接受,因此您根本不需要更改$location.hash。(文件

这是最好的解决方案,因为它根本不影响路由。因为我使用ngRoute我无法得到任何其他的解决方案,工作和路线将尽快为我设置重装$location.hash(id)之前$anchorScroll可以做它的魔力。

这是如何使用它...首先,在指令或控制器中:

$scope.scrollTo = function (id) {
  $anchorScroll(id);  
}

然后在视图中:

<a href="" ng-click="scrollTo(id)">Text</a>

另外,如果您需要考虑固定的导航栏(或其他UI),则可以这样设置$ anchorScroll的偏移量(在主模块的运行功能中):

.run(function ($anchorScroll) {
   //this will make anchorScroll scroll to the div minus 50px
   $anchorScroll.yOffset = 50;
});

谢谢。您将如何实施哈希链接与路由更改相结合的策略?示例:单击此导航项,这将打开另一个视图并向下滚动到id该视图中的特定视图。
凯尔·瓦塞拉

对不起。.如果有机会,您可以看一下我的Stack问题吗?我觉得您在这里的答案让我非常接近,但我无法实现它:stackoverflow.com/questions/41494330/…。我也正在使用ngRoute较新的Angular版本。
凯尔·瓦塞拉

抱歉,我没有尝试过这种特殊情况...但是您是否看过$ location.search()$ routeParams呢?也许您可以在控制器初始化时使用一个或另一个-如果您的scrollTo搜索参数位于URL中,则控制器可以使用上述的$ anchorScroll来滚动页面。
丽贝卡

2
通过将ID直接传递到$ anchorScroll,我的路线从/ contact#contact变为/ contact。这应该是恕我直言的答案。
RandomUs1r

12

这是我使用指令的解决方案,该指令看起来更像是Angular-y,因为我们正在处理DOM:

在这里

的github

angular.module('app', [])
.directive('scrollTo', function ($location, $anchorScroll) {
  return function(scope, element, attrs) {

    element.bind('click', function(event) {
        event.stopPropagation();
        var off = scope.$on('$locationChangeStart', function(ev) {
            off();
            ev.preventDefault();
        });
        var location = attrs.scrollTo;
        $location.hash(location);
        $anchorScroll();
    });

  };
});

的HTML

<ul>
  <li><a href="" scroll-to="section1">Section 1</a></li>
  <li><a href="" scroll-to="section2">Section 2</a></li>
</ul>

<h1 id="section1">Hi, I'm section 1</h1>
<p>
Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora quaeritis. 
 Summus brains sit​​, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum mauris. 
Hi mindless mortuis soulless creaturas, imo evil stalking monstra adventus resi dentevil vultus comedat cerebella viventium. 
Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv ingdead.
</p>

<h1 id="section2">I'm totally section 2</h1>
<p>
Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora quaeritis. 
 Summus brains sit​​, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum mauris. 
Hi mindless mortuis soulless creaturas, imo evil stalking monstra adventus resi dentevil vultus comedat cerebella viventium. 
Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv ingdead.
</p>

我使用了$ anchorScroll服务。为了抵消哈希更改所伴随的页面刷新,我继续进行并取消了locationChangeStart事件。这对我有用,因为我有一个连接到ng-switch的帮助页面,刷新本质上会破坏该应用程序。


我喜欢您的指令解决方案。但是,您要如何加载另一个页面并同时滚动到锚点位置。如果没有angularjs,则名义上为href =“ location#hash”。但是您的指令阻止页面重新加载。
CMCDragonkai 2013年

@CMCDragonkai与我的指令,我不确定,因为我利用了对$ anchorScroll的调用,该调用看起来只处理滚动到页面上当前的元素。您可能需要弄乱$ location$ window才能获得涉及页面更改的内容。
KhalilRavanna 2013年

2
您需要取消订阅locationChangeStart事件:var off = scope。$ on('$ locationChangeStart',function(ev){off(); ev.preventDefault();});

很好@EugeneTskhovrebov,我继续进行编辑,并将其添加到答案中。
KhalilRavanna 2014年

5

尝试为角度路线设置哈希前缀 $locationProvider.hashPrefix('!')

完整示例:

angular.module('app', [])
  .config(['$routeProvider', '$locationProvider', 
    function($routeProvider, $locationProvider){
      $routeProvider.when( ... );
      $locationProvider.hashPrefix('!');
    }
  ])

这不会影响结果。虽然会很甜蜜。
Rasmus

2
你确定吗。为什么不起作用?如果哈希前缀为!,则哈希路由应为#!page。因此,AngularJS应该检测它何时只是#hash,它应该自动定位滚动并适用于HTML5模式url和哈希模式url。
CMCDragonkai 2013年

5

我在我的应用程序的路由逻辑中解决了这个问题。

function config($routeProvider) {
  $routeProvider
    .when('/', {
      templateUrl: '/partials/search.html',
      controller: 'ctrlMain'
    })
    .otherwise({
      // Angular interferes with anchor links, so this function preserves the
      // requested hash while still invoking the default route.
      redirectTo: function() {
        // Strips the leading '#/' from the current hash value.
        var hash = '#' + window.location.hash.replace(/^#\//g, '');
        window.location.hash = hash;
        return '/' + hash;
      }
    });
}

1
这不起作用:错误:[$ injector:modulerr]由于以下原因而无法实例化模块角度:错误:when()中的无效“ handler”
大地测量学时间

5

这是一篇旧文章,但是我花了很长时间研究各种解决方案,所以我想分享一个更简单的解决方案。只需添加target="_self"<a>标签即可为我修复。该链接有效,并将我带到页面上的正确位置。

但是,Angular仍然会在URL中使用#注入一些怪异之处,因此使用后退按钮进行导航等操作可能会遇到麻烦。


4

这可能是ngView的新属性,但是我已经能够angular-route使用ngView autoscroll属性和'double-hashhes' 使其锚定哈希链接。

ngView(请参阅自动滚动)

(下面的代码用于角度表带)

<!-- use the autoscroll attribute to scroll to hash on $viewContentLoaded -->    
<div ng-view="" autoscroll></div>

<!-- A.href link for bs-scrollspy from angular-strap -->
<!-- A.ngHref for autoscroll on current route without a location change -->
<ul class="nav bs-sidenav">
  <li data-target="#main-html5"><a href="#main-html5" ng-href="##main-html5">HTML5</a></li>
  <li data-target="#main-angular"><a href="#main-angular" ng-href="##main-angular" >Angular</a></li>
  <li data-target="#main-karma"><a href="#main-karma" ng-href="##main-karma">Karma</a></li>
</ul>

3

我可以这样做:

<li>
<a href="#/#about">About</a>
</li>

2

通过创建将滚动到指定元素(带有硬编码“常见问题”)的自定义指令,这是一种肮脏的解决方法

app.directive('h3', function($routeParams) {
  return {
    restrict: 'E',
    link: function(scope, element, attrs){        
        if ('faq'+$routeParams.v == attrs.id) {
          setTimeout(function() {
             window.scrollTo(0, element[0].offsetTop);
          },1);        
        }
    }
  };
});

http://plnkr.co/edit/Po37JFeP5IsNoz5ZycFs?p=preview


确实确实可以,但是正如您所说,这很脏。很肮脏。让我们看看其他人是否可以提出更漂亮的解决方案,否则我将不得不这样做。
Rasmus

Angular已经通过via内置了scrollTo功能$anchorScroll,请参阅我的答案。
Ben Lesh

更改了监听器,使其不那么脏:它使用$ location.path(),因此源中没有硬编码的“常见问题”。并且还尝试使用$ anchorScroll,但似乎由于路由它不起作用...
Valentyn Shybanov

2
<a href="/#/#faq-1">Question 1</a>
<a href="/#/#faq-2">Question 2</a>
<a href="/#/#faq-3">Question 3</a>

2

如果您不喜欢使用ng-click此处,则可以选择其他解决方案。它使用filter来根据当前状态生成正确的网址。我的示例使用ui.router

好处是用户将看到链接在悬停时的位置。

<a href="{{ 'my-element-id' | anchor }}">My element</a>

过滤器:

.filter('anchor', ['$state', function($state) {
    return function(id) {
        return '/#' + $state.current.url + '#' + id;
    };
}])

2

我的ng-route解决方案是这个简单的指令:

   app.directive('scrollto',
       function ($anchorScroll,$location) {
            return {
                link: function (scope, element, attrs) {
                    element.click(function (e) {
                        e.preventDefault();
                        $location.hash(attrs["scrollto"]);
                        $anchorScroll();
                    });
                }
            };
    })

HTML看起来像:

<a href="" scrollTo="yourid">link</a>

你不必像指定属性scroll-to="yourid"并命名该指令scrollTo(和访问属性为attrs["scrollTo"]?再说,没有explicite jQuery的夹杂物的处理程序已与约束element.on('click', function (e) {..})
Theodros Zelleke

1

您可以尝试使用anchorScroll

因此,控制器将是:

app.controller('MainCtrl', function($scope, $location, $anchorScroll, $routeParams) {
  $scope.scrollTo = function(id) {
     $location.hash(id);
     $anchorScroll();
  }
});

和视图:

<a href="" ng-click="scrollTo('foo')">Scroll to #foo</a>

...并且没有关于锚ID的秘密:

<div id="foo">
  This is #foo
</div>

1

我试图使我的Angular应用滚动到加载的锚opon上,并遇到$ routeProvider的URL重写规则。

经过长时间的实验,我决定这样做:

  1. 从Angular应用程序模块的.run()部分注册document.onload事件处理程序。
  2. 在处理程序中,通过执行一些字符串操作来找出原来具有锚标签的对象。
  3. 用剥离的锚标记覆盖location.hash(这将导致$ routeProvider立即使用“#/”规则再次覆盖它。但这很好,因为Angular现在与URL 4中的内容保持同步)调用$ anchorScroll()。

angular.module("bla",[]).}])
.run(function($location, $anchorScroll){
         $(document).ready(function() {
	 if(location.hash && location.hash.length>=1)    		{
			var path = location.hash;
			var potentialAnchor = path.substring(path.lastIndexOf("/")+1);
			if ($("#" + potentialAnchor).length > 0) {   // make sure this hashtag exists in the doc.                          
			    location.hash = potentialAnchor;
			    $anchorScroll();
			}
		}	 
 });


1

我不能百分百确定这是否一直有效,但是在我的应用程序中,这给了我预期的行为。

假设您在“ 关于”页面上,并且具有以下路线:

yourApp.config(['$routeProvider', 
    function($routeProvider) {
        $routeProvider.
            when('/about', {
                templateUrl: 'about.html',
                controller: 'AboutCtrl'
            }).
            otherwise({
                redirectTo: '/'
            });
        }
]);

现在,在您的HTML中

<ul>
    <li><a href="#/about#tab1">First Part</a></li>
    <li><a href="#/about#tab2">Second Part</a></li>
    <li><a href="#/about#tab3">Third Part</a></li>                      
</ul>

<div id="tab1">1</div>
<div id="tab2">2</div>
<div id="tab3">3</div>

结论

在锚点之前添加页面名称对我有用。让我知道你的想法。

缺点

这将重新渲染页面,然后滚动到锚点。

更新

更好的方法是添加以下内容:

<a href="#tab1" onclick="return false;">First Part</a>


1

基于@Stoyan,我提出了以下解决方案:

app.run(function($location, $anchorScroll){
    var uri = window.location.href;

    if(uri.length >= 4){

        var parts = uri.split('#!#');
        if(parts.length > 1){
            var anchor = parts[parts.length -1];
            $location.hash(anchor);
            $anchorScroll();
        }
    }
});

0

更改路线时,它将滚动到页面顶部。

 $scope.$on('$routeChangeSuccess', function () {
      window.scrollTo(0, 0);
  });

将此代码放在您的控制器上。


0

我认为@slugslog拥有它,但是我会改变一件事。我将使用replace代替,因此您不必重新设置它。

$scope.scrollTo = function(id) {
    var old = $location.hash();
    $location.hash(id).replace();
    $anchorScroll();
};

在文档中搜索“替换方法”


0

上面的解决方案都不对我有用,但是我只是尝试了一下,并且它起作用了,

<a href="#/#faq-1">Question 1</a>

因此,我意识到我需要通知页面以索引页开始,然后使用传统的锚点。


0

有时在angularjs应用程序中,哈希导航不起作用,并且引导jquery javascript库大量使用了这种类型的导航,以使其工作添加target="_self"到锚标记中。例如<a data-toggle="tab" href="#id_of_div_to_navigate" target="_self">



0

我正在使用AngularJS 1.3.15,看起来我不需要做任何特别的事情。

https://code.angularjs.org/1.3.15/docs/api/ng/provider/ $ anchorScrollProvider

因此,以下内容适用于我的html:

<ul>
  <li ng-repeat="page in pages"><a ng-href="#{{'id-'+id}}">{{id}}</a>
  </li>
</ul>
<div ng-attr-id="{{'id-'+id}}" </div>

我根本不需要对控制器或JavaScript进行任何更改。

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.