Skip to content

Commit 8c5a5e1

Browse files
crisbetommalerba
authored andcommitted
refactor(connected-position-strategy): use top/left instead of transforms for positioning (#2024)
* refactor(connected-position-strategy): use top/left instead of transforms for positioning Switches the `ConnectedPositionStrategy` to use `top` and `left` for positioning, instead of `translateX` and `translateY`. This avoids having to do extra work to prevent subpixel rendering issues, while freeing up the transforms to be used on animations or for offsets. * Fix the failing positioning-related tests.
1 parent c592051 commit 8c5a5e1

File tree

7 files changed

+67
-48
lines changed

7 files changed

+67
-48
lines changed

e2e/components/menu/menu-page.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,15 @@ export class MenuPage {
5151
});
5252
}
5353

54-
expectMenuLocation(el: ElementFinder, {x,y}: {x: number, y: number}) {
55-
el.getLocation().then((loc) => {
56-
// Round the values because we expect the menu overlay to also have been rounded
57-
// to avoid blurriness due to subpixel rendering.
58-
expect(loc.x).toEqual(Math.round(x));
59-
expect(loc.y).toEqual(Math.round(y));
54+
expectMenuLocation(el: ElementFinder, {x, y}: {x: number, y: number}) {
55+
el.getLocation().then(loc => {
56+
expect(loc.x).toEqual(x);
57+
expect(loc.y).toEqual(y);
6058
});
6159
}
6260

6361
expectMenuAlignedWith(el: ElementFinder, id: string) {
64-
element(by.id(id)).getLocation().then((loc) => {
62+
element(by.id(id)).getLocation().then(loc => {
6563
this.expectMenuLocation(el, {x: loc.x, y: loc.y});
6664
});
6765
}

e2e/components/menu/menu.e2e.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ describe('menu', () => {
1212
page.trigger().click();
1313

1414
page.expectMenuPresent(true);
15-
expect(page.menu().getText()).toEqual("One\nTwo\nThree\nFour");
15+
expect(page.menu().getText()).toEqual('One\nTwo\nThree\nFour');
1616
});
1717

1818
it('should close menu when menu item is clicked', () => {
@@ -39,7 +39,7 @@ describe('menu', () => {
3939

4040
it('should support multiple triggers opening the same menu', () => {
4141
page.triggerTwo().click();
42-
expect(page.menu().getText()).toEqual("One\nTwo\nThree\nFour");
42+
expect(page.menu().getText()).toEqual('One\nTwo\nThree\nFour');
4343
page.expectMenuAlignedWith(page.menu(), 'trigger-two');
4444

4545
page.backdrop().click();
@@ -48,7 +48,7 @@ describe('menu', () => {
4848
// TODO(kara): temporary, remove when #1607 is fixed
4949
browser.sleep(250);
5050
page.trigger().click();
51-
expect(page.menu().getText()).toEqual("One\nTwo\nThree\nFour");
51+
expect(page.menu().getText()).toEqual('One\nTwo\nThree\nFour');
5252
page.expectMenuAlignedWith(page.menu(), 'trigger');
5353

5454
page.backdrop().click();
@@ -57,7 +57,7 @@ describe('menu', () => {
5757

5858
it('should mirror classes on host to menu template in overlay', () => {
5959
page.trigger().click();
60-
page.menu().getAttribute('class').then((classes) => {
60+
page.menu().getAttribute('class').then(classes => {
6161
expect(classes).toContain('md-menu-panel custom');
6262
});
6363
});
@@ -157,7 +157,7 @@ describe('menu', () => {
157157

158158
it('should align overlay end to origin end when x-position is "before"', () => {
159159
page.beforeTrigger().click();
160-
page.beforeTrigger().getLocation().then((trigger) => {
160+
page.beforeTrigger().getLocation().then(trigger => {
161161

162162
// the menu's right corner must be attached to the trigger's right corner.
163163
// menu = 112px wide. trigger = 60px wide. 112 - 60 = 52px of menu to the left of trigger.
@@ -169,7 +169,7 @@ describe('menu', () => {
169169

170170
it('should align overlay bottom to origin bottom when y-position is "above"', () => {
171171
page.aboveTrigger().click();
172-
page.aboveTrigger().getLocation().then((trigger) => {
172+
page.aboveTrigger().getLocation().then(trigger => {
173173

174174
// the menu's bottom corner must be attached to the trigger's bottom corner.
175175
// menu.x should equal trigger.x because only y position has changed.
@@ -181,7 +181,7 @@ describe('menu', () => {
181181

182182
it('should align menu to top left of trigger when "below" and "above"', () => {
183183
page.combinedTrigger().click();
184-
page.combinedTrigger().getLocation().then((trigger) => {
184+
page.combinedTrigger().getLocation().then(trigger => {
185185

186186
// trigger.x (left corner) - 52px (menu left of trigger) = expected menu.x
187187
// trigger.y (top corner) - 44px (menu above trigger) = expected menu.y

src/lib/core/overlay/overlay-directives.spec.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,9 @@ describe('Overlay directives', () => {
138138
fixture.detectChanges();
139139

140140
const pane = overlayContainerElement.children[0] as HTMLElement;
141-
expect(pane.style.transform)
142-
.toContain(`translateX(${startX + 5}px)`,
141+
142+
expect(pane.style.left)
143+
.toBe(startX + 5 + 'px',
143144
`Expected overlay translateX to equal the original X + the offsetX.`);
144145

145146
fixture.componentInstance.isOpen = false;
@@ -149,8 +150,8 @@ describe('Overlay directives', () => {
149150
fixture.componentInstance.isOpen = true;
150151
fixture.detectChanges();
151152

152-
expect(pane.style.transform)
153-
.toContain(`translateX(${startX + 15}px)`,
153+
expect(pane.style.left)
154+
.toBe(startX + 15 + 'px',
154155
`Expected overlay directive to reflect new offsetX if it changes.`);
155156
});
156157

@@ -167,19 +168,18 @@ describe('Overlay directives', () => {
167168
// expected y value is the starting y + trigger height + offset y
168169
// 30 + 20 + 45 = 95px
169170
const pane = overlayContainerElement.children[0] as HTMLElement;
170-
expect(pane.style.transform)
171-
.toContain(`translateY(95px)`,
172-
`Expected overlay translateY to equal the start Y + height + offsetY.`);
171+
172+
expect(pane.style.top)
173+
.toBe('95px', `Expected overlay translateY to equal the start Y + height + offsetY.`);
173174

174175
fixture.componentInstance.isOpen = false;
175176
fixture.detectChanges();
176177

177178
fixture.componentInstance.offsetY = 55;
178179
fixture.componentInstance.isOpen = true;
179180
fixture.detectChanges();
180-
expect(pane.style.transform)
181-
.toContain(`translateY(105px)`,
182-
`Expected overlay directive to reflect new offsetY if it changes.`);
181+
expect(pane.style.top)
182+
.toBe('105px', `Expected overlay directive to reflect new offsetY if it changes.`);
183183
});
184184

185185
});

src/lib/core/overlay/position/connected-position-strategy.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {PositionStrategy} from './position-strategy';
22
import {ElementRef} from '@angular/core';
33
import {ViewportRuler} from './viewport-ruler';
4-
import {applyCssTransform} from '../../style/apply-transform';
54
import {
65
ConnectionPositionPair,
76
OriginConnectionPosition,
@@ -232,18 +231,11 @@ export class ConnectedPositionStrategy implements PositionStrategy {
232231
* @param overlayPoint
233232
*/
234233
private _setElementPosition(element: HTMLElement, overlayPoint: Point) {
235-
// Round the values to prevent blurry overlays due to subpixel rendering.
236-
let x = Math.round(overlayPoint.x);
237-
let y = Math.round(overlayPoint.y);
238-
239-
// TODO(jelbourn): we don't want to always overwrite the transform property here,
240-
// because it will need to be used for animations.
241-
applyCssTransform(element, `translateX(${x}px) translateY(${y}px)`);
234+
element.style.left = overlayPoint.x + 'px';
235+
element.style.top = overlayPoint.y + 'px';
242236
}
243237
}
244238

245239

246240
/** A simple (x, y) coordinate. */
247241
type Point = {x: number, y: number};
248-
249-

src/lib/menu/menu-trigger.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
HorizontalConnectionPos,
2525
VerticalConnectionPos,
2626
} from '../core';
27-
import { Subscription } from 'rxjs/Subscription';
27+
import {Subscription} from 'rxjs/Subscription';
2828
import {MenuPositionX, MenuPositionY} from './menu-positions';
2929

3030
/**

src/lib/menu/menu.spec.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -148,19 +148,19 @@ describe('MdMenu', () => {
148148

149149
fixture.componentInstance.trigger.openMenu();
150150
fixture.detectChanges();
151-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
151+
const overlayPane = getOverlayPane();
152152
const triggerRect = trigger.getBoundingClientRect();
153153
const overlayRect = overlayPane.getBoundingClientRect();
154154

155155
// In "before" position, the right sides of the overlay and the origin are aligned.
156156
// To find the overlay left, subtract the menu width from the origin's right side.
157157
const expectedLeft = triggerRect.right - overlayRect.width;
158-
expect(overlayRect.left)
158+
expect(Math.round(overlayRect.left))
159159
.toBe(Math.round(expectedLeft),
160160
`Expected menu to open in "before" position if "after" position wouldn't fit.`);
161161

162162
// The y-position of the overlay should be unaffected, as it can already fit vertically
163-
expect(overlayRect.top)
163+
expect(Math.round(overlayRect.top))
164164
.toBe(Math.round(triggerRect.top),
165165
`Expected menu top position to be unchanged if it can fit in the viewport.`);
166166
});
@@ -177,19 +177,19 @@ describe('MdMenu', () => {
177177

178178
fixture.componentInstance.trigger.openMenu();
179179
fixture.detectChanges();
180-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
180+
const overlayPane = getOverlayPane();
181181
const triggerRect = trigger.getBoundingClientRect();
182182
const overlayRect = overlayPane.getBoundingClientRect();
183183

184184
// In "above" position, the bottom edges of the overlay and the origin are aligned.
185185
// To find the overlay top, subtract the menu height from the origin's bottom edge.
186186
const expectedTop = triggerRect.bottom - overlayRect.height;
187-
expect(overlayRect.top)
187+
expect(Math.round(overlayRect.top))
188188
.toBe(Math.round(expectedTop),
189189
`Expected menu to open in "above" position if "below" position wouldn't fit.`);
190190

191191
// The x-position of the overlay should be unaffected, as it can already fit horizontally
192-
expect(overlayRect.left)
192+
expect(Math.round(overlayRect.left))
193193
.toBe(Math.round(triggerRect.left),
194194
`Expected menu x position to be unchanged if it can fit in the viewport.`);
195195
});
@@ -207,18 +207,18 @@ describe('MdMenu', () => {
207207

208208
fixture.componentInstance.trigger.openMenu();
209209
fixture.detectChanges();
210-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
210+
const overlayPane = getOverlayPane();
211211
const triggerRect = trigger.getBoundingClientRect();
212212
const overlayRect = overlayPane.getBoundingClientRect();
213213

214214
const expectedLeft = triggerRect.right - overlayRect.width;
215215
const expectedTop = triggerRect.bottom - overlayRect.height;
216216

217-
expect(overlayRect.left)
217+
expect(Math.round(overlayRect.left))
218218
.toBe(Math.round(expectedLeft),
219219
`Expected menu to open in "before" position if "after" position wouldn't fit.`);
220220

221-
expect(overlayRect.top)
221+
expect(Math.round(overlayRect.top))
222222
.toBe(Math.round(expectedTop),
223223
`Expected menu to open in "above" position if "below" position wouldn't fit.`);
224224
});
@@ -230,23 +230,28 @@ describe('MdMenu', () => {
230230

231231
fixture.componentInstance.trigger.openMenu();
232232
fixture.detectChanges();
233-
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
233+
const overlayPane = getOverlayPane();
234234
const triggerRect = trigger.getBoundingClientRect();
235235
const overlayRect = overlayPane.getBoundingClientRect();
236236

237237
// As designated "before" position won't fit on screen, the menu should fall back
238238
// to "after" mode, where the left sides of the overlay and trigger are aligned.
239-
expect(overlayRect.left)
239+
expect(Math.round(overlayRect.left))
240240
.toBe(Math.round(triggerRect.left),
241241
`Expected menu to open in "after" position if "before" position wouldn't fit.`);
242242

243243
// As designated "above" position won't fit on screen, the menu should fall back
244244
// to "below" mode, where the top edges of the overlay and trigger are aligned.
245-
expect(overlayRect.top)
245+
expect(Math.round(overlayRect.top))
246246
.toBe(Math.round(triggerRect.top),
247247
`Expected menu to open in "below" position if "above" position wouldn't fit.`);
248248
});
249249

250+
function getOverlayPane(): HTMLElement {
251+
let pane = overlayContainerElement.children[0] as HTMLElement;
252+
pane.style.position = 'absolute';
253+
return pane;
254+
}
250255
});
251256

252257
describe('animations', () => {

src/lib/select/select.spec.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,11 @@ describe('MdSelect', () => {
515515
*/
516516
function checkTriggerAlignedWithOption(index: number): void {
517517
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
518+
519+
// We need to set the position to absolute, because the top/left positioning won't work
520+
// since the component CSS isn't included in the tests.
521+
overlayPane.style.position = 'absolute';
522+
518523
const triggerTop = trigger.getBoundingClientRect().top;
519524
const overlayTop = overlayPane.getBoundingClientRect().top;
520525
const options = overlayPane.querySelectorAll('md-option');
@@ -685,6 +690,11 @@ describe('MdSelect', () => {
685690
fixture.detectChanges();
686691

687692
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
693+
694+
// We need to set the position to absolute, because the top/left positioning won't work
695+
// since the component CSS isn't included in the tests.
696+
overlayPane.style.position = 'absolute';
697+
688698
const triggerBottom = trigger.getBoundingClientRect().bottom;
689699
const overlayBottom = overlayPane.getBoundingClientRect().bottom;
690700
const scrollContainer = overlayPane.querySelector('.md-select-panel');
@@ -712,6 +722,11 @@ describe('MdSelect', () => {
712722
fixture.detectChanges();
713723

714724
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
725+
726+
// We need to set the position to absolute, because the top/left positioning won't work
727+
// since the component CSS isn't included in the tests.
728+
overlayPane.style.position = 'absolute';
729+
715730
const triggerTop = trigger.getBoundingClientRect().top;
716731
const overlayTop = overlayPane.getBoundingClientRect().top;
717732
const scrollContainer = overlayPane.querySelector('.md-select-panel');
@@ -740,6 +755,11 @@ describe('MdSelect', () => {
740755
fixture.detectChanges();
741756

742757
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
758+
759+
// We need to set the position to absolute, because the top/left positioning won't work
760+
// since the component CSS isn't included in the tests.
761+
overlayPane.style.position = 'absolute';
762+
743763
const triggerLeft = trigger.getBoundingClientRect().left;
744764
const firstOptionLeft =
745765
overlayPane.querySelector('md-option').getBoundingClientRect().left;
@@ -758,6 +778,11 @@ describe('MdSelect', () => {
758778
fixture.detectChanges();
759779

760780
const overlayPane = overlayContainerElement.children[0] as HTMLElement;
781+
782+
// We need to set the position to absolute, because the top/left positioning won't work
783+
// since the component CSS isn't included in the tests.
784+
overlayPane.style.position = 'absolute';
785+
761786
const triggerRight = trigger.getBoundingClientRect().right;
762787
const firstOptionRight =
763788
overlayPane.querySelector('md-option').getBoundingClientRect().right;
@@ -768,7 +793,6 @@ describe('MdSelect', () => {
768793
.toEqual((triggerRight + 16).toFixed(2),
769794
`Expected trigger to align with the selected option on the x-axis in RTL.`);
770795
});
771-
772796
});
773797

774798
});

0 commit comments

Comments
 (0)