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

Commit cfa6844

Browse files
committed
fix($anchorScroll): fix scrolling with element as yOffset in Firefox, IE
Firefox and IE use the `<html>` element as the viewport, while Chrome uses the `<body>`. This caused scrolling to not work correctly when an element was set as `yOffset`. Using `window.pageYOffset` instead, seems to work consistently across all supported browsers.
1 parent ea8aba8 commit cfa6844

File tree

2 files changed

+50
-16
lines changed

2 files changed

+50
-16
lines changed

src/ng/anchorScroll.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,10 @@ function $AnchorScrollProvider() {
222222
// case for elements near the bottom of the page.
223223
// In such cases we do not need to scroll the whole `offset` up, just the fraction of the
224224
// offset that is necessary to align the top of `elem` at the desired position.
225-
var body = document.body;
226-
var bodyRect = body.getBoundingClientRect();
227-
var elemRect = elem.getBoundingClientRect();
228-
var necessaryOffset = offset - (elemRect.top - (bodyRect.top + body.scrollTop));
225+
var elemTop = elem.getBoundingClientRect().top;
226+
var bodyTop = document.body.getBoundingClientRect().top;
227+
var scrollTop = $window.pageYOffset;
228+
var necessaryOffset = offset - (elemTop - (bodyTop + scrollTop));
229229

230230
$window.scrollBy(0, -1 * necessaryOffset);
231231
}

test/ng/anchorScrollSpec.js

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@
33
describe('$anchorScroll', function() {
44

55
var elmSpy;
6+
var windowSpies;
67

78
function addElements() {
89
var elements = sliceArgs(arguments);
910

1011
return function($window) {
1112
forEach(elements, function(identifier) {
12-
var match = identifier.match(/(\w* )?(\w*)=(\w*)/),
13-
jqElm = jqLite('<' + (match[1] || 'a ') + match[2] + '="' + match[3] + '"/>'),
13+
var match = identifier.match(/(?:(\w*) )?(\w*)=(\w*)/),
14+
nodeName = match[1] || 'a',
15+
tmpl = '<' + nodeName + ' ' + match[2] + '="' + match[3] + '">' +
16+
match[3] + // add some content or else Firefox and IE place the element
17+
// in weird ways that break yOffset-testing.
18+
'</' + nodeName + '>',
19+
jqElm = jqLite(tmpl),
1420
elm = jqElm[0];
1521

1622
elmSpy[identifier] = spyOn(elm, 'scrollIntoView');
@@ -20,7 +26,7 @@ describe('$anchorScroll', function() {
2026
}
2127

2228
function callAnchorScroll() {
23-
return function ($anchorScroll) {
29+
return function($anchorScroll) {
2430
$anchorScroll();
2531
};
2632
}
@@ -33,7 +39,7 @@ describe('$anchorScroll', function() {
3339
}
3440

3541
function changeHashTo(hash) {
36-
return function ($anchorScroll, $location, $rootScope) {
42+
return function($anchorScroll, $location, $rootScope) {
3743
$rootScope.$apply(function() {
3844
$location.hash(hash);
3945
});
@@ -73,14 +79,29 @@ describe('$anchorScroll', function() {
7379
return expectScrollingTo(NaN);
7480
}
7581

82+
function resetAllSpies() {
83+
function resetSpy(spy) {
84+
spy.reset();
85+
}
86+
87+
return function($window) {
88+
forEach(elmSpy, resetSpy);
89+
forEach(windowSpies, resetSpy);
90+
};
91+
}
92+
7693

7794
beforeEach(module(function($provide) {
7895
elmSpy = {};
96+
windowSpies = [];
97+
var i = 0;
98+
7999
$provide.value('$window', {
80-
scrollTo: jasmine.createSpy('$window.scrollTo'),
81-
scrollBy: jasmine.createSpy('$window.scrollBy'),
100+
scrollTo: (windowSpies[i++] = jasmine.createSpy('$window.scrollTo')),
101+
scrollBy: (windowSpies[i++] = jasmine.createSpy('$window.scrollBy')),
82102
document: document,
83103
navigator: {},
104+
pageYOffset: 0,
84105
getComputedStyle: function(elem) {
85106
return getComputedStyle(elem);
86107
}
@@ -251,7 +272,10 @@ describe('$anchorScroll', function() {
251272
expectScrollingTo(identifierCountMap)($window);
252273
expect($window.scrollBy.calls.length).toBe(list.length);
253274
forEach(list, function(offset, idx) {
254-
expect($window.scrollBy.calls[idx].args).toEqual([0, -1 * offset]);
275+
// Due to sub-pixel rendering, there is a +/-1 error margin in the actual offset
276+
var args = $window.scrollBy.calls[idx].args;
277+
expect(args[0]).toBe(0);
278+
expect(Math.abs(offset + args[1])).toBeLessThan(1);
255279
});
256280
};
257281
}
@@ -270,11 +294,17 @@ describe('$anchorScroll', function() {
270294
}
271295

272296
function setYOffset(yOffset) {
273-
return function ($anchorScroll) {
297+
return function($anchorScroll) {
274298
$anchorScroll.yOffset = yOffset;
275299
};
276300
}
277301

302+
function updateMockPageYOffset() {
303+
return function($window) {
304+
$window.pageYOffset = window.pageYOffset;
305+
};
306+
}
307+
278308
beforeEach(inject(setupBodyForOffsetTesting()));
279309

280310
afterEach(inject(function($document) {
@@ -323,16 +353,20 @@ describe('$anchorScroll', function() {
323353
'padding:0',
324354
''].join(';');
325355

326-
forEach($window.document.body.children, function (elem) {
356+
forEach($window.document.body.children, function(elem) {
327357
elem.style.cssText = cssText;
328358
});
329359

330-
// Make sure scrolling does actually take place (it is necessary for this test)
331-
forEach(elmSpy, function (spy, identifier) {
360+
// Make sure scrolling does actually take place
361+
// (this is necessary for the current test)
362+
forEach(elmSpy, function(spy, identifier) {
332363
elmSpy[identifier] = spy.andCallThrough();
333364
});
334365
},
335366
changeHashTo('some2'),
367+
updateMockPageYOffset(),
368+
resetAllSpies(),
369+
callAnchorScroll(),
336370
expectScrollingWithOffset('id=some2', targetAdjustedOffset));
337371
});
338372
});
@@ -380,7 +414,7 @@ describe('$anchorScroll', function() {
380414

381415
var jqElem = jqLite('<div style="' + cssText + '"></div>');
382416

383-
return function ($anchorScroll, $window) {
417+
return function($anchorScroll, $window) {
384418
jqLite($window.document.body).append(jqElem);
385419
$anchorScroll.yOffset = jqElem;
386420
};

0 commit comments

Comments
 (0)