Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

ANCHOR SCROLL: WIP - feat($anchorScroll): add support for configurable scroll offset #9371

Closed
wants to merge 3 commits into from

Conversation

gkalpak
Copy link
Member

@gkalpak gkalpak commented Oct 1, 2014

Add support for a configurable scroll offset to $anchorScrollProvider.
The offset is expressed in pixels and can be specified either as a fixed value or as a function that returns the offset it pixels dynamically.
(This is a POC and a WIP: no docs, no tests)

Related to #9368

Closes #2070, #9360


var style = window.getComputedStyle(header);
return (style.position === 'fixed') ? header.offsetHeight : 0;
});
Copy link
Member Author

@gkalpak gkalpak Oct 1, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, don't scream about this. I know :)
(It's ugly, it's not-Angular, it's hacky, it's hurting your eyes and I will remove it asap - just wanted to get some feedback on the offset approach.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or for POC you could have just done:

.config(['$anchorScrollProvider', function($anchorScrollProvider) {
  $anchorScrollProvider.setScrollOffset(function () { return 100; });
});

:-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I guess that you wanted to see it change as the header changed size...

if (offset) {
$window.scrollBy(0, -1 * offset);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be:

function scrollTo(elem) {
  if (elem) {
    elem.scrollIntoView();
    var offset = scrollOffsetGetter();
    if (offset) {
      $window.scrollBy(0, -1 * offset);
    }
  } else {
    $window.scrollTo(0, 0);
  }
}

Otherwise scrolling to the top never gets to 0, no?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I forgot about the .main-body-grid { margin-top: 120px; CSS style.

@petebacondarwin
Copy link
Contributor

I kind of like this...

@petebacondarwin petebacondarwin changed the title WIP - feat($anchorScroll): add support for configurable scroll offset ANCHOR SCROLL: WIP - feat($anchorScroll): add support for configurable scroll offset Oct 1, 2014
@petebacondarwin petebacondarwin added this to the 1.3.0-rc.5 milestone Oct 1, 2014
@petebacondarwin petebacondarwin self-assigned this Oct 1, 2014
@petebacondarwin
Copy link
Contributor

Actually the more I think about this, a finessed version of this seems like the right way to go

@gkalpak
Copy link
Member Author

gkalpak commented Oct 2, 2014

I updated the $anchorScroll docs and implemented the dynamic scroll offset in the docs app in a more proper way.

Unfortunately, there seems to be an issue (for a demo see the example I added in the $anchorScroll docs). In a nutshell, when the scrolled element is not the body, then scrollBy does not work.
(I haven't fully tracked down the problem, so there might be other things playing role.) :(

Possible solutions:

  1. Document that in order for scroll offset to work, you need to have the body be scrolled (not some other element). - [Ugly and unconvincing, but easy to (not) implement]
  2. Somehow detect the scrolled element and decrement its scrollTop. - [How to detect the scrolled element ? What if there are multiple scrolled elements]
  3. Define an acompanying directive, that will designate the scrolled element (i.e. the element from whose scrollTop we should substract the offset). - [Might be the more reliable solution, but requires a new directive and possibly extending the $anchorScroll API]

@petebacondarwin any thoughts

@gkalpak
Copy link
Member Author

gkalpak commented Oct 2, 2014

I added some code to prevent elements near the bottom of the scrolled element to be scrolled further down than required.

@petebacondarwin
Copy link
Contributor

I made some changes here: gkalpak#1

@gkalpak
Copy link
Member Author

gkalpak commented Oct 3, 2014

@petebacondarwin , a little update: fb71268

All the functionality should be in place (still missing updated docs and tests).

I tried to do a little research and I came to the conclusion that the most consistent and accurate (at least enough for our purpose) way of aquiring the element's height is using its offsetHeight property. It takes into account content, padding, scrollbars and border (so no box-sizing issues) and works reliably in Chrome 37, Firefox 32 and IE8-IE11.
I used this fiddle (should anyone find it useful).

I also added a helper to calculate the accumulated offsetTop to get the element's top position, because computedStyle.top was failing where top was set to auto.

I put back the fix to avoid scrolling more than it is necessary for elements near the end of the page.

I added a helper function to get an element's [computed]Style, with fallbacks for when getComputedStyle() is not available. More specifically, element.currentStyle (which is proprietary to IE) seems to work as expected in IE8 (where getComputedStyle() is missing) and if currentStyle is not available I fall back to element.style (so as a final resort, one could add inline styles to make the offset work in browsers that support neither getComputedStyle() nor currentStyle - not that I know of any such browser).
(I haven't tested it, but the code should be IE8 compatible, so maybe backportable to 1.2.x.)

Finally, I removed two redundant calls to $anchorScroll() from the docs' examples (since automatic scrolling is not disabled).


I have put a couple of large inline comments , which I am not a big fan of. If you have any idea how to make them more elegant or concise...

I will finish up with the docs tonight and write some tests tomorrow.

In the meantime, please feel free to tell me wdyt :)

* - **number**: A fixed number of pixels to be used as offset.<br /><br />
* - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return
* a number representing the offset (in pixels).<br /><br />
* - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The sum of the
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@petebacondarwin
Right now we only need the "raw" DOM element (not the jqLite element), but still expect a jqLite element.
Should we modify the implementation so both raw and jqLite work (or do you think it complicates the "contract" for no real benefit) ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave it as a wrapped element for now, it is easier to add public API that remove it.

@petebacondarwin
Copy link
Contributor

I'll take a look tomorrow. Thanks

return $window.getComputedStyle ?
$window.getComputedStyle(elem) :
elem.currentStyle || elem.style || {};
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are not supporting IE8 in 1.3.x so this can go away. Since this is a new feature it won't be backported to 1.2.x

ref: http://caniuse.com/#search=getComputedStyle

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It never occurred me that 1.2.x only receives fixes (no new features). It makes sense.
No worries for IE8 then (yay) ! I'll remove the unnecessary code.

@gkalpak
Copy link
Member Author

gkalpak commented Oct 7, 2014

@caitp : Aha!

@petebacondarwin :
There is an issue with Firefox and IE (but not Chrome):
When loading a page with some hash in the URL, Firefox and IE seem to do the "native scrolling"
AFTER $anchorScroll has done its scrolling, thus ruining the yOffset.
E.g. if you navigate to http://localhost:8000/build/docs/api/ng/service/$anchorScroll#usage it will initially scroll correctly (accounting for the yOffset), but then instantly Firefox will scroll the element to the top (not aware of the yOffset). Subsequent calls to $anchorScroll() work as expected.
If you first navigate to http://localhost:8000/build/docs/api/ng/service/$anchorScroll, then append #usage to the URL and press ENTER, it works as expected.

It probably has something to do with Firefox (and IE) doing their native scrolling after something has happened (but I haven't found out what that something is - tried to wait for the load event but no luck).

The only workaround I have found thus far is changing the scrollYOffsetElement like this (but I am too reluctant to push this - it's ugly and unreliable):

.directive('scrollYOffsetElement', ['$anchorScroll', '$timeout', function($anchorScroll, $timeout) {
  return function(scope, element) {
    $anchorScroll.yOffset = element;
   $timeout($anchorScroll, 1000);
  };
}]);

Ideas anyone ?

@petebacondarwin
Copy link
Contributor

I believe Igor has a fix for this. ..
On 7 Oct 2014 19:02, "Georgios Kalpakas" notifications@github.com wrote:

@caitp https://github.com/caitp : Aha!

@petebacondarwin https://github.com/petebacondarwin :
There is an issue with Firefox and IE (but not Chrome):
When loading a page with some hash in the URL, Firefox and IE seem to do
the "native scrolling"
AFTER $anchorScroll has done its scrolling, thus ruining the yOffset.
E.g. if you navigate to
http://localhost:8000/build/docs/api/ng/service/$anchorScroll#usage it
will initially scroll correctly (accounting for the yOffset), but then
instantly Firefox will scroll the element to the top (not aware of the
yOffset). Subsequent calls to $anchorScroll() work as expected.
If you first navigate to
http://localhost:8000/build/docs/api/ng/service/$anchorScroll, then
append #usage to the URL and press ENTER, it works as expected.

It probably has something to do with Firefox (and IE) doing their native
scrolling after something has happened (but I haven't found out what that
something is - tried to wait for the load event but no luck).

The only workaround I have found thus far is changing the
scrollYOffsetElement like this (but I am too reluctant to push this -
it's ugly and unreliable):

.directive('scrollYOffsetElement', ['$anchorScroll', '$timeout', function($anchorScroll, $timeout) {
return function(scope, element) {
$anchorScroll.yOffset = element;
$timeout($anchorScroll, 1000);
};
}]);

Ideas anyone ?


Reply to this email directly or view it on GitHub
#9371 (comment).

@gkalpak
Copy link
Member Author

gkalpak commented Oct 7, 2014

Little update on the Firefox/IE issue
Curiously enough I can't seem to be able to reproduce on IE any more. It's just a Firefox issue now.
Seems to be related to the readyState of the document.

@gkalpak
Copy link
Member Author

gkalpak commented Oct 7, 2014

Does Igor have a drawer with fixes for all sorts of stuff or something :P

@gkalpak
Copy link
Member Author

gkalpak commented Oct 7, 2014

@petebacondarwin : Delaying scrolling until readyState === 'complete' solves the problem on Firefox but not in IE (I was able to re-reproduce it on IE).
On IE, the issue appears when navigating to the page, but not when refreshing (while already on the page) although the page is reloaded anyway.

@petebacondarwin
Copy link
Contributor

The fix I was thinking of was #9393 but it is probably not relevant to this problem. I will take a look this morning.

@gkalpak gkalpak force-pushed the add-offset-to-$anchorScroll branch from 00dbb36 to 8967fd7 Compare October 8, 2014 06:52
@gkalpak
Copy link
Member Author

gkalpak commented Oct 8, 2014

@petebacondarwin : I pushed gkalpak@8967fd7 that fixes the issue in Firefox.

(As mentioned earlier, applying a sufficient timeout works around the issue, but...)

@gkalpak
Copy link
Member Author

gkalpak commented Oct 8, 2014

@petebacondarwin : Yay, tests pass !
A couple of questions:

  1. Is there any easy way to test that scrolling is "deferred" until after readyState === 'complete' ?

    E.g. somehow providing arbitrary values for document.readyState, but without having to mock all methods of document used by $anchorScroll[Provider].
  2. Is there a more efficient way to test some change in the docs app than grunt package + wait until its packaged + wait some more + oh, I am still waiting...

@petebacondarwin
Copy link
Contributor

  1. ... I think mocking is the way forward
  2. If you are only making changes to the app code and not regenerating the docs you can do
cd docs
gulp build-app

If you made changes to templates and need to rebuild docs you can do

grunt docs

Or

cd docs
gulp doc-gen

or to rebuld docs and generate the app

cd docs
gulp

You get the idea. It is good to run grunt package now and again any way to ensure that you have a clean full build.

@gkalpak
Copy link
Member Author

gkalpak commented Oct 8, 2014

BTW, I pushed a commit with some tests regarding document.readyState (although one of them might be flawed - looking into it).

(Thx, @petebacondarwin , for the docs-app-building info !)

}
});
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this function is called from within a $watch handler so it is already in a digest phase, but the readystatechange handler is not inside a $digest. So I think this should look like:

function scrollWhenReady() {
  if (document.readyState === 'complete') {
    $rootScope.$evalAsync(scroll);
  } else if (!scrollScheduled) {
    scrollScheduled = true;
    document.addEventListener('readystatechange', function unbindAndScroll() {
      $rootScope.$apply(function() {
        if (document.readyState === 'complete') {
          document.removeEventListener('readystatechange', unbindAndScroll);
          scroll();
        }
      });
    });
  }
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For good measure it might be best to set scrollScheduled back to false in the handler just in case in the future we want to call it again.

@gkalpak
Copy link
Member Author

gkalpak commented Oct 8, 2014

WARNING: Although the tests passed, when I run them locally, I come across some inconsistent behaviour with Firefox:
Sometimes all tests pass, sometimes some tests fail (the number of failing tests varies from 1 to ~20).

@petebacondarwin
Copy link
Contributor

I'll take a look tonight
On 8 Oct 2014 17:52, "Georgios Kalpakas" notifications@github.com wrote:

WARNING: Although the tests passed, when I run them locally, I come across
some inconsistent behaviour with Firefox:
Sometimes all tests pass, sometimes some tests fail (the number of failing
tests varies from 1 to ~20).


Reply to this email directly or view it on GitHub
#9371 (comment).

@jeffbcross jeffbcross force-pushed the master branch 2 times, most recently from abdaab7 to 30996f8 Compare October 8, 2014 19:47
@btford btford modified the milestones: 1.3.0-rc.5, 1.3.0-rc.6 Oct 8, 2014
@gkalpak
Copy link
Member Author

gkalpak commented Oct 9, 2014

You are right :(

@gkalpak gkalpak force-pushed the add-offset-to-$anchorScroll branch 2 times, most recently from 50a9ab6 to b32a767 Compare October 9, 2014 11:59
gkalpak and others added 3 commits October 9, 2014 22:07
Add support for a configurable scroll offset to $anchorScrollProvider.
The offset is expressed in pixels and can be specified either as a fixed
value or as a function that return the offset it pixels dynamically.
(This is a POC and a WIP: no docs, no tests)

Related to angular#9368

Closes angular#2070, angular#9360
…ents

Finalize the implementation, fix scrolling for elements near the end of
the page, make sure the values calculated for top-offset and height are
accurate (and unaffected by box-sizing or offset-parents).
Separately document $anchorScrollProvider, document the newly added
`yOffset` property of $anchorScroll, fix minor issues in the examples and
overall improve the docs.
Add tests for `$anchorScroll.yOffset` and make some necessary
modifications to the existing test helper functions.
@gkalpak gkalpak force-pushed the add-offset-to-$anchorScroll branch from d66450e to 92e5a05 Compare October 9, 2014 19:13
@gkalpak
Copy link
Member Author

gkalpak commented Oct 9, 2014

Closing this in favor of #9517.

@gkalpak gkalpak closed this Oct 9, 2014
@gkalpak gkalpak deleted the add-offset-to-$anchorScroll branch October 13, 2014 10:40
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

$anchorScroll Offset
5 participants