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