UNPKG

can

Version:

MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.

323 lines (256 loc) 8.15 kB
/* --- name: Element.Dimensions description: Contains methods to work with size, scroll, or positioning of Elements and the window object. license: MIT-style license. credits: - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html). - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html). requires: [Element, Element.Style] provides: [Element.Dimensions] ... */ (function(){ var element = document.createElement('div'), child = document.createElement('div'); element.style.height = '0'; element.appendChild(child); var brokenOffsetParent = (child.offsetParent === element); element = child = null; var heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'], widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth']; var svgCalculateSize = function(el){ var gCS = window.getComputedStyle(el), bounds = {x: 0, y: 0}; heightComponents.each(function(css){ bounds.y += parseFloat(gCS[css]); }); widthComponents.each(function(css){ bounds.x += parseFloat(gCS[css]); }); return bounds; }; var isOffset = function(el){ return styleString(el, 'position') != 'static' || isBody(el); }; var isOffsetStatic = function(el){ return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName); }; Element.implement({ scrollTo: function(x, y){ if (isBody(this)){ this.getWindow().scrollTo(x, y); } else { this.scrollLeft = x; this.scrollTop = y; } return this; }, getSize: function(){ if (isBody(this)) return this.getWindow().getSize(); //<ltIE9> // This if clause is because IE8- cannot calculate getBoundingClientRect of elements with visibility hidden. if (!window.getComputedStyle) return {x: this.offsetWidth, y: this.offsetHeight}; //</ltIE9> // This svg section under, calling `svgCalculateSize()`, can be removed when FF fixed the svg size bug. // Bug info: https://bugzilla.mozilla.org/show_bug.cgi?id=530985 if (this.get('tag') == 'svg') return svgCalculateSize(this); try { var bounds = this.getBoundingClientRect(); return {x: bounds.width, y: bounds.height}; } catch(e) { return {x: 0, y: 0}; } }, getScrollSize: function(){ if (isBody(this)) return this.getWindow().getScrollSize(); return {x: this.scrollWidth, y: this.scrollHeight}; }, getScroll: function(){ if (isBody(this)) return this.getWindow().getScroll(); return {x: this.scrollLeft, y: this.scrollTop}; }, getScrolls: function(){ var element = this.parentNode, position = {x: 0, y: 0}; while (element && !isBody(element)){ position.x += element.scrollLeft; position.y += element.scrollTop; element = element.parentNode; } return position; }, getOffsetParent: brokenOffsetParent ? function(){ var element = this; if (isBody(element) || styleString(element, 'position') == 'fixed') return null; var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset; while ((element = element.parentNode)){ if (isOffsetCheck(element)) return element; } return null; } : function(){ var element = this; if (isBody(element) || styleString(element, 'position') == 'fixed') return null; try { return element.offsetParent; } catch(e){} return null; }, getOffsets: function(){ var hasGetBoundingClientRect = this.getBoundingClientRect; //<1.4compat> hasGetBoundingClientRect = hasGetBoundingClientRect && !Browser.Platform.ios //</1.4compat> if (hasGetBoundingClientRect){ var bound = this.getBoundingClientRect(), html = document.id(this.getDocument().documentElement), htmlScroll = html.getScroll(), elemScrolls = this.getScrolls(), isFixed = (styleString(this, 'position') == 'fixed'); return { x: bound.left.toFloat() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft, y: bound.top.toFloat() + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop }; } var element = this, position = {x: 0, y: 0}; if (isBody(this)) return position; while (element && !isBody(element)){ position.x += element.offsetLeft; position.y += element.offsetTop; //<1.4compat> if (Browser.firefox){ if (!borderBox(element)){ position.x += leftBorder(element); position.y += topBorder(element); } var parent = element.parentNode; if (parent && styleString(parent, 'overflow') != 'visible'){ position.x += leftBorder(parent); position.y += topBorder(parent); } } else if (element != this && Browser.safari){ position.x += leftBorder(element); position.y += topBorder(element); } //</1.4compat> element = element.offsetParent; } //<1.4compat> if (Browser.firefox && !borderBox(this)){ position.x -= leftBorder(this); position.y -= topBorder(this); } //</1.4compat> return position; }, getPosition: function(relative){ var offset = this.getOffsets(), scroll = this.getScrolls(); var position = { x: offset.x - scroll.x, y: offset.y - scroll.y }; if (relative && (relative = document.id(relative))){ var relativePosition = relative.getPosition(); return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)}; } return position; }, getCoordinates: function(element){ if (isBody(this)) return this.getWindow().getCoordinates(); var position = this.getPosition(element), size = this.getSize(); var obj = { left: position.x, top: position.y, width: size.x, height: size.y }; obj.right = obj.left + obj.width; obj.bottom = obj.top + obj.height; return obj; }, computePosition: function(obj){ return { left: obj.x - styleNumber(this, 'margin-left'), top: obj.y - styleNumber(this, 'margin-top') }; }, setPosition: function(obj){ return this.setStyles(this.computePosition(obj)); } }); [Document, Window].invoke('implement', { getSize: function(){ var doc = getCompatElement(this); return {x: doc.clientWidth, y: doc.clientHeight}; }, getScroll: function(){ var win = this.getWindow(), doc = getCompatElement(this); return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop}; }, getScrollSize: function(){ var doc = getCompatElement(this), min = this.getSize(), body = this.getDocument().body; return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)}; }, getPosition: function(){ return {x: 0, y: 0}; }, getCoordinates: function(){ var size = this.getSize(); return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x}; } }); // private methods var styleString = Element.getComputedStyle; function styleNumber(element, style){ return styleString(element, style).toInt() || 0; } //<1.4compat> function borderBox(element){ return styleString(element, '-moz-box-sizing') == 'border-box'; } //</1.4compat> function topBorder(element){ return styleNumber(element, 'border-top-width'); } function leftBorder(element){ return styleNumber(element, 'border-left-width'); } function isBody(element){ return (/^(?:body|html)$/i).test(element.tagName); } function getCompatElement(element){ var doc = element.getDocument(); return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; } })(); //aliases Element.alias({position: 'setPosition'}); //compatability [Window, Document, Element].invoke('implement', { getHeight: function(){ return this.getSize().y; }, getWidth: function(){ return this.getSize().x; }, getScrollTop: function(){ return this.getScroll().y; }, getScrollLeft: function(){ return this.getScroll().x; }, getScrollHeight: function(){ return this.getScrollSize().y; }, getScrollWidth: function(){ return this.getScrollSize().x; }, getTop: function(){ return this.getPosition().y; }, getLeft: function(){ return this.getPosition().x; } });