ScrollTo function in AngularJS
Solution 1
Here is a simple directive that will scroll to an element on click:
myApp.directive('scrollOnClick', function() {
return {
restrict: 'A',
link: function(scope, $elm) {
$elm.on('click', function() {
$("body").animate({scrollTop: $elm.offset().top}, "slow");
});
}
}
});
Demo: http://plnkr.co/edit/yz1EHB8ad3C59N6PzdCD?p=preview
For help creating directives, check out the videos at http://egghead.io, starting at #10 "first directive".
edit: To make it scroll to a specific element specified by a href, just check attrs.href
.
myApp.directive('scrollOnClick', function() {
return {
restrict: 'A',
link: function(scope, $elm, attrs) {
var idToScroll = attrs.href;
$elm.on('click', function() {
var $target;
if (idToScroll) {
$target = $(idToScroll);
} else {
$target = $elm;
}
$("body").animate({scrollTop: $target.offset().top}, "slow");
});
}
}
});
Then you could use it like this: <div scroll-on-click></div>
to scroll to the element clicked. Or <a scroll-on-click href="#element-id"></div>
to scroll to element with the id.
Solution 2
This is a better directive in case you would like to use it:
you can scroll to any element in the page:
.directive('scrollToItem', function() {
return {
restrict: 'A',
scope: {
scrollTo: "@"
},
link: function(scope, $elm,attr) {
$elm.on('click', function() {
$('html,body').animate({scrollTop: $(scope.scrollTo).offset().top }, "slow");
});
}
}})
Usage (for example click on div 'back-to-top' will scroll to id scroll-top):
<a id="top-scroll" name="top"></a>
<div class="back-to-top" scroll-to-item scroll-to="#top-scroll">
It's also supported by chrome,firefox,safari and IE cause of the html,body element .
Solution 3
In order to animate to a specific element inside a scroll container (fixed DIV)
/*
@param Container(DIV) that needs to be scrolled, ID or Div of the anchor element that should be scrolled to
Scrolls to a specific element in the div container
*/
this.scrollTo = function(container, anchor) {
var element = angular.element(anchor);
angular.element(container).animate({scrollTop: element.offset().top}, "slow");
}
Solution 4
An angular solution using $anchorScroll
taken from a now archived blog post by Ben Lesh, which is also reproduced in some detail at this SO answer he contributed (including a rewrite of how to do this within a routing):
app.controller('MainCtrl', function($scope, $location, $anchorScroll) {
var i = 1;
$scope.items = [{ id: 1, name: 'Item 1' }];
$scope.addItem = function (){
i++;
//add the item.
$scope.items.push({ id: i, name: 'Item ' + i});
//now scroll to it.
$location.hash('item' + i);
$anchorScroll();
};
});
And here is the plunker, from the blog that provided this solution: http://plnkr.co/edit/xi2r8wP6ZhQpmJrBj1jM?p=preview
Important to note that the template at that plunker includes this, which sets up the id
that you're using $anchorScroll
to scroll to:
<li ng-repeat="item in items"
id="item{{item.id}}"
>{{item.name}</li>
And if you care for a pure javascript solution, here is one:
Invoke runScroll in your code with parent container id and target scroll id:
function runScroll(parentDivId,targetID) {
var longdiv;
longdiv = document.querySelector("#" + parentDivId);
var div3pos = document.getElementById(targetID).offsetTop;
scrollTo(longdiv, div3pos, 600);
}
function scrollTo(element, to, duration) {
if (duration < 0) return;
var difference = to - element.scrollTop;
var perTick = difference / duration * 10;
setTimeout(function () {
element.scrollTop = element.scrollTop + perTick;
if (element.scrollTop == to) return;
scrollTo(element, to, duration - 10);
}, 10);
}
Reference: Cross browser JavaScript (not jQuery...) scroll to top animation
Solution 5
Thanks Andy for the example, this was very helpful. I ended implementing a slightly different strategy since I am developing a single-page scroll and did not want Angular to refresh when using the hashbang URL. I also want to preserve the back/forward action of the browser.
Instead of using the directive and the hash, I am using a $scope.$watch on the $location.search, and obtaining the target from there. This gives a nice clean anchor tag
<a ng-href="#/?scroll=myElement">My element</a>
I chained the watch code to the my module declaration in app.js like so:
.run(function($location, $rootScope) {
$rootScope.$watch(function() { return $location.search() }, function(search) {
var scrollPos = 0;
if (search.hasOwnProperty('scroll')) {
var $target = $('#' + search.scroll);
scrollPos = $target.offset().top;
}
$("body,html").animate({scrollTop: scrollPos}, "slow");
});
})
The caveat with the code above is that if you access by URL directly from a different route, the DOM may not be loaded in time for jQuery's $target.offset() call. The solution is to nest this code within a $viewContentLoaded watcher. The final code looks something like this:
.run(function($location, $rootScope) {
$rootScope.$on('$viewContentLoaded', function() {
$rootScope.$watch(function() { return $location.search() }, function(search) {
var scrollPos = 0
if (search.hasOwnProperty('scroll')) {
var $target = $('#' + search.scroll);
var scrollPos = $target.offset().top;
}
$("body,html").animate({scrollTop: scrollPos}, "slow");
});
});
})
Tested with Chrome and FF
EnigmaRM
I've focused my learning to front-end technologies. AngularJS, D3.js, jQuery, and AJAX.
Updated on July 05, 2022Comments
-
EnigmaRM almost 2 years
I'm trying to get a quick nav to work correctly. It's floating on the side. When they click on a link, it takes them to that ID on the page. I'm following this guide from Treehouse. This is what I have for the scrolling:
$("#quickNav a").click(function(){ var quickNavId = $(this).attr("href"); $("html, body").animate({scrollTop: $(location).offset().top}, "slow"); return false; });
I initially placed it before the
</body>
. But I seem to be running into a race condition where that was firing before the quickNav compiled (it has ang-hide
placed on it, not sure if that's causing it - but it is within the DOM).If I run that block of code in the console, then the scrolling works as expected.
I figured it'd be more effective to move this into the controller - or more likely within a directive. But I'm not having luck accomplishing that. How can I get this block of code to work with AngularJS?
-
EnigmaRM almost 11 yearsThanks for the help with a basic directive. I've made a couple very basic ones already. I'm not exactly sure how I would access the href within the quicknav (using a directive) to have it do the anchor linking.
-
EnigmaRM almost 11 yearsI ended up removing several lines of code from your edit (mostly just the
if
block.) That would be used to scroll to an element clicked on (like you demonstrated in your plunker) correct? Just so it would be more modular? -
Simon H over 9 yearsAnyone managed to use this and get around the iOS 'feature' that results in having to double-tap to trigger a 'click'
-
nidal over 9 years@rnrneverdies it does work on firefox if you change $("body") to $("body, html")
-
Cory about 9 yearsFor best cross-browser support, you should use $("html, body").animate()
-
Hasteq almost 9 yearsThanks Ionic. Updated my answer
-
Rafael code over 8 yearsHow come I get "Uncaught TypeError: $ is not a function" when trying your solution?
-
aw04 over 8 yearsI'm not sure using href to pass the id is the best solution... I found it to be a bit jumpy as the browser tries to process it in a 'normal' way on click as well. The better solution in my opinion is to bind something to the scope, ie. { scope: elementId: '@' }, then <a href scroll-on-click element-id="#element-id"></a> and grab that in the link function instead
-
nmante about 8 years@Cory have you included/loaded JQuery?
-
Wang'l Pakhrin over 7 yearssays, $elm.offest is undefined. how can it be an undefined value.
-
Undefitied over 7 yearsWhy do I need two directives instead one
scroll-to-item=".selector"
? -
Dylanthepiguy about 6 years@AndrewJoslin Your plunker example does not work at all for me on chrome on mac