1 // OMG its another non-W3C standard browser 2 var REGEX_HTML_BODY = /^body|html$/i, 3 /** @private */ 4 getWIN = function( elem ) { 5 return ("scrollTo" in elem && elem.document) ? 6 elem : 7 elem.nodeType === 9 ? 8 elem.defaultView || elem.parentWindow : 9 false; 10 }; 11 12 if( "getBoundingClientRect" in DOC.documentElement ){ 13 /** 14 * @description to get the top, left offset of an element 15 * @param {Element} elem the element to get the offset of 16 * @returns {Object} top, left 17 */ 18 Simples.offset = function( elem ){ 19 20 if ( !elem || !elem.ownerDocument ) { 21 return null; 22 } 23 24 if ( elem === elem.ownerDocument.body ) { 25 return Simples.bodyOffset( elem ); 26 } 27 28 var box = elem.getBoundingClientRect(), doc = elem.ownerDocument, body = doc.body, 29 docElem = doc.documentElement, win = getWIN(doc), 30 clientTop = docElem.clientTop || body.clientTop || 0, 31 clientLeft = docElem.clientLeft || body.clientLeft || 0, 32 scrollTop = (win.pageYOffset || Simples.support.boxModel && docElem.scrollTop || body.scrollTop ), 33 scrollLeft = (win.pageXOffset || Simples.support.boxModel && docElem.scrollLeft || body.scrollLeft), 34 top = box.top + scrollTop - clientTop, left = box.left + scrollLeft - clientLeft; 35 36 return { top: top, left: left }; 37 }; 38 } else { 39 Simples.offset = function( elem ) { 40 41 if ( !elem || !elem.ownerDocument ) { 42 return null; 43 } 44 45 if ( elem === elem.ownerDocument.body ) { 46 return Simples.bodyOffset( elem ); 47 } 48 49 Simples.offset.init(); 50 51 var offsetParent = elem.offsetParent, prevOffsetParent = elem, 52 doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement, 53 body = doc.body, defaultView = doc.defaultView, 54 prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, 55 top = elem.offsetTop, left = elem.offsetLeft; 56 57 while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { 58 if ( Simples.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { 59 break; 60 } 61 62 computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; 63 top -= elem.scrollTop; 64 left -= elem.scrollLeft; 65 66 if ( elem === offsetParent ) { 67 top += elem.offsetTop; 68 left += elem.offsetLeft; 69 70 if ( Simples.offset.doesNotAddBorder && !(Simples.offset.doesAddBorderForTableAndCells && (/^t(able|d|h)$/i).test(elem.nodeName)) ) { 71 top += parseFloat( computedStyle.borderTopWidth ) || 0; 72 left += parseFloat( computedStyle.borderLeftWidth ) || 0; 73 } 74 75 prevOffsetParent = offsetParent; 76 offsetParent = elem.offsetParent; 77 } 78 79 if ( Simples.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { 80 top += parseFloat( computedStyle.borderTopWidth ) || 0; 81 left += parseFloat( computedStyle.borderLeftWidth ) || 0; 82 } 83 84 prevComputedStyle = computedStyle; 85 } 86 87 if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { 88 top += body.offsetTop; 89 left += body.offsetLeft; 90 } 91 92 if ( Simples.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { 93 top += Math.max( docElem.scrollTop, body.scrollTop ); 94 left += Math.max( docElem.scrollLeft, body.scrollLeft ); 95 } 96 97 return { top: top, left: left }; 98 }; 99 } 100 /** @private */ 101 Simples.offset.init = function(){ 102 var body = DOC.body, container = DOC.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( Simples.currentCSS(body, "marginTop", true) ) || 0, 103 html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>"; 104 105 Simples.merge( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } ); 106 107 container.innerHTML = html; 108 body.insertBefore( container, body.firstChild ); 109 innerDiv = container.firstChild; 110 checkDiv = innerDiv.firstChild; 111 td = innerDiv.nextSibling.firstChild.firstChild; 112 113 Simples.offset.doesNotAddBorder = (checkDiv.offsetTop !== 5); 114 Simples.offset.doesAddBorderForTableAndCells = (td.offsetTop === 5); 115 116 checkDiv.style.position = "fixed"; 117 checkDiv.style.top = "20px"; 118 119 // safari subtracts parent border width here which is 5px 120 Simples.offset.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15); 121 checkDiv.style.position = checkDiv.style.top = ""; 122 123 innerDiv.style.overflow = "hidden"; 124 innerDiv.style.position = "relative"; 125 126 Simples.offset.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5); 127 128 Simples.offset.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop); 129 130 body.removeChild( container ); 131 body = container = innerDiv = checkDiv = table = td = null; 132 Simples.offset.init = Simples.noop; 133 }; 134 135 Simples.merge( /** @lends Simples */ { 136 /** 137 * @description to get the offset of the body 138 * @param {Body} body body element to measure 139 * @returns {Object} top, left 140 */ 141 bodyOffset : function( body ) { 142 var top = body.offsetTop, left = body.offsetLeft; 143 144 Simples.offset.init(); 145 146 if ( Simples.offset.doesNotIncludeMarginInBodyOffset ) { 147 top += parseFloat( Simples.currentCSS(body, "marginTop", true) ) || 0; 148 left += parseFloat( Simples.currentCSS(body, "marginLeft", true) ) || 0; 149 } 150 151 return { top: top, left: left }; 152 }, 153 /** 154 * @description to set the offset of the top and left of an element passed on its current offset 155 * @param {Element} elem element to set the offset on 156 * @param {Object} options 157 * @param {Number} options.top the top offset desired 158 * @param {Number} options.left the left offset desired 159 */ 160 setOffset : function( elem, options ) { 161 var position = Simples.currentCSS( elem, "position" ); 162 163 // set position first, in-case top/left are set even on static elem 164 if ( position === "static" ) { 165 elem.style.position = "relative"; 166 } 167 168 var curElem = Simples( elem ), 169 curOffset = curElem.offset(), 170 curCSSTop = Simples.currentCSS( elem, TOP, true ), 171 curCSSLeft = Simples.currentCSS( elem, LEFT, true ), 172 calculatePosition = (position === "absolute" && (curCSSTop === 'auto' || curCSSLeft === 'auto' ) ), 173 props = {}, curPosition = {}, curTop, curLeft; 174 175 // need to be able to calculate position if either top or left is auto and position is absolute 176 if ( calculatePosition ) { 177 curPosition = curElem.position(); 178 } 179 180 curTop = calculatePosition ? curPosition.top : parseInt( curCSSTop, 10 ) || 0; 181 curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0; 182 183 if (options.top != null) { 184 props.top = (options.top - curOffset.top) + curTop; 185 } 186 if (options.left != null) { 187 props.left = (options.left - curOffset.left) + curLeft; 188 } 189 190 curElem.css( props ); 191 }, 192 /** 193 * @description to get the offsetParent of an element 194 * @param {Element} elem the element to get the offsetParent of 195 * @returns {Element} 196 */ 197 offsetParent : function( elem ) { 198 var offsetParent = elem.offsetParent || DOC.body; 199 while ( offsetParent && (!REGEX_HTML_BODY.test(offsetParent.nodeName) && Simples.currentCSS(offsetParent, "position") === "static") ) { 200 offsetParent = offsetParent.offsetParent; 201 } 202 203 return offsetParent; 204 }, 205 /** 206 * @description to get the position of the element 207 * @param {Element} elem to get the position of 208 * @returns {Object} top, left 209 */ 210 position: function( elem ) { 211 212 // Get *real* offsetParent 213 var offsetParent = Simples.offsetParent( elem ), 214 215 // Get correct offsets 216 offset = Simples.offset( elem ), 217 parentOffset = REGEX_HTML_BODY.test(offsetParent.nodeName) ? { top: 0, left: 0 } : Simples.offset( offsetParent ); 218 219 // Subtract element margins 220 // note: when an element has margin: auto the offsetLeft and marginLeft 221 // are the same in Safari causing offset.left to incorrectly be 0 222 offset.top -= parseFloat( Simples.currentCSS(elem, "marginTop", true) ) || 0; 223 offset.left -= parseFloat( Simples.currentCSS(elem, "marginLeft", true) ) || 0; 224 225 // Add offsetParent borders 226 parentOffset.top += parseFloat( Simples.currentCSS(offsetParent, "borderTopWidth", true) ) || 0; 227 parentOffset.left += parseFloat( Simples.currentCSS(offsetParent, "borderLeftWidth", true) ) || 0; 228 229 // Subtract the two offsets 230 return { 231 top: offset.top - parentOffset.top, 232 left: offset.left - parentOffset.left 233 }; 234 }, 235 /** 236 * @description To set the scrollTop / scrollLeft for a given element 237 * @param {Element} elem element to set the scroll on 238 * @param {String} name 'top' or 'left' 239 * @param {Number} value 240 */ 241 setScroll : function( elem, name, val ){ 242 win = getWIN( elem ); 243 244 if ( win ) { 245 win.scrollTo( 246 name === LEFT ? val : Simples.getScroll( win, LEFT ), 247 name === TOP ? val : Simples.getScroll( win, TOP ) 248 ); 249 250 } else { 251 name = name === TOP ? "scrollTop" : "scrollLeft"; 252 elem[ name ] = val; 253 } 254 }, 255 /** 256 * @description To retrieve the scrollTop / scrollLeft for a given element 257 * @param {Element} elem element to get the scroll of 258 * @param {String} name 'top' or 'left' 259 * @returns {Number} the value of the scrollTop / scrollLeft 260 */ 261 getScroll : function( elem, name ){ 262 var isTop = name === TOP; 263 name = isTop ? "scrollTop" : "scrollLeft"; 264 win = getWIN( elem ); 265 266 // Return the scroll offset 267 return win ? ( ("pageXOffset" in win) ? win[ isTop ? "pageYOffset" : "pageXOffset" ] : Simples.support.boxModel && win.document.documentElement[ name ] || win.document.body[ name ] ) : elem[ name ]; 268 } 269 }); 270 271 Simples.extend( /** @lends Simples.fn */ { 272 /** 273 * @description To set or retrieve the offset of the selected elements on the Simples object 274 * @param {Object} options object with top and / or left specified to set the offset 275 * @returns {Number|Simples} the value of the offset or Simples object 276 */ 277 offset : function( options ){ 278 279 if ( options ) { 280 var len = this.length; 281 while( len ){ 282 Simples.setOffset( this[ --len ], options ); 283 } 284 return this; 285 } 286 287 return this[0] ? Simples.offset( this[0] ) : null; 288 }, 289 /** 290 * @description To return the same object with the offsetParents added in place of the selected elements 291 */ 292 offsetParent : function(){ 293 var len = this.length; 294 while( len ){ 295 this[ --len ] = Simples.offsetParent( this[ len ] ); 296 } 297 return this; 298 }, 299 /** 300 * @description To retrieve or set the scrollTop / scrollLeft elements on the simples object, if no value is provided the first element has the value return 301 * @param {String} name 'top' or 'left' 302 * @param {Number} val the value to set the offset to 303 * @returns {Number|Simples} the value of the scrollTop / scrollLeft or Simples object 304 */ 305 scroll : function( name, val ){ 306 if( val !== UNDEF ){ 307 var len = this.length; 308 while( len ){ 309 Simples.setScroll( this[ --len ], name, val ); 310 } 311 return this; 312 } 313 return this[0] ? Simples.getScroll( this[0], name ) : null; 314 }, 315 /** 316 * @description To retrieve or set the scrollTop / scrollLeft elements on the simples object 317 * @param {String} name 'top' or 'left' 318 * @param {Number} val the value to set the offset to 319 * @returns {Number} the value of the scrollTop / scrollLeft 320 */ 321 position : function(){ 322 return this[0] ? Simples.position( this[0] ) : null; 323 } 324 });