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

Commit fc7e9b3

Browse files
devversionkara
authored andcommitted
fix(util): properly determine viewport top offset (#9458)
* Properly determines the viewport top offset, without any consistency issues for the `scrollTop`. * The `scrollTop` method had a wrong name when passing an element, because it resolved the client rects in relative to the viewport. * Now it determines the viewport top offset from the window (instead of using the body element - this is deprecated) Fixes #9370.
1 parent 0592dfa commit fc7e9b3

File tree

5 files changed

+66
-32
lines changed

5 files changed

+66
-32
lines changed

src/components/autocomplete/js/autocompleteController.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,11 @@ function MdAutocompleteCtrl ($scope, $element, $mdUtil, $mdConstant, $mdTheming,
122122
styles.bottom = bot + 'px';
123123
styles.maxHeight = Math.min(dropdownHeight, hrect.top - root.top - MENU_PADDING) + 'px';
124124
} else {
125+
var bottomSpace = root.bottom - hrect.bottom - MENU_PADDING + $mdUtil.getViewportTop();
126+
125127
styles.top = (top - offset) + 'px';
126128
styles.bottom = 'auto';
127-
styles.maxHeight = Math.min(dropdownHeight, root.bottom + $mdUtil.scrollTop() - hrect.bottom - MENU_PADDING) + 'px';
129+
styles.maxHeight = Math.min(dropdownHeight, bottomSpace) + 'px';
128130
}
129131

130132
elements.$.scrollContainer.css(styles);

src/components/dialog/dialog.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1176,8 +1176,11 @@ function MdDialogProvider($$interimElementProvider) {
11761176
height: container.css('height')
11771177
};
11781178

1179+
// If the body is fixed, determine the distance to the viewport in relative from the parent.
1180+
var parentTop = Math.abs(options.parent[0].getBoundingClientRect().top);
1181+
11791182
container.css({
1180-
top: (isFixed ? $mdUtil.scrollTop(options.parent) : 0) + 'px',
1183+
top: (isFixed ? parentTop : 0) + 'px',
11811184
height: height ? height + 'px' : '100%'
11821185
});
11831186

src/components/input/input.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,8 @@ function inputTextareaDirective($mdUtil, $window, $mdAria, $timeout, $mdGesture)
572572

573573
function onDrag(ev) {
574574
if (!isDragging) return;
575-
element.css('height', startHeight + (ev.pointer.y - dragStart) - $mdUtil.scrollTop() + 'px');
575+
576+
element.css('height', (startHeight + ev.pointer.distanceY) + 'px');
576577
}
577578

578579
function onDragEnd(ev) {

src/core/util/util.js

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -133,18 +133,12 @@ function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $in
133133
},
134134

135135
/**
136-
* Calculate the positive scroll offset
137-
* TODO: Check with pinch-zoom in IE/Chrome;
138-
* https://code.google.com/p/chromium/issues/detail?id=496285
136+
* Determines the absolute position of the viewport.
137+
* Useful when making client rectangles absolute.
138+
* @returns {number}
139139
*/
140-
scrollTop: function(element) {
141-
element = angular.element(element || $document[0].body);
142-
143-
var body = (element[0] == $document[0].body) ? $document[0].body : undefined;
144-
var scrollTop = body ? body.scrollTop + body.parentElement.scrollTop : 0;
145-
146-
// Calculate the positive scroll offset
147-
return scrollTop || Math.abs(element[0].getBoundingClientRect().top);
140+
getViewportTop: function() {
141+
return window.scrollY || window.pageYOffset || 0;
148142
},
149143

150144
/**
@@ -251,37 +245,39 @@ function UtilFactory($document, $timeout, $compile, $rootScope, $$mdAnimate, $in
251245

252246
// Converts the body to a position fixed block and translate it to the proper scroll position
253247
function disableBodyScroll() {
254-
var htmlNode = body.parentNode;
255-
var restoreHtmlStyle = htmlNode.style.cssText || '';
256-
var restoreBodyStyle = body.style.cssText || '';
257-
var scrollOffset = $mdUtil.scrollTop(body);
248+
var documentElement = $document[0].documentElement;
249+
250+
var prevDocumentStyle = documentElement.style.cssText || '';
251+
var prevBodyStyle = body.style.cssText || '';
252+
253+
var viewportTop = $mdUtil.getViewportTop();
258254
var clientWidth = body.clientWidth;
259255

260256
if (body.scrollHeight > body.clientHeight + 1) {
261-
applyStyles(body, {
257+
258+
angular.element(body).css({
262259
position: 'fixed',
263260
width: '100%',
264-
top: -scrollOffset + 'px'
261+
top: -viewportTop + 'px'
265262
});
266263

267-
htmlNode.style.overflowY = 'scroll';
264+
documentElement.style.overflowY = 'scroll';
268265
}
269266

270-
if (body.clientWidth < clientWidth) applyStyles(body, {overflow: 'hidden'});
267+
if (body.clientWidth < clientWidth) {
268+
body.style.overflow = 'hidden';
269+
}
271270

272271
return function restoreScroll() {
273-
body.style.cssText = restoreBodyStyle;
274-
htmlNode.style.cssText = restoreHtmlStyle;
275-
body.scrollTop = scrollOffset;
276-
htmlNode.scrollTop = scrollOffset;
272+
// Reset the inline style CSS to the previous.
273+
body.style.cssText = prevBodyStyle;
274+
documentElement.style.cssText = prevDocumentStyle;
275+
276+
// The body loses its scroll position while being fixed.
277+
body.scrollTop = viewportTop;
277278
};
278279
}
279280

280-
function applyStyles(el, styles) {
281-
for (var key in styles) {
282-
el.style[key] = styles[key];
283-
}
284-
}
285281
},
286282
enableScrolling: function() {
287283
var method = this.disableScrollAround._enableScrolling;

src/core/util/util.spec.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,42 @@ describe('util', function() {
223223
// Restore the scrolling.
224224
enableScrolling();
225225
window.scrollTo(0, 0);
226-
document.body.removeChild(element[0]);
226+
227+
element.remove();
227228
}));
228229
});
229230

231+
describe('getViewportTop', function() {
232+
233+
it('should properly determine the top offset', inject(function($mdUtil) {
234+
var viewportHeight = Math.round(window.innerHeight);
235+
var element = angular.element('<div style="height: ' + (viewportHeight * 2) + 'px">');
236+
237+
document.body.appendChild(element[0]);
238+
239+
// Scroll down the viewport height.
240+
window.scrollTo(0, viewportHeight);
241+
242+
expect(getViewportTop()).toBe(viewportHeight);
243+
244+
// Restore the scrolling.
245+
window.scrollTo(0, 0);
246+
247+
expect(getViewportTop()).toBe(0);
248+
249+
element.remove();
250+
251+
/*
252+
* Round the viewport top offset because the test browser might be resized and
253+
* could cause deviations for the test.
254+
*/
255+
function getViewportTop() {
256+
return Math.round($mdUtil.getViewportTop());
257+
}
258+
}));
259+
260+
});
261+
230262
describe('nextTick', function() {
231263
it('should combine multiple calls into a single digest', inject(function($mdUtil, $rootScope, $timeout) {
232264
var digestWatchFn = jasmine.createSpy('watchFn');

0 commit comments

Comments
 (0)