Как обрабатывать привязку хеш-ссылок в AngularJS
Кто-нибудь из вас знает, как правильно обрабатывать привязку хеш-ссылок в 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+, поэтому я боюсь, что это не является приемлемым решением:/
28 ответов
Ты ищешь $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>
В моем случае я заметил, что логика маршрутизации включается, если я изменил $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);
};
Там нет необходимости изменять какие-либо маршрутизации или что-либо еще просто нужно использовать 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>
Нет необходимости использовать ## как указано / упомянуто в комментариях;-)
<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>
Если вы всегда знаете маршрут, вы можете просто добавить привязку следующим образом:
href="#/route#anchorID
где route
текущий угловой маршрут и anchorID
соответствует <a id="anchorID">
где-то на странице
Это было мое решение с использованием директивы, которая кажется более угловатой, потому что мы имеем дело с DOM:
КОД
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, и обновления по существу сломали приложение.
$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;
});
Это старый пост, но я потратил много времени на изучение различных решений, поэтому хотел поделиться еще одним простым. Просто добавив target="_self"
к <a>
тег исправил это для меня. Ссылка работает и доставит меня в нужное место на странице.
Тем не менее, Angular по-прежнему вносит некоторые странности с # в URL, поэтому вы можете столкнуться с проблемами при использовании кнопки "Назад" для навигации и т. П. После использования этого метода.
Я обошел это в логике маршрута для моего приложения.
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;
}
});
}
Попробуй установить хеш-префикс для угловых маршрутов $locationProvider.hashPrefix('!')
Полный пример:
angular.module('app', [])
.config(['$routeProvider', '$locationProvider',
function($routeProvider, $locationProvider){
$routeProvider.when( ... );
$locationProvider.hashPrefix('!');
}
])
Это может быть новый атрибут для ngView, но я смог заставить его привязывать хеш-ссылки для работы с angular-route
с использованием ngView autoscroll
атрибут и "двойные хэши".
(Следующий код был использован с угловым ремешком)
<!-- 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>
Я мог бы сделать это так:
<li>
<a href="#/#about">About</a>
</li>
Мое решение с 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>
Вот своего рода грязный обходной путь, создавая пользовательскую директиву, которая будет прокручивать до указанного элемента (с жестко закодированным "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);
}
}
};
});
<a href="/#/#faq-1">Question 1</a>
<a href="/#/#faq-2">Question 2</a>
<a href="/#/#faq-3">Question 3</a>
Если вы не любите использовать 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;
};
}])
Попробуйте, это решит проблему с привязкой.
app.run(function($location, $anchorScroll){
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
});
});
});
});
Получите вашу функцию прокрутки легко. Он также поддерживает анимированную / плавную прокрутку в качестве дополнительной функции. Детали для библиотеки 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/
Надеюсь это поможет.
На основе @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();
}
}
});
Я пытался сделать так, чтобы мое приложение Angular прокручивалось до загрузки якорей и столкнулось с правилами перезаписи URL-адреса $routeProvider.
После долгих экспериментов я остановился на этом:
- зарегистрируйте обработчик события document.onload из раздела.run() модуля приложения Angular.
- в обработчике выясните, каким должен был быть исходный тег привязки, выполнив некоторые строковые операции.
- переопределить 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();
}
}
});
Я не уверен на 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>
Вы можете попробовать использовать 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>
См. https://code.angularjs.org/1.4.10/docs/api/ngRoute/provider/$ routeProvider.
[reloadOnSearch=true] - {boolean=} - перезагрузить маршрут при изменении только $location.search() или $location.hash().
Установка этого в false сделала трюк без всего вышеперечисленного для меня.
Я использую 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 вообще.
Ни одно из приведенных выше решений не работает для меня, но я только что попробовал это, и это сработало,
<a href="#/#faq-1">Question 1</a>
Таким образом, я понял, что мне нужно уведомить страницу, чтобы начать со страницы индекса, а затем использовать традиционную привязку.
Иногда в Angularjs-приложении хэш-навигация не работает, и библиотеки начальной загрузки jquery javascript широко используют этот тип навигации, чтобы он работал. target="_self"
прикрепить тег. например <a data-toggle="tab" href="#id_of_div_to_navigate" target="_self">
При изменении маршрута он будет прокручиваться до верхней части страницы.
$scope.$on('$routeChangeSuccess', function () {
window.scrollTo(0, 0);
});
поместите этот код на свой контроллер.
В моем представлении @slugslog было это, но я бы изменил одну вещь. Вместо этого я бы использовал замену, чтобы вам не пришлось устанавливать ее обратно.
$scope.scrollTo = function(id) {
var old = $location.hash();
$location.hash(id).replace();
$anchorScroll();
};
Документы Поиск "Заменить метод"