Skip to content

Commit 560bb59

Browse files
Fix issue where Element#clonePosition could give wrong results when the page is scrolled. [close prototypejs#305]
1 parent 3caa54b commit 560bb59

File tree

3 files changed

+84
-20
lines changed

3 files changed

+84
-20
lines changed

src/prototype/dom/layout.js

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,6 +1357,9 @@
13571357
* not it is part of the same [CSS containing
13581358
* block](http://www.w3.org/TR/CSS21/visudet.html#containing-block-details).
13591359
*
1360+
* Also note that `element` must already be `position: absolute` or
1361+
* `position: fixed`. This method will not apply a `position` style.
1362+
*
13601363
* ##### Options
13611364
*
13621365
* <table class='options'>
@@ -1416,14 +1419,23 @@
14161419
element = $(element);
14171420
var p, delta, layout, styles = {};
14181421

1422+
var isAbsolute = Element.getStyle(element, 'position') === 'absolute';
1423+
var parent = Element.getOffsetParent(element);
1424+
14191425
if (options.setLeft || options.setTop) {
1426+
// We start by measuring the source's viewport offset.
14201427
p = Element.viewportOffset(source);
1428+
1429+
// If the element we're altering is `position: fixed`, that's all the
1430+
// information we need: later we'll apply that offset to the `top` and
1431+
// `left` properties directly.
14211432
delta = [0, 0];
1422-
// A delta of 0/0 will work for `positioned: fixed` elements, but
1423-
// for `position: absolute` we need to get the parent's offset.
1424-
if (Element.getStyle(element, 'position') === 'absolute') {
1425-
var parent = Element.getOffsetParent(element);
1426-
if (parent !== document.body) delta = Element.viewportOffset(parent);
1433+
1434+
// But if it's `position: absolute`, we have to know where its offset
1435+
// parent is positioned and take those measurements into account as
1436+
// well.
1437+
if (isAbsolute && parent !== document.body) {
1438+
delta = Element.viewportOffset(parent);
14271439
}
14281440
}
14291441

@@ -1440,27 +1452,30 @@
14401452
return { x: x, y: y };
14411453
}
14421454

1443-
var pageXY = pageScrollXY();
1444-
1445-
1446-
if (options.setWidth || options.setHeight) {
1447-
layout = Element.getLayout(source);
1448-
}
1455+
// When the offset parent is the document body, we need to account for
1456+
// scroll offsets when we set `top` and `left`. (Unless the element is
1457+
// `position: fixed`; in that case we should always ignore scroll
1458+
// position.)
1459+
var pageXY = (isAbsolute && parent === document.body) ? pageScrollXY() : { x: 0, y: 0 };
14491460

14501461
// Set position.
14511462
if (options.setLeft)
14521463
styles.left = (p[0] + pageXY.x - delta[0] + options.offsetLeft) + 'px';
14531464
if (options.setTop)
14541465
styles.top = (p[1] + pageXY.y - delta[1] + options.offsetTop) + 'px';
14551466

1456-
// Use content box when setting width/height. If padding/border are
1457-
// different between source and target, that's for the user to fix;
1458-
// there's no good option for us.
1459-
if (options.setWidth) {
1460-
styles.width = layout.get('width') + 'px';
1461-
}
1462-
if (options.setHeight) {
1463-
styles.height = layout.get('height') + 'px';
1467+
if (options.setWidth || options.setHeight) {
1468+
layout = Element.getLayout(source);
1469+
1470+
// Use content box when setting width/height. If padding/border are
1471+
// different between source and target, that's for the user to fix;
1472+
// there's no good option for us.
1473+
if (options.setWidth) {
1474+
styles.width = layout.get('width') + 'px';
1475+
}
1476+
if (options.setHeight) {
1477+
styles.height = layout.get('height') + 'px';
1478+
}
14641479
}
14651480

14661481
return Element.setStyle(element, styles);

test/unit/tests/layout.test.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,43 @@ suite("Layout", function(){
394394
assert.equal(before, after);
395395
});
396396

397+
test('#clonePosition (when element is absolutely positioned and has a non-body offset parent)', function () {
398+
var opts = { offsetTop: 20, offsetLeft: 0, setWidth: false, setHeight: false };
399+
400+
var subMenu = $('sub_menu_2');
401+
var mainMenu = $('main_menu_2');
402+
403+
subMenu.clonePosition(mainMenu, opts);
404+
var offset = subMenu.viewportOffset().top - mainMenu.viewportOffset().top;
405+
406+
assert.equal(offset, 20);
407+
408+
scrollTo(0, 300);
409+
410+
subMenu.clonePosition(mainMenu, opts);
411+
offset = subMenu.viewportOffset().top - mainMenu.viewportOffset().top;
412+
assert.equal(offset, 20);
413+
});
414+
415+
test('#clonePosition (when element has fixed position)', function () {
416+
var opts = { offsetTop: 20, offsetLeft: 0, setWidth: false, setHeight: false };
417+
418+
var subMenu = $('sub_menu_3');
419+
var mainMenu = $('main_menu_3');
420+
421+
subMenu.clonePosition(mainMenu, opts);
422+
var offset = subMenu.viewportOffset().top - mainMenu.viewportOffset().top;
423+
424+
assert.equal(offset, 20);
425+
426+
scrollTo(0, 300);
427+
428+
subMenu.clonePosition(mainMenu, opts);
429+
offset = subMenu.viewportOffset().top - mainMenu.viewportOffset().top;
430+
assert.equal(offset, 20);
431+
432+
});
433+
397434
test('#clonePosition (when elements have the same size)', function() {
398435
var source = $('clone_position_source');
399436
var target = $('clone_position_target');

test/unit/views/tests/layout.erb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@
301301
</style>
302302

303303
<style type="text/css" media="screen">
304-
#sub_menu {
304+
#sub_menu, #sub_menu_2, #sub_menu_3 {
305305
position: absolute;
306306
top: 250px;
307307
left: 250px;
@@ -314,6 +314,18 @@
314314
<div id="scroller" style="height: 2000px;">Force scroll</div>
315315
<div id="sub_menu">Sub Menu</div>
316316

317+
<div id="menu_container" style="position: relative;">
318+
<div id="main_menu_2" style="background-color: #0f0;">Main Menu</div>
319+
320+
<div id="sub_menu_2">Sub Menu</div>
321+
</div>
322+
323+
<div id="menu_container_3" style="position: relative;">
324+
<div id="main_menu_3" style="background-color: #0f0;">Main Menu</div>
325+
</div>
326+
327+
<div id="sub_menu_3" style="position: fixed;">Sub Menu</div>
328+
317329

318330
<style type="text/css" media="screen">
319331
#clone_position_source {

0 commit comments

Comments
 (0)