"./css/curCSS",
"./css/addGetHookIf",
"./css/support",
- "./core/nodeName",
"./core/init",
"./css",
"./selector" // contains
], function( jQuery, access, document, documentElement, rnumnonpx,
- curCSS, addGetHookIf, support, nodeName ) {
+ curCSS, addGetHookIf, support ) {
"use strict";
};
jQuery.fn.extend( {
+
+ // offset() relates an element's border box to the document origin
offset: function( options ) {
// Preserve chaining for setter
} );
}
- var doc, docElem, rect, win,
+ var rect, win,
elem = this[ 0 ];
if ( !elem ) {
return { top: 0, left: 0 };
}
+ // Get document-relative position by adding viewport scroll to viewport-relative gBCR
rect = elem.getBoundingClientRect();
-
- doc = elem.ownerDocument;
- docElem = doc.documentElement;
- win = doc.defaultView;
-
+ win = elem.ownerDocument.defaultView;
return {
- top: rect.top + win.pageYOffset - docElem.clientTop,
- left: rect.left + win.pageXOffset - docElem.clientLeft
+ top: rect.top + win.pageYOffset,
+ left: rect.left + win.pageXOffset
};
},
+ // position() relates an element's margin box to its offset parent's padding box
+ // This corresponds to the behavior of CSS absolute positioning
position: function() {
if ( !this[ 0 ] ) {
return;
}
- var offsetParent, offset,
+ var offsetParent, offset, doc,
elem = this[ 0 ],
parentOffset = { top: 0, left: 0 };
- // Fixed elements are offset from window (parentOffset = {top:0, left: 0},
- // because it is its only offset parent
+ // position:fixed elements are offset from the viewport, which itself always has zero offset
if ( jQuery.css( elem, "position" ) === "fixed" ) {
- // Assume getBoundingClientRect is there when computed position is fixed
+ // Assume position:fixed implies availability of getBoundingClientRect
offset = elem.getBoundingClientRect();
} else {
+ offset = this.offset();
- // Get *real* offsetParent
- offsetParent = this.offsetParent();
+ // Account for the *real* offset parent, which can be the document or its root element
+ // when a statically positioned element is identified
+ doc = elem.ownerDocument;
+ offsetParent = elem.offsetParent || doc.documentElement;
+ while ( offsetParent &&
+ ( offsetParent === doc.body || offsetParent === doc.documentElement ) &&
+ jQuery.css( offsetParent, "position" ) === "static" ) {
- // Get correct offsets
- offset = this.offset();
- if ( !nodeName( offsetParent[ 0 ], "html" ) ) {
- parentOffset = offsetParent.offset();
+ offsetParent = offsetParent.parentNode;
+ }
+ if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {
+
+ // Incorporate borders into its offset, since they are outside its content origin
+ parentOffset = jQuery( offsetParent ).offset();
+ parentOffset = {
+ top: parentOffset.top + jQuery.css( offsetParent, "borderTopWidth", true ),
+ left: parentOffset.left + jQuery.css( offsetParent, "borderLeftWidth", true )
+ };
}
-
- // Add offsetParent borders
- parentOffset = {
- top: parentOffset.top + jQuery.css( offsetParent[ 0 ], "borderTopWidth", true ),
- left: parentOffset.left + jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true )
- };
}
// Subtract parent offsets and element margins
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html id="documentElement" class="box">
+ <head>
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+ <meta name="description" content="horizontal values 2^N; vertical doubled">
+ <title>Nonempty margin/border/padding/position</title>
+ <style type="text/css" media="screen">
+ /* work with convenient classes, units, and dimensions */
+ .static { position: static; }
+ .relative { position: relative; }
+ .absolute { position: absolute; }
+ .fixed { position: fixed; }
+ .box {
+ font-size: 4px;
+ border-style: solid;
+ min-width: 300px;
+ }
+
+ /* start the exponential scales, reserving the first bit for scroll position */
+ .box {
+ border-width: 1em 0.5em;
+ top: 2em; left: 1em;
+ margin: 4em 2em;
+ padding: 8em 4em;
+ }
+ #documentElement {
+ margin: 16em 8em;
+ border-width: 32em 16em;
+ padding: 64em 32em;
+ }
+ #body {
+ margin: 128em 64em;
+ border-width: 256em 128em;
+ padding: 512em 256em;
+ }
+ #documentElement {
+ top: 1024em; left: 512em;
+ }
+ #body {
+ top: 2048em; left: 1024em;
+ }
+
+ /* style for humans */
+ :not(.box) {
+ font-size: 20px;
+ }
+ html {
+ border-color: hsl(20, 100%, 70%);
+ background-color: hsl(110, 100%, 70%);
+ }
+ body {
+ border-color: hsl(200, 100%, 70%);
+ background-color: hsl(290, 100%, 70%);
+ }
+ html::after,
+ body::after {
+ font: italic 16px sans-serif;
+ content: attr(id);
+ }
+ div.box {
+ background-color: hsla(0, 0%, 70%, 0.5);
+ opacity: 0.7;
+ }
+ div.box div.box {
+ background-color: hsla(60, 100%, 70%, 0.5);
+ }
+ </style>
+ <script src="../../jquery.js"></script>
+ <script src="../iframeTest.js"></script>
+ <script type="text/javascript" charset="utf-8">
+ jQuery( function() {
+ window.scrollTo( 1, 2 );
+ startIframeTest();
+ } );
+ </script>
+ </head>
+ <body id="body" class="box">
+ <div id="relative" class="relative box">
+ <div id="relative-relative" class="relative box"><code
+ >relative > relative</code></div>
+ <div id="relative-absolute" class="absolute box"><code
+ >relative > absolute</code></div>
+ </div>
+ <div id="absolute" class="absolute box">
+ <div id="absolute-relative" class="relative box"><code
+ >absolute > relative</code></div>
+ <div id="absolute-absolute" class="absolute box"><code
+ >absolute > absolute</code></div>
+ </div>
+ <div id="fixed" class="fixed box">
+ <div id="fixed-relative" class="relative box"><code
+ >fixed > relative</code></div>
+ <div id="fixed-absolute" class="absolute box"><code
+ >fixed > absolute</code></div>
+ </div>
+ <p id="positionTest" class="absolute">position:absolute with no top/left values</p>
+ </body>
+</html>
assert.equal( jQuery( "#absolute-1" ).offset( undefined ).jquery, jQuery.fn.jquery, "offset(undefined) returns jQuery object (#5571)" );
} );
+// Test complex content under a variety of <html>/<body> positioning styles
+( function() {
+ var POSITION_VALUES = [ "static", "relative", "absolute", "fixed" ],
+
+ // Use shorthands for describing an element's relevant properties
+ BOX_PROPS =
+ ( "top left marginTop marginLeft borderTop borderLeft paddingTop paddingLeft" +
+ " style parent" ).split( /\s+/g ),
+ props = function() {
+ var propObj = {};
+ supportjQuery.each( arguments, function( i, value ) {
+ propObj[ BOX_PROPS[ i ] ] = value;
+ } );
+ return propObj;
+ },
+
+ // Values must stay synchronized with test/data/offset/boxes.html
+ divProps = function( position, parentId ) {
+ return props( 8, 4, 16, 8, 4, 2, 32, 16, position, parentId );
+ },
+ htmlProps = function( position ) {
+ return props( position === "static" ? 0 : 4096, position === "static" ? 0 : 2048,
+ 64, 32, 128, 64, 256, 128, position );
+ },
+ bodyProps = function( position ) {
+ return props( position === "static" ? 0 : 8192, position === "static" ? 0 : 4096,
+ 512, 256, 1024, 512, 2048, 1024, position,
+ position !== "fixed" && "documentElement" );
+ },
+ viewportScroll = { top: 2, left: 1 },
+
+ alwaysScrollable = false;
+
+ // Support: iOS <=7
+ // Detect viewport scrollability for pages with position:fixed document element
+ ( function() {
+ var $iframe = jQuery( "<iframe/>" )
+ .css( { position: "absolute", width: "50px", left: "-60px" } )
+ .attr( "src", url( "./data/offset/boxes.html" ) );
+
+ // Hijack the iframe test infrastructure
+ window.iframeCallback = function( $, win, doc ) {
+ doc.documentElement.style.position = "fixed";
+ alwaysScrollable = win.pageXOffset !== 0;
+ window.iframeCallback = undefined;
+ $iframe.remove();
+ return;
+ };
+
+ $iframe.appendTo( document.body );
+ return;
+ } )();
+
+ function getExpectations( htmlPos, bodyPos ) {
+
+ // Initialize data about page elements
+ var expectations = {
+ "documentElement": htmlProps( htmlPos ),
+ "body": bodyProps( bodyPos ),
+ "relative": divProps( "relative", "body" ),
+ "relative-relative": divProps( "relative", "relative" ),
+ "relative-absolute": divProps( "absolute", "relative" ),
+ "absolute": divProps( "absolute", "body" ),
+ "absolute-relative": divProps( "relative", "absolute" ),
+ "absolute-absolute": divProps( "absolute", "absolute" ),
+ "fixed": divProps( "fixed" ),
+ "fixed-relative": divProps( "relative", "fixed" ),
+ "fixed-absolute": divProps( "absolute", "fixed" )
+ };
+
+ // Define position and offset expectations for page elements
+ supportjQuery.each( expectations, function( id, props ) {
+ var parent = expectations[ props.parent ],
+
+ // position() relates an element's margin box to its offset parent's padding box
+ pos = props.pos = {
+ top: props.top,
+ left: props.left
+ },
+
+ // offset() relates an element's border box to the document origin
+ offset = props.offset = {
+ top: pos.top + props.marginTop,
+ left: pos.left + props.marginLeft
+ };
+
+ // Account for ancestors differently by element position
+ // fixed: ignore them
+ // absolute: offset includes offsetParent offset+border
+ // relative: position includes parent padding (and also position+margin+border when
+ // parent is not offsetParent); offset includes parent offset+border+padding
+ // static: same as relative
+ for ( ; parent; parent = expectations[ parent.parent ] ) {
+ // position:fixed
+ if ( props.style === "fixed" ) {
+ break;
+ }
+
+ // position:absolute bypass
+ if ( props.style === "absolute" && parent.style === "static" ) {
+ continue;
+ }
+
+ // Offset update
+ offset.top += parent.offset.top + parent.borderTop;
+ offset.left += parent.offset.left + parent.borderLeft;
+ if ( props.style !== "absolute" ) {
+ offset.top += parent.paddingTop;
+ offset.left += parent.paddingLeft;
+
+ // position:relative or position:static position update
+ pos.top += parent.paddingTop;
+ pos.left += parent.paddingLeft;
+ if ( parent.style === "static" ) {
+ pos.top += parent.pos.top + parent.marginTop + parent.borderTop;
+ pos.left += parent.pos.left + parent.marginLeft + parent.borderLeft;
+ }
+ }
+ break;
+ }
+
+ // Viewport scroll affects position:fixed elements, except when the page is
+ // unscrollable.
+ if ( props.style === "fixed" &&
+ ( alwaysScrollable || expectations.documentElement.style !== "fixed" ) ) {
+
+ offset.top += viewportScroll.top;
+ offset.left += viewportScroll.left;
+ }
+ } );
+
+ // Support: IE<=10 only
+ // Fudge the tests to work around <html>.gBCR() erroneously including margins
+ if ( /MSIE (?:9|10)\./.test( navigator.userAgent ) ) {
+ expectations.documentElement.pos.top -= expectations.documentElement.marginTop -
+ viewportScroll.top;
+ expectations.documentElement.offset.top -= expectations.documentElement.marginTop -
+ viewportScroll.top;
+ expectations.documentElement.pos.left -= expectations.documentElement.marginLeft -
+ viewportScroll.left;
+ expectations.documentElement.offset.left -= expectations.documentElement.marginLeft -
+ viewportScroll.left;
+ if ( htmlPos !== "static" ) {
+ delete expectations.documentElement;
+ delete expectations.body;
+ delete expectations.relative;
+ delete expectations.absolute;
+ }
+ }
+
+ return expectations;
+ }
+
+ // Cover each combination of <html> position and <body> position
+ supportjQuery.each( POSITION_VALUES, function( _, htmlPos ) {
+ supportjQuery.each( POSITION_VALUES, function( _, bodyPos ) {
+ var label = "nonzero box properties - html." + htmlPos + " body." + bodyPos;
+ testIframe( label, "offset/boxes.html", function( assert, $, win, doc ) {
+
+ // Define expectations at runtime so alwaysScrollable is correct
+ var expectations = getExpectations( htmlPos, bodyPos );
+
+ assert.expect( 3 * Object.keys( expectations ).length );
+
+ // Setup documentElement and body styles
+ doc.documentElement.style.position = htmlPos;
+ doc.body.style.position = bodyPos;
+
+ // Verify expected document offset
+ supportjQuery.each( expectations, function( id, descriptor ) {
+ assert.deepEqual(
+ supportjQuery.extend( {}, $( "#" + id ).offset() ),
+ descriptor.offset,
+ "jQuery('#" + id + "').offset()" );
+ } );
+
+ // Verify expected relative position
+ supportjQuery.each( expectations, function( id, descriptor ) {
+ assert.deepEqual(
+ supportjQuery.extend( {}, $( "#" + id ).position() ),
+ descriptor.pos,
+ "jQuery('#" + id + "').position()" );
+ } );
+
+ // Verify that values round-trip
+ supportjQuery.each( Object.keys( expectations ).reverse(), function( _, id ) {
+ var $el = $( "#" + id ),
+ pos = supportjQuery.extend( {}, $el.position() );
+
+ $el.css( { top: pos.top, left: pos.left } );
+ if ( $el.css( "position" ) === "relative" ) {
+
+ // $relative.position() includes parent padding; switch to absolute
+ // positioning so we don't double its effects.
+ $el.css( { position: "absolute" } );
+ }
+ assert.deepEqual( supportjQuery.extend( {}, $el.position() ), pos,
+ "jQuery('#" + id + "').position() round-trips" );
+
+ // TODO Verify .offset(...)
+ // assert.deepEqual( $el.offset( offset ).offset(), offset )
+ // assert.deepEqual( $el.offset( adjustedOffset ).offset(), adjustedOffset )
+ // assert.deepEqual( $new.offset( offset ).offset(), offset )
+ } );
+ } );
+ } );
+ } );
+} )();
+
QUnit.test( "offsetParent", function( assert ) {
assert.expect( 13 );
// the iframe but only its parent element.
// It seems (not confirmed) in android 4.0 it's not possible to scroll iframes from the code.
if (
- /iphone os/i.test( navigator.userAgent ) ||
+ /iphone os|ipad/i.test( navigator.userAgent ) ||
/android 4\.0/i.test( navigator.userAgent )
) {
assert.equal( true, true, "Can't scroll iframes in this environment" );