Как обрабатывать привязку хеш-ссылок в AngularJS


318

Кто-нибудь из вас знает, как правильно обрабатывать привязку хеш-ссылок в AngularJS ?

У меня есть следующая разметка для простой FAQ-страницы

<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-модели. Интересно, если вы назначите hashPrefix для locationProvider, если он будет игнорировать ссылки на идентификаторы: docs.angularjs.org/guide/dev_guide.services.$location
Адам

Адам прав в использовании ng-href.
Расмус


Это также проблема для нового Angular: stackoverflow.com/questions/36101756/…
phil294

Ответы:


375

Ты ищешь $anchorScroll().

Вот (дерьмовая) документация.

И вот источник.

По сути, вы просто внедряете его и вызываете в контроллере, и он прокручивает вас до любого элемента с идентификатором $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, демонстрирующий прокрутку с маршрутизацией и $ 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
Валентин Шибанов

1
@blesh, вызов location.hash (X) меняет страницу, так как маршрутизация контролирует представления.
dsldsl

24
Это решение вызывает повторную визуализацию всего приложения.

2
@dsldsl && @OliverJosephAsh: $ location.hash () не перезагрузит страницу. Если это так, то происходит что-то еще. Вот тот же план с записываемым временем при загрузке страницы, вы увидите, что он не меняется. Если вы хотите прокрутить тег привязки на текущей странице без перезагрузки маршрута, вы просто сделаете обычная ссылка <a href="#foo">foo</a>. Мой пример кода должен был показать прокрутку до идентификатора на routechange.
Бен Леш

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 (...), и это сортирует его.
ЖЖ

8
Ваш трюк с "спасением старого хэша" был абсолютным спасителем. Это предотвращает перезагрузку страницы, сохраняя маршрут чистым. Отличная идея!
ThisLanham

2
Хороший. Но после реализации вашего решения URL не обновляет значение целевого идентификатора.
Мохамед Хуссейн

1
У меня был тот же опыт, что и у Мохамеда ... Он действительно остановил перезагрузку, но он отображает маршрут без хэширования (и $ anchorScroll не имел никакого эффекта). 1,2,6 ммм
Майкл

3
Я использую $location.hash(my_id); $anchorScroll; $location.hash(null). Это предотвращает перезагрузку, и мне не нужно управлять oldпеременной.
MFB

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>

И используйте idатрибут в ваших HTML- элементах так:

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

Нет необходимости использовать ## как указано / упомянуто в комментариях ;-)


1
Это не сработало для меня, но решение здесь
сработало

6
Спасибо за это решение. Но target = "_ self" достаточно. Не нужно удваивать #
Кристоф П

5
target = "_ self" - определенно лучший ответ (не нужно удваивать #, как указал Кристоф П). Это работает независимо от того, включен Html5Mode или нет.
новичок

3
Просто и полно. Работал для меня без необходимости добавлять еще одну угловую зависимость.
adeady

4
Это правильное решение. Нет необходимости привлекать угловой сервис $ anchorScroll. См. Документацию по тегу: https://developer.mozilla.org/en/docs/Web/HTML/Element/a
Yiling

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>

Потрясающие! Безусловно самое простое решение, но есть идеи, как сделать ссылку на якорь на отдельной странице? (то есть / продукты # книги)
8bithero

Я думаю, что это то же самое, что и решение (/ products ## books) в AngularJS
Линкольн

1
По моему опыту, href = "##" работает только тогда, когда вводится $ anchorScroll.
Брайан Парк

9
это кажется относительно простым, но это не работает :-(
brykneval

4
Я добавил target = "_ self", и это сработало как прелесть для всех типов навигации по странице (чтение слайдеров, переход к различным разделам и т. Д.). Спасибо, что поделились этим замечательным и простым трюком.
Кумар Нитеш

19

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

href="#/route#anchorID

где routeтекущий угловой маршрут и anchorIDсоответствует <a id="anchorID">где-то на странице


1
Это вызывает нормальное изменение маршрута AngularJS и поэтому не рекомендуется. В моем случае это было очень наглядно, поскольку видео YouTube на странице часто задаваемых вопросов / справки перезагружалось.
Робин Андерссон

9
@ RobinWassén-Andersson, указав reloadOnSearch: falseэтот маршрут в конфигурации маршрутов, angular не будет вызывать изменение маршрута и просто перейдет к идентификатору. В сочетании с полным маршрутом, указанным в aтеге, я бы сказал, что это самое простое и простое решение.
Элиз

Спасибо. Это помогло мне. Я не использую никаких пользовательских маршрутов в своем приложении, поэтому выполнение 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>

Кроме того, если вам нужно учитывать фиксированную панель навигации (или другой пользовательский интерфейс), вы можете установить смещение для $ anchorScroll следующим образом (в функции запуска основного модуля):

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

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

Извините за приставание .. Если бы у вас была возможность, вы могли бы взглянуть на мой вопрос о стеке? Я чувствую, что ваш ответ здесь настолько приблизил меня, но я просто не могу его реализовать: stackoverflow.com/questions/41494330/… . Я тоже использую ngRouteи более новую версию Angular.
Кайл Васселла

Извините, я не пробовал этот конкретный случай ... но вы смотрели на $ location.search () или $ routeParams ? Возможно, вы могли бы использовать один или другой при инициализации вашего контроллера - если ваш параметр поиска scrollTo находится в URL, тогда контроллер может использовать $ anchorScroll, как указано выше, для прокрутки страницы.
Ребекка

2
Передав идентификатор непосредственно в $ anchorScroll, мои маршруты изменились с чего-то вроде / contact # contact на just / contact. Это должен быть принятый ответ imho.
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

@CMCDragonkai с моей директивой, я не уверен, потому что я использую вызов $ anchorScroll, который, похоже, обрабатывает только прокрутку к элементу, находящемуся в данный момент на странице. Возможно, вам придется возиться с $ location или $ window, чтобы получить что-то, связанное с изменением страницы.
ХалилРаванна

2
Вам необходимо отписаться от события locationChangeStart: var off = scope. $ On ('$ locationChangeStart', function (ev) {off (); ev.preventDefault ();});

Хороший улов @EugeneTskhovrebov, я пошел дальше и добавил это к ответу в редактировании.
ХалилРаванна

5

Попробуй установить хеш-префикс для угловых маршрутов $locationProvider.hashPrefix('!')

Полный пример:

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

Это не влияет на результат. Это было бы мило, хотя.
Расмус

2
Ты уверен. Почему это не работает? Если префикс хеша равен!, То хеш-маршрутизация должна быть #! Page. Поэтому AngularJS должен определять, когда это просто #hash, он должен автоматически привязывать прокрутку и работать как с URL-адресами режима HTML5, так и с URL-адресами режима хеширования.
CMCDragonkai

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
Это не работает: Ошибка: [$ инжектор: modulerr] Не удалось создать экземпляр угла модуля из-за: Ошибка: недопустимый «обработчик» в when ()
геоидезический

5

Это старый пост, но я потратил много времени на изучение различных решений, поэтому хотел поделиться еще одним простым. Просто добавление target="_self"в <a>тег исправило это для меня. Ссылка работает и доставит меня в нужное место на странице.

Тем не менее, Angular все еще вводит некоторую странность с # в URL, поэтому вы можете столкнуться с проблемами при использовании кнопки «Назад» для навигации и т. Д. После использования этого метода.


4

Это может быть новый атрибут для ngView, но я смог заставить его привязывать хеш-ссылки для работы с angular-routeиспользованием ngView autoscrollатрибута и «двойных хешей».

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>


2

Вот своего рода грязный обходной путь, создавая пользовательскую директиву, которая будет прокручивать до указанного элемента (с жестко закодированным «faq»)

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


Это действительно работает, но это, как вы сказали, грязно. Очень грязный. Давайте посмотрим, может ли кто-то еще придумать более красивое решение, или мне придется пойти с этим.
Расмус

Angular уже имеет встроенную функцию scrollTo $anchorScroll, смотрите мой ответ.
Бен Леш

Изменен plunker, чтобы он был менее грязным: он использует $ location.path (), чтобы в исходнике не было жестко закодированного "faq". А также пытался использовать $ anchorScroll, но, кажется, из-за маршрутизации он не работает ...
Валентин Шибанов

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для создания правильного URL на основе текущего состояния. Мой пример использует 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"]? Кроме того, без явного включения 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>

... и не секрет для идентификатора якоря:

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

1

Я пытался сделать мое приложение Угловое прокрутки к загрузке якорь OPON и побежал в URL переписывания правила $ routeProvider.

После долгих экспериментов я остановился на этом:

  1. зарегистрируйте обработчик события document.onload из раздела .run () модуля приложения Angular.
  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

Я не уверен на 100%, работает ли это все время, но в моем приложении это дает мне ожидаемое поведение.

Допустим, вы находитесь на странице ПРО, и у вас есть следующий маршрут:

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

Получите вашу функцию прокрутки легко. Он также поддерживает анимированную / плавную прокрутку в качестве дополнительной функции. Детали для библиотеки Angular Scroll :

Github - https://github.com/oblador/angular-scroll

Бауэр :bower install --save angular-scroll

нпм :npm install --save angular-scroll

Minfied версия - всего 9kb

Плавная прокрутка (анимированная прокрутка) - да

Scroll Spy - да

Документация - отлично

Демо - http://oblador.github.io/angular-scroll/

Надеюсь это поможет.


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 было это, но я бы изменил одну вещь. Вместо этого я бы использовал замену, чтобы вам не пришлось устанавливать ее обратно.

$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 вообще.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.