3 if ( !jQuery.fn.offset ) {
7 var supportsScroll, supportsFixedPosition,
8 forceScroll = jQuery("<div/>").css({ width: 2000, height: 2000 }),
9 checkSupport = function() {
13 var checkFixed = jQuery("<div/>").css({ position: "fixed", top: "20px" }).appendTo("#qunit-fixture");
15 // Must append to body because #qunit-fixture is hidden and elements inside it don't have a scrollTop
16 forceScroll.appendTo("body");
17 window.scrollTo( 200, 200 );
18 supportsScroll = document.documentElement.scrollTop || document.body.scrollTop;
21 supportsFixedPosition = checkFixed[0].offsetTop === 20;
25 module("offset", { setup: function(){
26 if ( typeof checkSupport === "function" ) {
30 // Force a scroll value on the main window to ensure incorrect results
31 // if offset is using the scroll offset of the parent window
32 forceScroll.appendTo("body");
33 window.scrollTo( 1, 1 );
35 }, teardown: moduleTeardown });
38 Closure-compiler will roll static methods off of the jQuery object and so they will
39 not be passed with the jQuery object across the windows. To differentiate this, the
40 testIframe callbacks use the "$" symbol to refer to the jQuery object passed from
41 the iframe window and the "jQuery" symbol is used to access any static methods.
44 test("empty set", function() {
46 strictEqual( jQuery().offset(), undefined, "offset() returns undefined for empty set (#11962)" );
47 strictEqual( jQuery().position(), undefined, "position() returns undefined for empty set (#11962)" );
50 test("object without getBoundingClientRect", function() {
53 // Simulates a browser without gBCR on elements, we just want to return 0,0
54 var result = jQuery({ ownerDocument: document }).offset();
55 equal( result.top, 0, "Check top" );
56 equal( result.left, 0, "Check left" );
59 test("disconnected node", function() {
62 var result = jQuery( document.createElement("div") ).offset();
64 equal( result.top, 0, "Check top" );
65 equal( result.left, 0, "Check left" );
68 testIframe("offset/absolute", "absolute", function($, iframe) {
71 var doc = iframe.document,
76 { "id": "#absolute-1", "top": 1, "left": 1 }
78 jQuery.each( tests, function() {
79 equal( jQuery( this["id"], doc ).offset().top, this["top"], "jQuery('" + this["id"] + "').offset().top" );
80 equal( jQuery( this["id"], doc ).offset().left, this["left"], "jQuery('" + this["id"] + "').offset().left" );
86 { "id": "#absolute-1", "top": 0, "left": 0 }
88 jQuery.each( tests, function() {
89 equal( jQuery( this["id"], doc ).position().top, this["top"], "jQuery('" + this["id"] + "').position().top" );
90 equal( jQuery( this["id"], doc ).position().left, this["left"], "jQuery('" + this["id"] + "').position().left" );
94 testIframe("offset/absolute", "absolute", function( $ ) {
101 { "id": "#absolute-1", "top": 1, "left": 1 },
102 { "id": "#absolute-1-1", "top": 5, "left": 5 },
103 { "id": "#absolute-1-1-1", "top": 9, "left": 9 },
104 { "id": "#absolute-2", "top": 20, "left": 20 }
106 jQuery.each( tests, function() {
107 equal( $( this["id"] ).offset().top, this["top"], "jQuery('" + this["id"] + "').offset().top" );
108 equal( $( this["id"] ).offset().left, this["left"], "jQuery('" + this["id"] + "').offset().left" );
114 { "id": "#absolute-1", "top": 0, "left": 0 },
115 { "id": "#absolute-1-1", "top": 1, "left": 1 },
116 { "id": "#absolute-1-1-1", "top": 1, "left": 1 },
117 { "id": "#absolute-2", "top": 19, "left": 19 }
119 jQuery.each( tests, function() {
120 equal( $( this["id"] ).position().top, this["top"], "jQuery('" + this["id"] + "').position().top" );
121 equal( $( this["id"] ).position().left, this["left"], "jQuery('" + this["id"] + "').position().left" );
125 offset = $( "#positionTest" ).offset({ "top": 10, "left": 10 }).offset();
126 equal( offset.top, 10, "Setting offset on element with position absolute but 'auto' values." );
127 equal( offset.left, 10, "Setting offset on element with position absolute but 'auto' values." );
132 { "id": "#absolute-2", "top": 30, "left": 30 },
133 { "id": "#absolute-2", "top": 10, "left": 10 },
134 { "id": "#absolute-2", "top": -1, "left": -1 },
135 { "id": "#absolute-2", "top": 19, "left": 19 },
136 { "id": "#absolute-1-1-1", "top": 15, "left": 15 },
137 { "id": "#absolute-1-1-1", "top": 5, "left": 5 },
138 { "id": "#absolute-1-1-1", "top": -1, "left": -1 },
139 { "id": "#absolute-1-1-1", "top": 9, "left": 9 },
140 { "id": "#absolute-1-1", "top": 10, "left": 10 },
141 { "id": "#absolute-1-1", "top": 0, "left": 0 },
142 { "id": "#absolute-1-1", "top": -1, "left": -1 },
143 { "id": "#absolute-1-1", "top": 5, "left": 5 },
144 { "id": "#absolute-1", "top": 2, "left": 2 },
145 { "id": "#absolute-1", "top": 0, "left": 0 },
146 { "id": "#absolute-1", "top": -1, "left": -1 },
147 { "id": "#absolute-1", "top": 1, "left": 1 }
149 jQuery.each( tests, function() {
150 $( this["id"] ).offset({ "top": this["top"], "left": this["left"] });
151 equal( $( this["id"] ).offset().top, this["top"], "jQuery('" + this["id"] + "').offset({ top: " + this["top"] + " })" );
152 equal( $( this["id"] ).offset().left, this["left"], "jQuery('" + this["id"] + "').offset({ left: " + this["left"] + " })" );
154 var top = this["top"], left = this["left"];
156 $( this["id"] ).offset(function(i, val){
157 equal( val.top, top, "Verify incoming top position." );
158 equal( val.left, left, "Verify incoming top position." );
159 return { "top": top + 1, "left": left + 1 };
161 equal( $( this["id"] ).offset().top, this["top"] + 1, "jQuery('" + this["id"] + "').offset({ top: " + (this["top"] + 1) + " })" );
162 equal( $( this["id"] ).offset().left, this["left"] + 1, "jQuery('" + this["id"] + "').offset({ left: " + (this["left"] + 1) + " })" );
165 .offset({ "left": this["left"] + 2 })
166 .offset({ "top": this["top"] + 2 });
167 equal( $( this["id"] ).offset().top, this["top"] + 2, "Setting one property at a time." );
168 equal( $( this["id"] ).offset().left, this["left"] + 2, "Setting one property at a time." );
170 $( this["id"] ).offset({ "top": this["top"], "left": this["left"], "using": function( props ) {
172 "top": props.top + 1,
173 "left": props.left + 1
176 equal( $( this["id"] ).offset().top, this["top"] + 1, "jQuery('" + this["id"] + "').offset({ top: " + (this["top"] + 1) + ", using: fn })" );
177 equal( $( this["id"] ).offset().left, this["left"] + 1, "jQuery('" + this["id"] + "').offset({ left: " + (this["left"] + 1) + ", using: fn })" );
181 testIframe("offset/relative", "relative", function( $ ) {
186 { "id": "#relative-1", "top": 7, "left": 7 },
187 { "id": "#relative-1-1", "top": 15, "left": 15 },
188 { "id": "#relative-2", "top": 142, "left": 27 }
190 jQuery.each( tests, function() {
191 equal( $( this["id"] ).offset().top, this["top"], "jQuery('" + this["id"] + "').offset().top" );
192 equal( $( this["id"] ).offset().left, this["left"], "jQuery('" + this["id"] + "').offset().left" );
198 { "id": "#relative-1", "top": 6, "left": 6 },
199 { "id": "#relative-1-1", "top": 5, "left": 5 },
200 { "id": "#relative-2", "top": 141, "left": 26 }
202 jQuery.each( tests, function() {
203 equal( $( this["id"] ).position().top, this["top"], "jQuery('" + this["id"] + "').position().top" );
204 equal( $( this["id"] ).position().left, this["left"], "jQuery('" + this["id"] + "').position().left" );
210 { "id": "#relative-2", "top": 200, "left": 50 },
211 { "id": "#relative-2", "top": 100, "left": 10 },
212 { "id": "#relative-2", "top": -5, "left": -5 },
213 { "id": "#relative-2", "top": 142, "left": 27 },
214 { "id": "#relative-1-1", "top": 100, "left": 100 },
215 { "id": "#relative-1-1", "top": 5, "left": 5 },
216 { "id": "#relative-1-1", "top": -1, "left": -1 },
217 { "id": "#relative-1-1", "top": 15, "left": 15 },
218 { "id": "#relative-1", "top": 100, "left": 100 },
219 { "id": "#relative-1", "top": 0, "left": 0 },
220 { "id": "#relative-1", "top": -1, "left": -1 },
221 { "id": "#relative-1", "top": 7, "left": 7 }
223 jQuery.each( tests, function() {
224 $( this["id"] ).offset({ "top": this["top"], "left": this["left"] });
225 equal( $( this["id"] ).offset().top, this["top"], "jQuery('" + this["id"] + "').offset({ top: " + this["top"] + " })" );
226 equal( $( this["id"] ).offset().left, this["left"], "jQuery('" + this["id"] + "').offset({ left: " + this["left"] + " })" );
228 $( this["id"] ).offset({ "top": this["top"], "left": this["left"], "using": function( props ) {
230 "top": props.top + 1,
231 "left": props.left + 1
234 equal( $( this["id"] ).offset().top, this["top"] + 1, "jQuery('" + this["id"] + "').offset({ top: " + (this["top"] + 1) + ", using: fn })" );
235 equal( $( this["id"] ).offset().left, this["left"] + 1, "jQuery('" + this["id"] + "').offset({ left: " + (this["left"] + 1) + ", using: fn })" );
239 testIframe("offset/static", "static", function( $ ) {
244 { "id": "#static-1", "top": 7, "left": 7 },
245 { "id": "#static-1-1", "top": 15, "left": 15 },
246 { "id": "#static-1-1-1", "top": 23, "left": 23 },
247 { "id": "#static-2", "top": 122, left: 7 }
249 jQuery.each( tests, function() {
250 equal( $( this["id"] ).offset().top, this["top"], "jQuery('" + this["id"] + "').offset().top" );
251 equal( $( this["id"] ).offset().left, this["left"], "jQuery('" + this["id"] + "').offset().left" );
257 { "id": "#static-1", "top": 6, "left": 6 },
258 { "id": "#static-1-1", "top": 14, "left": 14 },
259 { "id": "#static-1-1-1", "top": 22, "left": 22 },
260 { "id": "#static-2", "top": 121, "left": 6 }
262 jQuery.each( tests, function() {
263 equal( $( this["id"] ).position().top, this["top"], "jQuery('" + this["top"] + "').position().top" );
264 equal( $( this["id"] ).position().left, this["left"], "jQuery('" + this["left"] +"').position().left" );
270 { "id": "#static-2", "top": 200, "left": 200 },
271 { "id": "#static-2", "top": 100, "left": 100 },
272 { "id": "#static-2", "top": -2, "left": -2 },
273 { "id": "#static-2", "top": 121, "left": 6 },
274 { "id": "#static-1-1-1", "top": 50, "left": 50 },
275 { "id": "#static-1-1-1", "top": 10, "left": 10 },
276 { "id": "#static-1-1-1", "top": -1, "left": -1 },
277 { "id": "#static-1-1-1", "top": 22, "left": 22 },
278 { "id": "#static-1-1", "top": 25, "left": 25 },
279 { "id": "#static-1-1", "top": 10, "left": 10 },
280 { "id": "#static-1-1", "top": -3, "left": -3 },
281 { "id": "#static-1-1", "top": 14, "left": 14 },
282 { "id": "#static-1", "top": 30, "left": 30 },
283 { "id": "#static-1", "top": 2, "left": 2 },
284 { "id": "#static-1", "top": -2, "left": -2 },
285 { "id": "#static-1", "top": 7, "left": 7 }
287 jQuery.each( tests, function() {
288 $( this["id"] ).offset({ "top": this["top"], "left": this["left"] });
289 equal( $( this["id"] ).offset().top, this["top"], "jQuery('" + this["id"] + "').offset({ top: " + this["top"] + " })" );
290 equal( $( this["id"] ).offset().left, this["left"], "jQuery('" + this["id"] + "').offset({ left: " + this["left"] + " })" );
292 $( this["id"] ).offset({ "top": this["top"], "left": this["left"], "using": function( props ) {
294 "top": props.top + 1,
295 "left": props.left + 1
298 equal( $( this["id"] ).offset().top, this["top"] + 1, "jQuery('" + this["id"] + "').offset({ top: " + (this["top"] + 1) + ", using: fn })" );
299 equal( $( this["id"] ).offset().left, this["left"] + 1, "jQuery('" + this["id"] + "').offset({ left: " + (this["left"] + 1) + ", using: fn })" );
303 testIframe("offset/fixed", "fixed", function( $ ) {
306 var tests, $noTopLeft;
325 jQuery.each( tests, function() {
326 if ( !window.supportsScroll ) {
327 ok( true, "Browser doesn't support scroll position." );
328 ok( true, "Browser doesn't support scroll position." );
329 ok( true, "Browser doesn't support scroll position." );
330 ok( true, "Browser doesn't support scroll position." );
332 } else if ( window.supportsFixedPosition ) {
333 equal( $( this["id"] ).offset().top, this["offsetTop"], "jQuery('" + this["id"] + "').offset().top" );
334 equal( $( this["id"] ).position().top, this["positionTop"], "jQuery('" + this["id"] + "').position().top" );
335 equal( $( this["id"] ).offset().left, this["offsetLeft"], "jQuery('" + this["id"] + "').offset().left" );
336 equal( $( this["id"] ).position().left, this["positionLeft"], "jQuery('" + this["id"] + "').position().left" );
338 // need to have same number of assertions
339 ok( true, "Fixed position is not supported" );
340 ok( true, "Fixed position is not supported" );
341 ok( true, "Fixed position is not supported" );
342 ok( true, "Fixed position is not supported" );
347 { "id": "#fixed-1", "top": 100, "left": 100 },
348 { "id": "#fixed-1", "top": 0, "left": 0 },
349 { "id": "#fixed-1", "top": -4, "left": -4 },
350 { "id": "#fixed-2", "top": 200, "left": 200 },
351 { "id": "#fixed-2", "top": 0, "left": 0 },
352 { "id": "#fixed-2", "top": -5, "left": -5 }
355 jQuery.each( tests, function() {
356 if ( window.supportsFixedPosition ) {
357 $( this["id"] ).offset({ "top": this["top"], "left": this["left"] });
358 equal( $( this["id"] ).offset().top, this["top"], "jQuery('" + this["id"] + "').offset({ top: " + this["top"] + " })" );
359 equal( $( this["id"] ).offset().left, this["left"], "jQuery('" + this["id"] + "').offset({ left: " + this["left"] + " })" );
361 $( this["id"] ).offset({ "top": this["top"], "left": this["left"], "using": function( props ) {
363 "top": props.top + 1,
364 "left": props.left + 1
367 equal( $( this["id"] ).offset().top, this["top"] + 1, "jQuery('" + this["id"] + "').offset({ top: " + (this["top"] + 1) + ", using: fn })" );
368 equal( $( this["id"] ).offset().left, this["left"] + 1, "jQuery('" + this["id"] + "').offset({ left: " + (this["left"] + 1) + ", using: fn })" );
370 // need to have same number of assertions
371 ok( true, "Fixed position is not supported" );
372 ok( true, "Fixed position is not supported" );
373 ok( true, "Fixed position is not supported" );
374 ok( true, "Fixed position is not supported" );
379 $noTopLeft = $("#fixed-no-top-left");
380 if ( window.supportsFixedPosition ) {
381 equal( $noTopLeft.offset().top, 1007, "Check offset top for fixed element with no top set" );
382 equal( $noTopLeft.offset().left, 1007, "Check offset left for fixed element with no left set" );
384 // need to have same number of assertions
385 ok( true, "Fixed position is not supported" );
386 ok( true, "Fixed position is not supported" );
390 testIframe("offset/table", "table", function( $ ) {
393 equal( $("#table-1").offset().top, 6, "jQuery('#table-1').offset().top" );
394 equal( $("#table-1").offset().left, 6, "jQuery('#table-1').offset().left" );
396 equal( $("#th-1").offset().top, 10, "jQuery('#th-1').offset().top" );
397 equal( $("#th-1").offset().left, 10, "jQuery('#th-1').offset().left" );
400 testIframe("offset/scroll", "scroll", function( $, win ) {
403 equal( $("#scroll-1").offset().top, 7, "jQuery('#scroll-1').offset().top" );
404 equal( $("#scroll-1").offset().left, 7, "jQuery('#scroll-1').offset().left" );
406 equal( $("#scroll-1-1").offset().top, 11, "jQuery('#scroll-1-1').offset().top" );
407 equal( $("#scroll-1-1").offset().left, 11, "jQuery('#scroll-1-1').offset().left" );
409 // scroll offset tests .scrollTop/Left
410 equal( $("#scroll-1").scrollTop(), 5, "jQuery('#scroll-1').scrollTop()" );
411 equal( $("#scroll-1").scrollLeft(), 5, "jQuery('#scroll-1').scrollLeft()" );
413 equal( $("#scroll-1-1").scrollTop(), 0, "jQuery('#scroll-1-1').scrollTop()" );
414 equal( $("#scroll-1-1").scrollLeft(), 0, "jQuery('#scroll-1-1').scrollLeft()" );
416 // scroll method chaining
417 equal( $("#scroll-1").scrollTop(undefined).scrollTop(), 5, ".scrollTop(undefined) is chainable (#5571)" );
418 equal( $("#scroll-1").scrollLeft(undefined).scrollLeft(), 5, ".scrollLeft(undefined) is chainable (#5571)" );
422 if ( !window.supportsScroll ) {
423 ok( true, "Browser doesn't support scroll position." );
424 ok( true, "Browser doesn't support scroll position." );
426 ok( true, "Browser doesn't support scroll position." );
427 ok( true, "Browser doesn't support scroll position." );
429 equal( $(win).scrollTop(), 1000, "jQuery(window).scrollTop()" );
430 equal( $(win).scrollLeft(), 1000, "jQuery(window).scrollLeft()" );
432 equal( $(win.document).scrollTop(), 1000, "jQuery(document).scrollTop()" );
433 equal( $(win.document).scrollLeft(), 1000, "jQuery(document).scrollLeft()" );
436 // test jQuery using parent window/document
437 // jQuery reference here is in the iframe
438 window.scrollTo(0,0);
439 equal( $(window).scrollTop(), 0, "jQuery(window).scrollTop() other window" );
440 equal( $(window).scrollLeft(), 0, "jQuery(window).scrollLeft() other window" );
441 equal( $(document).scrollTop(), 0, "jQuery(window).scrollTop() other document" );
442 equal( $(document).scrollLeft(), 0, "jQuery(window).scrollLeft() other document" );
444 // Tests scrollTop/Left with empty jquery objects
445 notEqual( $().scrollTop(100), null, "jQuery().scrollTop(100) testing setter on empty jquery object" );
446 notEqual( $().scrollLeft(100), null, "jQuery().scrollLeft(100) testing setter on empty jquery object" );
447 notEqual( $().scrollTop(null), null, "jQuery().scrollTop(null) testing setter on empty jquery object" );
448 notEqual( $().scrollLeft(null), null, "jQuery().scrollLeft(null) testing setter on empty jquery object" );
449 strictEqual( $().scrollTop(), null, "jQuery().scrollTop(100) testing setter on empty jquery object" );
450 strictEqual( $().scrollLeft(), null, "jQuery().scrollLeft(100) testing setter on empty jquery object" );
453 testIframe("offset/body", "body", function( $ ) {
456 equal( $("body").offset().top, 1, "jQuery('#body').offset().top" );
457 equal( $("body").offset().left, 1, "jQuery('#body').offset().left" );
458 equal( $("#firstElement").position().left, 5, "$('#firstElement').position().left" );
459 equal( $("#firstElement").position().top, 5, "$('#firstElement').position().top" );
462 test("chaining", function() {
464 var coords = { "top": 1, "left": 1 };
465 equal( jQuery("#absolute-1").offset(coords).jquery, jQuery.fn.jquery, "offset(coords) returns jQuery object" );
466 equal( jQuery("#non-existent").offset(coords).jquery, jQuery.fn.jquery, "offset(coords) with empty jQuery set returns jQuery object" );
467 equal( jQuery("#absolute-1").offset(undefined).jquery, jQuery.fn.jquery, "offset(undefined) returns jQuery object (#5571)" );
470 test("offsetParent", function(){
473 var body, header, div, area;
475 body = jQuery("body").offsetParent();
476 equal( body.length, 1, "Only one offsetParent found." );
477 equal( body[0], document.documentElement, "The html element is the offsetParent of the body." );
479 header = jQuery("#qunit").offsetParent();
480 equal( header.length, 1, "Only one offsetParent found." );
481 equal( header[0], document.documentElement, "The html element is the offsetParent of #qunit." );
483 div = jQuery("#nothiddendivchild").offsetParent();
484 equal( div.length, 1, "Only one offsetParent found." );
485 equal( div[0], document.getElementById("qunit-fixture"), "The #qunit-fixture is the offsetParent of #nothiddendivchild." );
487 jQuery("#nothiddendiv").css("position", "relative");
489 div = jQuery("#nothiddendivchild").offsetParent();
490 equal( div.length, 1, "Only one offsetParent found." );
491 equal( div[0], jQuery("#nothiddendiv")[0], "The div is the offsetParent." );
493 div = jQuery("body, #nothiddendivchild").offsetParent();
494 equal( div.length, 2, "Two offsetParent found." );
495 equal( div[0], document.documentElement, "The html element is the offsetParent of the body." );
496 equal( div[1], jQuery("#nothiddendiv")[0], "The div is the offsetParent." );
498 area = jQuery("#imgmap area").offsetParent();
499 equal( area[0], document.documentElement, "The html element is the offsetParent of the body." );
501 div = jQuery("<div>").css({ "position": "absolute" }).appendTo("body");
502 equal( div.offsetParent()[0], document.documentElement, "Absolutely positioned div returns html as offset parent, see #12139" );
507 test("fractions (see #7730 and #7885)", function() {
510 jQuery("body").append("<div id='fractions'/>");
513 expected = { "top": 1000, "left": 1000 },
514 div = jQuery("#fractions");
517 "position": "absolute",
518 "left": "1000.7432222px",
519 "top": "1000.532325px",
524 div.offset(expected);
526 result = div.offset();
528 equal( result.top, expected.top, "Check top" );
529 equal( result.left, expected.left, "Check left" );
534 test("iframe scrollTop/Left (see gh-1945)", function() {
537 var ifDoc = jQuery( "#iframe" )[ 0 ].contentDocument;
539 // Mobile Safari and Android 2.3 resize the iframe by its content
540 // meaning it's not possible to scroll the iframe only its parent element.
541 // It seems (not confirmed) in android 4.0 it's not possible to scroll iframes from the code.
542 if ( /iphone os/i.test( navigator.userAgent ) ||
543 /android 2\.3/i.test( navigator.userAgent ) ||
544 /android 4\.0/i.test( navigator.userAgent ) ) {
545 equal( true, true, "Can't scroll iframes in this environment" );
546 equal( true, true, "Can't scroll iframes in this environment" );
549 // Tests scrollTop/Left with iframes
550 jQuery( "#iframe" ).css( "width", "50px" ).css( "height", "50px" );
551 ifDoc.write( "<div style='width: 1000px; height: 1000px;'></div>" );
553 jQuery( ifDoc ).scrollTop( 200 );
554 jQuery( ifDoc ).scrollLeft( 500 );
556 equal( jQuery( ifDoc ).scrollTop(), 200, "$($('#iframe')[0].contentDocument).scrollTop()" );
557 equal( jQuery( ifDoc ).scrollLeft(), 500, "$($('#iframe')[0].contentDocument).scrollLeft()" );