offset.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. define( [
  2. "./core",
  3. "./core/access",
  4. "./var/documentElement",
  5. "./var/isFunction",
  6. "./css/var/rnumnonpx",
  7. "./css/curCSS",
  8. "./css/addGetHookIf",
  9. "./css/support",
  10. "./var/isWindow",
  11. "./core/init",
  12. "./css",
  13. "./selector" // contains
  14. ], function( jQuery, access, documentElement, isFunction, rnumnonpx,
  15. curCSS, addGetHookIf, support, isWindow ) {
  16. "use strict";
  17. jQuery.offset = {
  18. setOffset: function( elem, options, i ) {
  19. var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
  20. position = jQuery.css( elem, "position" ),
  21. curElem = jQuery( elem ),
  22. props = {};
  23. // Set position first, in-case top/left are set even on static elem
  24. if ( position === "static" ) {
  25. elem.style.position = "relative";
  26. }
  27. curOffset = curElem.offset();
  28. curCSSTop = jQuery.css( elem, "top" );
  29. curCSSLeft = jQuery.css( elem, "left" );
  30. calculatePosition = ( position === "absolute" || position === "fixed" ) &&
  31. ( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;
  32. // Need to be able to calculate position if either
  33. // top or left is auto and position is either absolute or fixed
  34. if ( calculatePosition ) {
  35. curPosition = curElem.position();
  36. curTop = curPosition.top;
  37. curLeft = curPosition.left;
  38. } else {
  39. curTop = parseFloat( curCSSTop ) || 0;
  40. curLeft = parseFloat( curCSSLeft ) || 0;
  41. }
  42. if ( isFunction( options ) ) {
  43. // Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
  44. options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
  45. }
  46. if ( options.top != null ) {
  47. props.top = ( options.top - curOffset.top ) + curTop;
  48. }
  49. if ( options.left != null ) {
  50. props.left = ( options.left - curOffset.left ) + curLeft;
  51. }
  52. if ( "using" in options ) {
  53. options.using.call( elem, props );
  54. } else {
  55. curElem.css( props );
  56. }
  57. }
  58. };
  59. jQuery.fn.extend( {
  60. // offset() relates an element's border box to the document origin
  61. offset: function( options ) {
  62. // Preserve chaining for setter
  63. if ( arguments.length ) {
  64. return options === undefined ?
  65. this :
  66. this.each( function( i ) {
  67. jQuery.offset.setOffset( this, options, i );
  68. } );
  69. }
  70. var rect, win,
  71. elem = this[ 0 ];
  72. if ( !elem ) {
  73. return;
  74. }
  75. // Return zeros for disconnected and hidden (display: none) elements (gh-2310)
  76. // Support: IE <=11 only
  77. // Running getBoundingClientRect on a
  78. // disconnected node in IE throws an error
  79. if ( !elem.getClientRects().length ) {
  80. return { top: 0, left: 0 };
  81. }
  82. // Get document-relative position by adding viewport scroll to viewport-relative gBCR
  83. rect = elem.getBoundingClientRect();
  84. win = elem.ownerDocument.defaultView;
  85. return {
  86. top: rect.top + win.pageYOffset,
  87. left: rect.left + win.pageXOffset
  88. };
  89. },
  90. // position() relates an element's margin box to its offset parent's padding box
  91. // This corresponds to the behavior of CSS absolute positioning
  92. position: function() {
  93. if ( !this[ 0 ] ) {
  94. return;
  95. }
  96. var offsetParent, offset, doc,
  97. elem = this[ 0 ],
  98. parentOffset = { top: 0, left: 0 };
  99. // position:fixed elements are offset from the viewport, which itself always has zero offset
  100. if ( jQuery.css( elem, "position" ) === "fixed" ) {
  101. // Assume position:fixed implies availability of getBoundingClientRect
  102. offset = elem.getBoundingClientRect();
  103. } else {
  104. offset = this.offset();
  105. // Account for the *real* offset parent, which can be the document or its root element
  106. // when a statically positioned element is identified
  107. doc = elem.ownerDocument;
  108. offsetParent = elem.offsetParent || doc.documentElement;
  109. while ( offsetParent &&
  110. ( offsetParent === doc.body || offsetParent === doc.documentElement ) &&
  111. jQuery.css( offsetParent, "position" ) === "static" ) {
  112. offsetParent = offsetParent.parentNode;
  113. }
  114. if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {
  115. // Incorporate borders into its offset, since they are outside its content origin
  116. parentOffset = jQuery( offsetParent ).offset();
  117. parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true );
  118. parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true );
  119. }
  120. }
  121. // Subtract parent offsets and element margins
  122. return {
  123. top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
  124. left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
  125. };
  126. },
  127. // This method will return documentElement in the following cases:
  128. // 1) For the element inside the iframe without offsetParent, this method will return
  129. // documentElement of the parent window
  130. // 2) For the hidden or detached element
  131. // 3) For body or html element, i.e. in case of the html node - it will return itself
  132. //
  133. // but those exceptions were never presented as a real life use-cases
  134. // and might be considered as more preferable results.
  135. //
  136. // This logic, however, is not guaranteed and can change at any point in the future
  137. offsetParent: function() {
  138. return this.map( function() {
  139. var offsetParent = this.offsetParent;
  140. while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
  141. offsetParent = offsetParent.offsetParent;
  142. }
  143. return offsetParent || documentElement;
  144. } );
  145. }
  146. } );
  147. // Create scrollLeft and scrollTop methods
  148. jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
  149. var top = "pageYOffset" === prop;
  150. jQuery.fn[ method ] = function( val ) {
  151. return access( this, function( elem, method, val ) {
  152. // Coalesce documents and windows
  153. var win;
  154. if ( isWindow( elem ) ) {
  155. win = elem;
  156. } else if ( elem.nodeType === 9 ) {
  157. win = elem.defaultView;
  158. }
  159. if ( val === undefined ) {
  160. return win ? win[ prop ] : elem[ method ];
  161. }
  162. if ( win ) {
  163. win.scrollTo(
  164. !top ? val : win.pageXOffset,
  165. top ? val : win.pageYOffset
  166. );
  167. } else {
  168. elem[ method ] = val;
  169. }
  170. }, method, val, arguments.length );
  171. };
  172. } );
  173. // Support: Safari <=7 - 9.1, Chrome <=37 - 49
  174. // Add the top/left cssHooks using jQuery.fn.position
  175. // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
  176. // Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347
  177. // getComputedStyle returns percent when specified for top/left/bottom/right;
  178. // rather than make the css module depend on the offset module, just check for it here
  179. jQuery.each( [ "top", "left" ], function( _i, prop ) {
  180. jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
  181. function( elem, computed ) {
  182. if ( computed ) {
  183. computed = curCSS( elem, prop );
  184. // If curCSS returns percentage, fallback to offset
  185. return rnumnonpx.test( computed ) ?
  186. jQuery( elem ).position()[ prop ] + "px" :
  187. computed;
  188. }
  189. }
  190. );
  191. } );
  192. return jQuery;
  193. } );