1 /** 2 * @fileOverview dom-offset 3 * @author lifesinger@gmail.com,yiminghe@gmail.com 4 */ 5 KISSY.add('dom/offset', function (S, DOM, UA, undefined) { 6 7 var win = S.Env.host, 8 doc = win.document, 9 docElem = doc.documentElement, 10 getWin = DOM._getWin, 11 CSS1Compat = "CSS1Compat", 12 compatMode = "compatMode", 13 MAX = Math.max, 14 PARSEINT = parseInt, 15 POSITION = 'position', 16 RELATIVE = 'relative', 17 DOCUMENT = 'document', 18 BODY = 'body', 19 DOC_ELEMENT = 'documentElement', 20 OWNER_DOCUMENT = 'ownerDocument', 21 VIEWPORT = 'viewport', 22 SCROLL = 'scroll', 23 CLIENT = 'client', 24 LEFT = 'left', 25 TOP = 'top', 26 isNumber = S.isNumber, 27 SCROLL_LEFT = SCROLL + 'Left', 28 SCROLL_TOP = SCROLL + 'Top'; 29 30 S.mix(DOM, 31 /** 32 * @lends DOM 33 */ 34 { 35 36 /** 37 * Get the current coordinates of the first element in the set of matched elements, relative to the document. 38 * or 39 * Set the current coordinates of every element in the set of matched elements, relative to the document. 40 * @param {HTMLElement[]|String|HTMLElement} selector Matched elements 41 * @param {Object} [coordinates ] An object containing the properties top and left, 42 * which are integers indicating the new top and left coordinates for the elements. 43 * @param {Number} [coordinates.left ] the new top and left coordinates for the elements. 44 * @param {Number} [coordinates.top ] the new top and top coordinates for the elements. 45 * @param {window} [relativeWin] The window to measure relative to. If relativeWin 46 * is not in the ancestor frame chain of the element, we measure relative to 47 * the top-most window. 48 * @returns {Object|undefined} if Get, the format of returned value is same with coordinates. 49 */ 50 offset:function (selector, coordinates, relativeWin) { 51 // getter 52 if (coordinates === undefined) { 53 var elem = DOM.get(selector), ret; 54 if (elem) { 55 ret = getOffset(elem, relativeWin); 56 } 57 return ret; 58 } 59 // setter 60 var els = DOM.query(selector), i; 61 for (i = els.length - 1; i >= 0; i--) { 62 elem = els[i]; 63 setOffset(elem, coordinates); 64 } 65 return undefined; 66 }, 67 68 /** 69 * Makes the first of matched elements visible in the container 70 * @param {HTMLElement[]|String|HTMLElement} selector Matched elements 71 * @param {String|HTMLElement|Document} [container=window] Container element 72 * @param {Boolean} [top=true] Whether align with top of container. 73 * @param {Boolean} [hscroll=true] Whether trigger horizontal scroll. 74 * @param {Boolean} [auto=false] Whether adjust element automatically 75 * (only scrollIntoView when element is out of view) 76 * @see http://www.w3.org/TR/2009/WD-html5-20090423/editing.html#scrollIntoView 77 * http://www.sencha.com/deploy/dev/docs/source/Element.scroll-more.html#scrollIntoView 78 * http://yiminghe.javaeye.com/blog/390732 79 */ 80 scrollIntoView:function (selector, container, top, hscroll, auto) { 81 var elem; 82 83 if (!(elem = DOM.get(selector))) { 84 return; 85 } 86 87 if (container) { 88 container = DOM.get(container); 89 } 90 91 if (!container) { 92 container = elem.ownerDocument; 93 } 94 95 if (auto !== true) { 96 hscroll = hscroll === undefined ? true : !!hscroll; 97 top = top === undefined ? true : !!top; 98 } 99 100 // document 归一化到 window 101 if (container.nodeType == DOM.DOCUMENT_NODE) { 102 container = getWin(container); 103 } 104 105 var isWin = !!getWin(container), 106 elemOffset = DOM.offset(elem), 107 eh = DOM.outerHeight(elem), 108 ew = DOM.outerWidth(elem), 109 containerOffset, 110 ch, 111 cw, 112 containerScroll, 113 diffTop, 114 diffBottom, 115 win, 116 winScroll, 117 ww, 118 wh; 119 120 if (isWin) { 121 win = container; 122 wh = DOM.height(win); 123 ww = DOM.width(win); 124 winScroll = { 125 left:DOM.scrollLeft(win), 126 top:DOM.scrollTop(win) 127 }; 128 // elem 相对 container 可视视窗的距离 129 diffTop = { 130 left:elemOffset[LEFT] - winScroll[LEFT], 131 top:elemOffset[TOP] - winScroll[TOP] 132 }; 133 diffBottom = { 134 left:elemOffset[LEFT] + ew - (winScroll[LEFT] + ww), 135 top:elemOffset[TOP] + eh - (winScroll[TOP] + wh) 136 }; 137 containerScroll = winScroll; 138 } 139 else { 140 containerOffset = DOM.offset(container); 141 ch = container.clientHeight; 142 cw = container.clientWidth; 143 containerScroll = { 144 left:DOM.scrollLeft(container), 145 top:DOM.scrollTop(container) 146 }; 147 // elem 相对 container 可视视窗的距离 148 // 注意边框 , offset 是边框到根节点 149 diffTop = { 150 left:elemOffset[LEFT] - containerOffset[LEFT] - 151 (PARSEINT(DOM.css(container, 'borderLeftWidth')) || 0), 152 top:elemOffset[TOP] - containerOffset[TOP] - 153 (PARSEINT(DOM.css(container, 'borderTopWidth')) || 0) 154 }; 155 diffBottom = { 156 left:elemOffset[LEFT] + ew - 157 (containerOffset[LEFT] + cw + 158 (PARSEINT(DOM.css(container, 'borderRightWidth')) || 0)), 159 top:elemOffset[TOP] + eh - 160 (containerOffset[TOP] + ch + 161 (PARSEINT(DOM.css(container, 'borderBottomWidth')) || 0)) 162 }; 163 } 164 165 if (diffTop.top < 0 || diffBottom.top > 0) { 166 // 强制向上 167 if (top === true) { 168 DOM.scrollTop(container, containerScroll.top + diffTop.top); 169 } else if (top === false) { 170 DOM.scrollTop(container, containerScroll.top + diffBottom.top); 171 } else { 172 // 自动调整 173 if (diffTop.top < 0) { 174 DOM.scrollTop(container, containerScroll.top + diffTop.top); 175 } else { 176 DOM.scrollTop(container, containerScroll.top + diffBottom.top); 177 } 178 } 179 } 180 181 if (hscroll) { 182 if (diffTop.left < 0 || diffBottom.left > 0) { 183 // 强制向上 184 if (top === true) { 185 DOM.scrollLeft(container, containerScroll.left + diffTop.left); 186 } else if (top === false) { 187 DOM.scrollLeft(container, containerScroll.left + diffBottom.left); 188 } else { 189 // 自动调整 190 if (diffTop.left < 0) { 191 DOM.scrollLeft(container, containerScroll.left + diffTop.left); 192 } else { 193 DOM.scrollLeft(container, containerScroll.left + diffBottom.left); 194 } 195 } 196 } 197 } 198 }, 199 /** 200 * Get the width of document 201 * @param {window} [win=window] Window to be referred. 202 * @function 203 */ 204 docWidth:0, 205 /** 206 * Get the height of document 207 * @param {window} [win=window] Window to be referred. 208 * @function 209 */ 210 docHeight:0, 211 /** 212 * Get the height of window 213 * @param {window} [win=window] Window to be referred. 214 * @function 215 */ 216 viewportHeight:0, 217 /** 218 * Get the width of document 219 * @param {window} [win=window] Window to be referred. 220 * @function 221 */ 222 viewportWidth:0, 223 /** 224 * Get the current vertical position of the scroll bar for the first element in the set of matched elements. 225 * or 226 * Set the current vertical position of the scroll bar for each of the set of matched elements. 227 * @param {HTMLElement[]|String|HTMLElement|window} selector matched elements 228 * @param {Number} value An integer indicating the new position to set the scroll bar to. 229 * @function 230 */ 231 scrollTop:0, 232 /** 233 * Get the current horizontal position of the scroll bar for the first element in the set of matched elements. 234 * or 235 * Set the current horizontal position of the scroll bar for each of the set of matched elements. 236 * @param {HTMLElement[]|String|HTMLElement|window} selector matched elements 237 * @param {Number} value An integer indicating the new position to set the scroll bar to. 238 * @function 239 */ 240 scrollLeft:0 241 }); 242 243 // http://old.jr.pl/www.quirksmode.org/viewport/compatibility.html 244 // http://www.quirksmode.org/dom/w3c_cssom.html 245 // add ScrollLeft/ScrollTop getter/setter methods 246 S.each(['Left', 'Top'], function (name, i) { 247 var method = SCROLL + name; 248 249 DOM[method] = function (elem, v) { 250 if (isNumber(elem)) { 251 return arguments.callee(win, elem); 252 } 253 elem = DOM.get(elem); 254 var ret, 255 w = getWin(elem), 256 d; 257 if (w) { 258 if (v !== undefined) { 259 v = parseFloat(v); 260 // 注意多 windw 情况,不能简单取 win 261 var left = name == "Left" ? v : DOM.scrollLeft(w), 262 top = name == "Top" ? v : DOM.scrollTop(w); 263 w['scrollTo'](left, top); 264 } else { 265 //标准 266 //chrome == body.scrollTop 267 //firefox/ie9 == documentElement.scrollTop 268 ret = w[ 'page' + (i ? 'Y' : 'X') + 'Offset']; 269 if (!isNumber(ret)) { 270 d = w[DOCUMENT]; 271 //ie6,7,8 standard mode 272 ret = d[DOC_ELEMENT][method]; 273 if (!isNumber(ret)) { 274 //quirks mode 275 ret = d[BODY][method]; 276 } 277 } 278 } 279 } else if (elem.nodeType == DOM.ELEMENT_NODE) { 280 if (v !== undefined) { 281 elem[method] = parseFloat(v) 282 } else { 283 ret = elem[method]; 284 } 285 } 286 return ret; 287 } 288 }); 289 290 // add docWidth/Height, viewportWidth/Height getter methods 291 S.each(['Width', 'Height'], function (name) { 292 DOM['doc' + name] = function (refWin) { 293 refWin = DOM.get(refWin); 294 var w = getWin(refWin), 295 d = w[DOCUMENT]; 296 return MAX( 297 //firefox chrome documentElement.scrollHeight< body.scrollHeight 298 //ie standard mode : documentElement.scrollHeight> body.scrollHeight 299 d[DOC_ELEMENT][SCROLL + name], 300 //quirks : documentElement.scrollHeight 最大等于可视窗口多一点? 301 d[BODY][SCROLL + name], 302 DOM[VIEWPORT + name](d)); 303 }; 304 305 DOM[VIEWPORT + name] = function (refWin) { 306 refWin = DOM.get(refWin); 307 var prop = CLIENT + name, 308 win = getWin(refWin), 309 doc = win[DOCUMENT], 310 body = doc[BODY], 311 documentElement = doc[DOC_ELEMENT], 312 documentElementProp = documentElement[prop]; 313 // 标准模式取 documentElement 314 // backcompat 取 body 315 return doc[compatMode] === CSS1Compat 316 && documentElementProp || 317 body && body[ prop ] || documentElementProp; 318 // return (prop in w) ? 319 // // 标准 = documentElement.clientHeight 320 // w[prop] : 321 // // ie 标准 documentElement.clientHeight , 在 documentElement.clientHeight 上滚动? 322 // // ie quirks body.clientHeight: 在 body 上? 323 // (isStrict ? d[DOC_ELEMENT][CLIENT + name] : d[BODY][CLIENT + name]); 324 } 325 }); 326 327 function getClientPosition(elem) { 328 var box, x , y , 329 doc = elem.ownerDocument, 330 body = doc.body, 331 w = getWin(elem[OWNER_DOCUMENT]); 332 333 // 根据 GBS 最新数据,A-Grade Browsers 都已支持 getBoundingClientRect 方法,不用再考虑传统的实现方式 334 box = elem.getBoundingClientRect(); 335 336 // 注:jQuery 还考虑减去 docElem.clientLeft/clientTop 337 // 但测试发现,这样反而会导致当 html 和 body 有边距/边框样式时,获取的值不正确 338 // 此外,ie6 会忽略 html 的 margin 值,幸运地是没有谁会去设置 html 的 margin 339 340 x = box[LEFT]; 341 y = box[TOP]; 342 343 // In IE, most of the time, 2 extra pixels are added to the top and left 344 // due to the implicit 2-pixel inset border. In IE6/7 quirks mode and 345 // IE6 standards mode, this border can be overridden by setting the 346 // document element's border to zero -- thus, we cannot rely on the 347 // offset always being 2 pixels. 348 349 // In quirks mode, the offset can be determined by querying the body's 350 // clientLeft/clientTop, but in standards mode, it is found by querying 351 // the document element's clientLeft/clientTop. Since we already called 352 // getClientBoundingRect we have already forced a reflow, so it is not 353 // too expensive just to query them all. 354 355 // ie 下应该减去窗口的边框吧,毕竟默认 absolute 都是相对窗口定位的 356 // 窗口边框标准是设 documentElement ,quirks 时设置 body 357 // 最好禁止在 body 和 html 上边框 ,但 ie < 9 html 默认有 2px ,减去 358 // 但是非 ie 不可能设置窗口边框,body html 也不是窗口 ,ie 可以通过 html,body 设置 359 // 标准 ie 下 docElem.clientTop 就是 border-top 360 // ie7 html 即窗口边框改变不了。永远为 2 361 // 但标准 firefox/chrome/ie9 下 docElem.clientTop 是窗口边框,即使设了 border-top 也为 0 362 363 x -= docElem.clientLeft || body.clientLeft; 364 y -= docElem.clientTop || body.clientTop; 365 366 // iphone/ipad/itouch 下的 Safari 获取 getBoundingClientRect 时,已经加入 scrollTop 367 if (UA.mobile == 'apple') { 368 x -= DOM[SCROLL_LEFT](w); 369 y -= DOM[SCROLL_TOP](w); 370 } 371 372 return { left:x, top:y }; 373 } 374 375 376 function getPageOffset(el) { 377 var pos = getClientPosition(el); 378 var w = getWin(el[OWNER_DOCUMENT]); 379 pos.left += DOM[SCROLL_LEFT](w); 380 pos.top += DOM[SCROLL_TOP](w); 381 return pos; 382 } 383 384 // 获取 elem 相对 elem.ownerDocument 的坐标 385 function getOffset(el, relativeWin) { 386 var position = {left:0, top:0}; 387 388 // Iterate up the ancestor frame chain, keeping track of the current window 389 // and the current element in that window. 390 var currentWin = getWin(el[OWNER_DOCUMENT]); 391 var currentEl = el; 392 relativeWin = relativeWin || currentWin; 393 do { 394 // if we're at the top window, we want to get the page offset. 395 // if we're at an inner frame, we only want to get the window position 396 // so that we can determine the actual page offset in the context of 397 // the outer window. 398 var offset = currentWin == relativeWin ? 399 getPageOffset(currentEl) : 400 getClientPosition(currentEl); 401 position.left += offset.left; 402 position.top += offset.top; 403 } while (currentWin && currentWin != relativeWin && 404 (currentEl = currentWin['frameElement']) && 405 (currentWin = currentWin.parent)); 406 407 return position; 408 } 409 410 // 设置 elem 相对 elem.ownerDocument 的坐标 411 function setOffset(elem, offset) { 412 // set position first, in-case top/left are set even on static elem 413 if (DOM.css(elem, POSITION) === 'static') { 414 elem.style[POSITION] = RELATIVE; 415 } 416 var old = getOffset(elem), ret = { }, current, key; 417 418 for (key in offset) { 419 current = PARSEINT(DOM.css(elem, key), 10) || 0; 420 ret[key] = current + offset[key] - old[key]; 421 } 422 DOM.css(elem, ret); 423 } 424 425 return DOM; 426 }, { 427 requires:["./base", "ua"] 428 }); 429 430 /** 431 * 2012-03-30 432 * - refer: http://www.softcomplex.com/docs/get_window_size_and_scrollbar_position.html 433 * - http://help.dottoro.com/ljkfqbqj.php 434 * - http://www.boutell.com/newfaq/creating/sizeofclientarea.html 435 * 436 * 2011-05-24 437 * - 承玉: 438 * - 调整 docWidth , docHeight , 439 * viewportHeight , viewportWidth ,scrollLeft,scrollTop 参数, 440 * 便于放置到 Node 中去,可以完全摆脱 DOM,完全使用 Node 441 * 442 * 443 * TODO: 444 * - 考虑是否实现 jQuery 的 position, offsetParent 等功能 445 * - 更详细的测试用例(比如:测试 position 为 fixed 的情况) 446 */ 447