1 /** 2 * @fileOverview Component.UIBase.Align 3 * @author yiminghe@gmail.com, qiaohua@taobao.com 4 */ 5 KISSY.add('component/uibase/align', function (S, UA, DOM, Node) { 6 7 var win = S.Env.host; 8 9 // var ieMode = document.documentMode || UA.ie; 10 11 /* 12 inspired by closure library by Google 13 see http://yiminghe.iteye.com/blog/1124720 14 */ 15 16 /** 17 * 得到会导致元素显示不全的祖先元素 18 */ 19 function getOffsetParent(element) { 20 // ie 这个也不是完全可行 21 /** 22 <div style="width: 50px;height: 100px;overflow: hidden"> 23 <div style="width: 50px;height: 100px;position: relative;" id="d6"> 24 元素 6 高 100px 宽 50px<br/> 25 </div> 26 </div> 27 **/ 28 // element.offsetParent does the right thing in ie7 and below. Return parent with layout! 29 // In other browsers it only includes elements with position absolute, relative or 30 // fixed, not elements with overflow set to auto or scroll. 31 // if (UA.ie && ieMode < 8) { 32 // return element.offsetParent; 33 // } 34 // 统一的 offsetParent 方法 35 var doc = element.ownerDocument, 36 body = doc.body, 37 parent, 38 positionStyle = DOM.css(element, 'position'), 39 skipStatic = positionStyle == 'fixed' || positionStyle == 'absolute'; 40 41 if (!skipStatic) { 42 return element.nodeName.toLowerCase() == 'html' ? null : element.parentNode; 43 } 44 45 for (parent = element.parentNode; parent && parent != body; parent = parent.parentNode) { 46 positionStyle = DOM.css(parent, 'position'); 47 if (positionStyle != "static") { 48 return parent; 49 } 50 } 51 return null; 52 } 53 54 /** 55 * 获得元素的显示部分的区域 56 */ 57 function getVisibleRectForElement(element) { 58 var visibleRect = { 59 left:0, 60 right:Infinity, 61 top:0, 62 bottom:Infinity 63 }, 64 el, 65 scrollX, 66 scrollY, 67 winSize, 68 doc = element.ownerDocument, 69 body = doc.body, 70 documentElement = doc.documentElement; 71 72 // Determine the size of the visible rect by climbing the dom accounting for 73 // all scrollable containers. 74 for (el = element; el = getOffsetParent(el);) { 75 // clientWidth is zero for inline block elements in ie. 76 if ((!UA.ie || el.clientWidth != 0) && 77 // body may have overflow set on it, yet we still get the entire 78 // viewport. In some browsers, el.offsetParent may be 79 // document.documentElement, so check for that too. 80 (el != body && el != documentElement && DOM.css(el, 'overflow') != 'visible')) { 81 var pos = DOM.offset(el); 82 // add border 83 pos.left += el.clientLeft; 84 pos.top += el.clientTop; 85 86 visibleRect.top = Math.max(visibleRect.top, pos.top); 87 visibleRect.right = Math.min(visibleRect.right, 88 // consider area without scrollBar 89 pos.left + el.clientWidth); 90 visibleRect.bottom = Math.min(visibleRect.bottom, 91 pos.top + el.clientHeight); 92 visibleRect.left = Math.max(visibleRect.left, pos.left); 93 } 94 } 95 96 // Clip by window's viewport. 97 scrollX = DOM.scrollLeft(); 98 scrollY = DOM.scrollTop(); 99 visibleRect.left = Math.max(visibleRect.left, scrollX); 100 visibleRect.top = Math.max(visibleRect.top, scrollY); 101 winSize = { 102 width:DOM.viewportWidth(), 103 height:DOM.viewportHeight() 104 }; 105 visibleRect.right = Math.min(visibleRect.right, scrollX + winSize.width); 106 visibleRect.bottom = Math.min(visibleRect.bottom, scrollY + winSize.height); 107 return visibleRect.top >= 0 && visibleRect.left >= 0 && 108 visibleRect.bottom > visibleRect.top && 109 visibleRect.right > visibleRect.left ? 110 visibleRect : null; 111 } 112 113 function getElFuturePos(elRegion, refNodeRegion, points, offset) { 114 var xy, 115 diff, 116 p1, 117 p2; 118 119 xy = { 120 left:elRegion.left, 121 top:elRegion.top 122 }; 123 124 p1 = getAlignOffset(refNodeRegion, points[0]); 125 p2 = getAlignOffset(elRegion, points[1]); 126 127 diff = [p2.left - p1.left, p2.top - p1.top]; 128 129 return { 130 left:xy.left - diff[0] + (+offset[0]), 131 top:xy.top - diff[1] + (+offset[1]) 132 }; 133 } 134 135 function isFailX(elFuturePos, elRegion, visibleRect) { 136 return elFuturePos.left < visibleRect.left || 137 elFuturePos.left + elRegion.width > visibleRect.right; 138 } 139 140 function isFailY(elFuturePos, elRegion, visibleRect) { 141 return elFuturePos.top < visibleRect.top || 142 elFuturePos.top + elRegion.height > visibleRect.bottom; 143 } 144 145 function adjustForViewport(elFuturePos, elRegion, visibleRect, overflow) { 146 var pos = S.clone(elFuturePos), 147 size = { 148 width:elRegion.width, 149 height:elRegion.height 150 }; 151 152 if (overflow.adjustX && pos.left < visibleRect.left) { 153 pos.left = visibleRect.left; 154 } 155 156 // Left edge inside and right edge outside viewport, try to resize it. 157 if (overflow['resizeWidth'] && 158 pos.left >= visibleRect.left && 159 pos.left + size.width > visibleRect.right) { 160 size.width -= (pos.left + size.width) - visibleRect.right; 161 } 162 163 // Right edge outside viewport, try to move it. 164 if (overflow.adjustX && pos.left + size.width > visibleRect.right) { 165 // 保证左边界和可视区域左边界对齐 166 pos.left = Math.max(visibleRect.right - size.width, visibleRect.left); 167 } 168 169 // Top edge outside viewport, try to move it. 170 if (overflow.adjustY && pos.top < visibleRect.top) { 171 pos.top = visibleRect.top; 172 } 173 174 // Top edge inside and bottom edge outside viewport, try to resize it. 175 if (overflow['resizeHeight'] && 176 pos.top >= visibleRect.top && 177 pos.top + size.height > visibleRect.bottom) { 178 size.height -= (pos.top + size.height) - visibleRect.bottom; 179 } 180 181 // Bottom edge outside viewport, try to move it. 182 if (overflow.adjustY && pos.top + size.height > visibleRect.bottom) { 183 // 保证上边界和可视区域上边界对齐 184 pos.top = Math.max(visibleRect.bottom - size.height, visibleRect.top); 185 } 186 187 return S.mix(pos, size); 188 } 189 190 191 function flip(points, reg, map) { 192 var ret = []; 193 S.each(points, function (p) { 194 ret.push(p.replace(reg, function (m) { 195 return map[m]; 196 })); 197 }); 198 return ret; 199 } 200 201 function flipOffset(offset, index) { 202 offset[index] = -offset[index]; 203 return offset; 204 } 205 206 207 /** 208 * @name Align 209 * @class 210 * Align extension class. 211 * Align component with specified element. 212 * @memberOf Component.UIBase 213 */ 214 function Align() { 215 } 216 217 218 Align.__getOffsetParent = getOffsetParent; 219 220 Align.__getVisibleRectForElement = getVisibleRectForElement; 221 222 Align.ATTRS = 223 /** 224 * @lends Component.UIBase.Align.prototype 225 */ 226 { 227 228 /** 229 * Align configuration. 230 * @type Object 231 * @field 232 * @example 233 * <code> 234 * { 235 * node: null, // 参考元素, falsy 或 window 为可视区域, 'trigger' 为触发元素, 其他为指定元素 236 * points: ['cc','cc'], // ['tr', 'tl'] 表示 overlay 的 tl 与参考节点的 tr 对齐 237 * offset: [0, 0] // 有效值为 [n, m] 238 * } 239 * </code> 240 */ 241 align:{ 242 value:{} 243 } 244 }; 245 246 function getRegion(node) { 247 var offset, w, h; 248 if (!S.isWindow(node[0])) { 249 offset = node.offset(); 250 w = node.outerWidth(); 251 h = node.outerHeight(); 252 } else { 253 offset = { left:DOM.scrollLeft(), top:DOM.scrollTop() }; 254 w = DOM.viewportWidth(); 255 h = DOM.viewportHeight(); 256 } 257 offset.width = w; 258 offset.height = h; 259 return offset; 260 } 261 262 /** 263 * 获取 node 上的 align 对齐点 相对于页面的坐标 264 * @param region 265 * @param align 266 */ 267 function getAlignOffset(region, align) { 268 var V = align.charAt(0), 269 H = align.charAt(1), 270 w = region.width, 271 h = region.height, 272 x, y; 273 274 x = region.left; 275 y = region.top; 276 277 if (V === 'c') { 278 y += h / 2; 279 } else if (V === 'b') { 280 y += h; 281 } 282 283 if (H === 'c') { 284 x += w / 2; 285 } else if (H === 'r') { 286 x += w; 287 } 288 289 return { left:x, top:y }; 290 } 291 292 Align.prototype = 293 /** 294 * @lends Component.UIBase.Align.prototype 295 */ 296 { 297 _uiSetAlign:function (v) { 298 if (v && v.points) { 299 this.align(v.node, v.points, v.offset, v.overflow); 300 } 301 }, 302 303 /* 304 对齐 Overlay 到 node 的 points 点, 偏移 offset 处 305 @function 306 @ignore 307 @param {Element} node 参照元素, 可取配置选项中的设置, 也可是一元素 308 @param {String[]} points 对齐方式 309 @param {Number[]} [offset] 偏移 310 */ 311 align:function (refNode, points, offset, overflow) { 312 refNode = Node.one(refNode || win); 313 offset = offset && [].concat(offset) || [0, 0]; 314 overflow = overflow || {}; 315 316 var self = this, 317 el = self.get("el"), 318 fail = 0, 319 // 当前节点可以被放置的显示区域 320 visibleRect = getVisibleRectForElement(el[0]), 321 // 当前节点所占的区域, left/top/width/height 322 elRegion = getRegion(el), 323 // 参照节点所占的区域, left/top/width/height 324 refNodeRegion = getRegion(refNode), 325 // 当前节点将要被放置的位置 326 elFuturePos = getElFuturePos(elRegion, refNodeRegion, points, offset), 327 // 当前节点将要所处的区域 328 newElRegion = S.merge(elRegion, elFuturePos); 329 330 // 如果可视区域不能完全放置当前节点时允许调整 331 if (visibleRect && (overflow.adjustX || overflow.adjustY)) { 332 333 // 如果横向不能放下 334 if (isFailX(elFuturePos, elRegion, visibleRect)) { 335 fail = 1; 336 // 对齐位置反下 337 points = flip(points, /[lr]/ig, { 338 l:"r", 339 r:"l" 340 }); 341 // 偏移量也反下 342 offset = flipOffset(offset, 0); 343 } 344 345 // 如果纵向不能放下 346 if (isFailY(elFuturePos, elRegion, visibleRect)) { 347 fail = 1; 348 // 对齐位置反下 349 points = flip(points, /[tb]/ig, { 350 t:"b", 351 b:"t" 352 }); 353 // 偏移量也反下 354 offset = flipOffset(offset, 1); 355 } 356 357 // 如果失败,重新计算当前节点将要被放置的位置 358 if (fail) { 359 elFuturePos = getElFuturePos(elRegion, refNodeRegion, points, offset); 360 S.mix(newElRegion, elFuturePos); 361 } 362 363 var newOverflowCfg = {}; 364 365 // 检查反下后的位置是否可以放下了 366 // 如果仍然放不下只有指定了可以调整当前方向才调整 367 newOverflowCfg.adjustX = overflow.adjustX && 368 isFailX(elFuturePos, elRegion, visibleRect); 369 370 newOverflowCfg.adjustY = overflow.adjustY && 371 isFailY(elFuturePos, elRegion, visibleRect); 372 373 // 确实要调整,甚至可能会调整高度宽度 374 if (newOverflowCfg.adjustX || newOverflowCfg.adjustY) { 375 newElRegion = adjustForViewport(elFuturePos, elRegion, 376 visibleRect, newOverflowCfg); 377 } 378 } 379 380 // 新区域位置发生了变化 381 if (newElRegion.left != elRegion.left) { 382 self.set("x", newElRegion.left) 383 } 384 385 if (newElRegion.top != elRegion.top) { 386 self.set("y", newElRegion.top) 387 } 388 389 // 新区域高宽发生了变化 390 if (newElRegion.width != elRegion.width) { 391 el.width(el.width() + newElRegion.width - elRegion.width); 392 } 393 if (newElRegion.height != elRegion.height) { 394 el.height(el.height() + newElRegion.height - elRegion.height); 395 } 396 397 return self; 398 }, 399 400 /** 401 * Make current element center within node. 402 * @param {undefined|String|HTMLElement|NodeList} node 403 * Same as node config of {@link Component.UIBase.Align#align} . 404 */ 405 center:function (node) { 406 var self = this; 407 self.set('align', { 408 node:node, 409 points:["cc", "cc"], 410 offset:[0, 0] 411 }); 412 return self; 413 } 414 }; 415 416 return Align; 417 }, { 418 requires:["ua", "dom", "node"] 419 }); 420 /** 421 * 2012-04-26 yiminghe@gmail.com 422 * - 优化智能对齐算法 423 * - 慎用 resizeXX 424 * 425 * 2011-07-13 yiminghe@gmail.com note: 426 * - 增加智能对齐,以及大小调整选项 427 **/