Angular.js: $anchorScroll Offset

Created on 26 Feb 2013  路  53Comments  路  Source: angular/angular.js

Just a quick feature request.

If your app has a fixed position header, $anchorScroll will "incorrectly" position the scroll for the users view. Nothing is broke here, but allowing for an $anchorScrollProvider offset number would be nice.

Basically, right after elm.scrollIntoView() it would execute roughly.. if (offset > 0) $window.scroll($window.scrollX, $window.scrollY - offset);. This would allow $anchorScrollProvider & $anchorScroll to play nicely with fixed position headers.

misc core moderate confusing feature

Most helpful comment

I wrote the following in my controller. Really awful hack put could not figure any other way. Am new to Angular; but thought i would share.

$scope.goToInternalHash = function(id, offset) {
$location.hash(id);
$anchorScroll();
setTimeout(function(){
window.scrollTo(window.pageXOffset, window.pageYOffset - offset);
},300);

All 53 comments

Funny enough, i noticed that even the official docs suffer from this same problem.

http://docs.angularjs.org/api/AUTO.$provide#factory

Notice how when you visit that url, you cannot see the title for that documentation? It's because it is hidden behind the page header, haha.

Volunteer

Actually, the underlying scroll functionality comes from https://developer.mozilla.org/en-US/docs/DOM/element.scrollIntoView which doesn't allow for an offset. Doing the $window.scroll after calling elm.scrollIntoView() may be obviously hacky.

+1 for a solution to this problem

+1 here as well. Probably not uncommon to want a 10-20 pixel offset from the element being scrolled to.

Actually, the underlying scroll functionality comes from https://developer.mozilla.org/en-US/docs/DOM/element.scrollIntoView which doesn't allow for an offset. Doing the $window.scroll after calling elm.scrollIntoView() may be obviously hacky.

Yea, that's why i haven't asked for my code to be pulled in. It's hacky, but i'm not aware of any other way. Anchors on my site would be broken without it heh.

+1 here too.

I wrote the following in my controller. Really awful hack put could not figure any other way. Am new to Angular; but thought i would share.

$scope.goToInternalHash = function(id, offset) {
$location.hash(id);
$anchorScroll();
setTimeout(function(){
window.scrollTo(window.pageXOffset, window.pageYOffset - offset);
},300);

Adding invisible anchor tags with negative relative offset seems like a nice workaround for this issue, e.g. http://stackoverflow.com/a/13184714/295416

Might as well be using angular-ui and bootstrap Scrollspy or wrapping some other plugin in a service.

I have the same problem.

+1 for a solution to this

There are any number of little hacks that can be implemented, but it would be nice if there was something built in.

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1 ( $anchorScroll is cool )

+1

+1 seriously need this

anchorScroll_$get = angular.copy($anchorScrollProvider.$get[3])
$anchorScrollProvider.$get[3] = (t1, t2, t3) ->
scroll = anchorScroll_$get(t1, t2, t3)
dec = () ->
scroll()
setTimeout(() ->
window.scrollTo(window.pageXOffset, window.pageYOffset - 100);
,100)
return dec

+1

+1

+1

Same problem here. +1 for this feature!

+1

+1

+1

+1

+1

+1

+1

+1

+1

+1

For the record I ended up using https://github.com/durated/angular-scroll/

Workaround posted by @trask works as well.

+1

Here's a way to fix that only with css
see: http://jsbin.com/foyelo/9/edit?html,css,js,output

+1

+1

:+1:

I like @a8m's solution that uses css only:

<span class="anchor" id="head1">head1</span>
.anchor {
   padding-top: 40px;
}

demo: http://jsbin.com/foyelo/9/edit

this solution is clean (keeps the layout information in CSS, doesn't require duplicate scrolling, timeout, etc) and flexible. Adding the extra offset info to $anchorScroll will result only in hacks and duplication of layout info between css and js.

unless there is a good reason why the css solution doesn't work, we should close this issue

The drawback of this approach is that it is kind of cubersome:

  • You have to specify a different padding for each screen size (based on the media queries used).
  • You have to account for the widths of the various elements inside the header (e.g. as specified by their col-x-y classes), in order to know when an element will wrap to the next line.
  • You have to manually keep track of any CSS styles affecting the height of an element (e.g. an explicit [min-/max-]height, a line-height etc).
  • If anything changes (e.g. a new element is added, a col-x-y is modified etc), you have to (remember to) recalculate all values again.

It is not that it's unworkable, but it sounds like a tedious (and error-prone) task, and a repetitive one in nature as well (supposing your header content and/or CSS won't stay unchanged forever).

(Of course, this is not specific to the docs app - it's quite often in several projects.)


The fixed offset in $anchorScroll won't address all of the issues either, so how about this:

$anchorScroll will accept either a fixed offset or...[drums please]...an offset-getter function.
It will execute the function each time to get the offset. This will save us a lot of time I think. We'll never have to worry about updating our CSS and account for any tiny change in the header or in CSS styles that might break our anchor scrolling.

(In case it's not clear yet, in the example at hand, we would pass a function that returns the current heght of the fixed header as offset.)

What do you think (@IgorMinar, @petebacondarwin) ?

How about this?

Create a directive that you attach to any headings that you don't want to be obscured.

This directive will insert a new anchor element in front of the heading (as proposed by @IgorMinar), which has a dynamic padding style based either on an expression passed into the directive or based on a config value defined in a service:

Explicit Example

<h1 ng-offset="{{ headerHeight }}">Heading 1</h1>

In this case the offset is computed based on the value of $scope.headerHeight, which can be changed at run time.

Implicit Example

<h1 ng-offset>Heading 1</h1>

In this case the directive could look for a service, say, headingOffset, which can be defined by the application and modified as needed:

.value('headingOffset', { value: 40 });

I will prototype this but my only concern is whether it would work on initial page render before the first digest has run...

@petebacondarwin: I have implemented a similar solution and use in my projects (where I usually have collapsible header menus and the issue is harder to address).
But, a simple solution for simpler cases would be nice.

For what you propose to work ngOffset should evaluate an expression (since the header height might change "at runtime"). The drawback with this approach is that the expression would have to be evaluated at every $digest, in contrast to the $anchorScroll solution, which would only get evaluated when $anchorScrolling (a much rarer task usually).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jtorbicki picture jtorbicki  路  3Comments

ceymard picture ceymard  路  3Comments

tdumitrescu picture tdumitrescu  路  3Comments

jpsimons picture jpsimons  路  3Comments

brijesh1ec picture brijesh1ec  路  3Comments