Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

why jquery position() method does not have a setter version

I'm currently studying jquery and have noticed most of the method in jquery have a setting and getter version. i.e. the method is used as a setter or as a getter, depending on the type/number of arguments passed to the method.

A example would be the jquery offset() method.

But I have also noticed that the jquery position() method only have a getter version, and its setter version is missing.

Does anyone have any idea why the position() method does not have a setter version? I'm not exactly sure but I think a setter version would be useful in certain situations.

like image 663
Thor Avatar asked Apr 11 '18 04:04

Thor


1 Answers

This is because offset() and position() are quite different, both by their purpose and under the hood. Authors tried to explain it in the docs:

The .position() method allows us to retrieve the current position of an element (specifically its margin box) relative to the offset parent (specifically its padding box, which excludes margins and borders). Contrast this with .offset(), which retrieves the current position relative to the document.

But the best explanation is, as usual, in the source. offset() getter is fairly simple: after checking that element's presence in live DOM, compare its box against the document's one, adjust by scroll:

// If we don't have gBCR, just use 0,0 rather than error
// BlackBerry 5, iOS 3 (original iPhone)
if ( typeof elem.getBoundingClientRect !== "undefined" ) {
    box = elem.getBoundingClientRect();
}
win = getWindow( doc );
return {
    top: box.top  + ( win.pageYOffset || docElem.scrollTop )  - ( docElem.clientTop  || 0),
    left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
};

(It's even more straightforward in 2.x branch, btw; no need for gBCR check)


Now, position() getter is relatively simple only if the target element has position: fixed applied - just take the gBCR results for offsets.

Otherwise, things get messy. First, the algorithm should locate the 'real' offsetParent - the closest predecessor with position other than static. If none found, documentElement is used instead:

// key part of `offsetParent()` method
while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) &&
       jQuery.css( offsetParent, "position" ) === "static" ) ) {
    offsetParent = offsetParent.offsetParent;
}
return offsetParent || documentElement;

Then the code calculates the offsetParent offset - uses its coords if it's documentElement, or top: 0, left: 0 instead. And don't forget about borders!

var parentOffset = { top: 0, left: 0 },
// ... later on
if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
    parentOffset = offsetParent.offset();
}
// Add offsetParent borders
parentOffset.top  += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );

Finally, offsets are compared - and now margins of the element are taken into account as well:

// Subtract parent offsets and element margins
// note: when an element has margin: auto the offsetLeft and marginLeft
// are the same in Safari causing offset.left to incorrectly be 0
return {
    top:  offset.top  - parentOffset.top - jQuery.css( elem, "marginTop", true ),
    left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
};

And the third part of the puzzle, offset() setter. Here are the key parts:

// set position first, in-case top/left are set even on static elem
if ( position === "static" ) {
    elem.style.position = "relative";
}

curOffset = curElem.offset();
curCSSTop = jQuery.css( elem, "top" );
curCSSLeft = jQuery.css( elem, "left" );
calculatePosition = ( position === "absolute" || position === "fixed" ) &&
        jQuery.inArray( "auto", [ curCSSTop, curCSSLeft ] ) > -1;

// need to be able to calculate position if either top or left
// is auto and position is either absolute or fixed
if ( calculatePosition ) {
    curPosition = curElem.position();
    curTop = curPosition.top;
    curLeft = curPosition.left;
} else {
    curTop = parseFloat( curCSSTop ) || 0;
    curLeft = parseFloat( curCSSLeft ) || 0;
}

// ...
if ( options.top != null ) {
    props.top = ( options.top - curOffset.top ) + curTop;
}
if ( options.left != null ) {
    props.left = ( options.left - curOffset.left ) + curLeft;
}
// ...
curElem.css( props );

Essentially, it's - again - quite simple: calculate the element's offset, and modify its top and left by the difference. The complicated part covers the case of position: fixed in combination with auto values for either top or left.


When thinking of how position setter might look like, two questions arise. First and foremost, what should it do with static elements? As seen above, offset setter just rewrites their position into relative; should we go the same way here - and deal with offsetParent sudden changes?

Second, each time that setter is called, it should recalculate the padding box of offsetParent. Of course, it hugely depends on use-cases, but - shouldn't we do this calculation only when the element is mount, and redo on layout-changing events? And if we should, perhaps offset() setter alongside existing css({top, left}) already covers all our needs?

Those concerns might explain why this option is not implemented yet in jQuery - despite the fact it's already there in jQuery UI. That plugin doesn't have dependencies on other jQuery UI components, but its latest version is ~500 lines long.

If you do think that it should be a part of jQuery too, just raise a corresponding issue on jQuery tracker; at least I didn't find anything related yet.

like image 145
raina77ow Avatar answered Nov 15 '22 03:11

raina77ow