<!-- @license Copyright (c) 2015 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt --> <link rel="import" href="../polymer/polymer.html"> <script> /** Polymer.IronFitBehavior fits an element in another element using `max-height` and `max-width`, and optionally centers it in the window or another element. The element will only be sized and/or positioned if it has not already been sized and/or positioned by CSS. CSS properties | Action -----------------------------|------------------------------------------- `position` set | Element is not centered horizontally or vertically `top` or `bottom` set | Element is not vertically centered `left` or `right` set | Element is not horizontally centered `max-height` or `height` set | Element respects `max-height` or `height` `max-width` or `width` set | Element respects `max-width` or `width` @demo demo/index.html @polymerBehavior */ Polymer.IronFitBehavior = { properties: { /** * The element that will receive a `max-height`/`width`. By default it is the same as `this`, * but it can be set to a child element. This is useful, for example, for implementing a * scrolling region inside the element. * @type {!Element} */ sizingTarget: { type: Object, value: function() { return this; } }, /** * The element to fit `this` into. */ fitInto: { type: Object, value: window }, /** * Set to true to auto-fit on attach. */ autoFitOnAttach: { type: Boolean, value: false }, /** @type {?Object} */ _fitInfo: { type: Object } }, get _fitWidth() { var fitWidth; if (this.fitInto === window) { fitWidth = this.fitInto.innerWidth; } else { fitWidth = this.fitInto.getBoundingClientRect().width; } return fitWidth; }, get _fitHeight() { var fitHeight; if (this.fitInto === window) { fitHeight = this.fitInto.innerHeight; } else { fitHeight = this.fitInto.getBoundingClientRect().height; } return fitHeight; }, get _fitLeft() { var fitLeft; if (this.fitInto === window) { fitLeft = 0; } else { fitLeft = this.fitInto.getBoundingClientRect().left; } return fitLeft; }, get _fitTop() { var fitTop; if (this.fitInto === window) { fitTop = 0; } else { fitTop = this.fitInto.getBoundingClientRect().top; } return fitTop; }, attached: function() { if (this.autoFitOnAttach) { if (window.getComputedStyle(this).display === 'none') { setTimeout(function() { this.fit(); }.bind(this)); } else { this.fit(); } } }, /** * Fits and optionally centers the element into the window, or `fitInfo` if specified. */ fit: function() { this._discoverInfo(); this.constrain(); this.center(); }, /** * Memoize information needed to position and size the target element. */ _discoverInfo: function() { if (this._fitInfo) { return; } var target = window.getComputedStyle(this); var sizer = window.getComputedStyle(this.sizingTarget); this._fitInfo = { inlineStyle: { top: this.style.top || '', left: this.style.left || '' }, positionedBy: { vertically: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto' ? 'bottom' : null), horizontally: target.left !== 'auto' ? 'left' : (target.right !== 'auto' ? 'right' : null), css: target.position }, sizedBy: { height: sizer.maxHeight !== 'none', width: sizer.maxWidth !== 'none' }, margin: { top: parseInt(target.marginTop, 10) || 0, right: parseInt(target.marginRight, 10) || 0, bottom: parseInt(target.marginBottom, 10) || 0, left: parseInt(target.marginLeft, 10) || 0 } }; }, /** * Resets the target element's position and size constraints, and clear * the memoized data. */ resetFit: function() { if (!this._fitInfo || !this._fitInfo.sizedBy.height) { this.sizingTarget.style.maxHeight = ''; this.style.top = this._fitInfo ? this._fitInfo.inlineStyle.top : ''; } if (!this._fitInfo || !this._fitInfo.sizedBy.width) { this.sizingTarget.style.maxWidth = ''; this.style.left = this._fitInfo ? this._fitInfo.inlineStyle.left : ''; } if (this._fitInfo) { this.style.position = this._fitInfo.positionedBy.css; } this._fitInfo = null; }, /** * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after the element, * the window, or the `fitInfo` element has been resized. */ refit: function() { this.resetFit(); this.fit(); }, /** * Constrains the size of the element to the window or `fitInfo` by setting `max-height` * and/or `max-width`. */ constrain: function() { var info = this._fitInfo; // position at (0px, 0px) if not already positioned, so we can measure the natural size. if (!this._fitInfo.positionedBy.vertically) { this.style.top = '0px'; } if (!this._fitInfo.positionedBy.horizontally) { this.style.left = '0px'; } // need border-box for margin/padding this.sizingTarget.style.boxSizing = 'border-box'; // constrain the width and height if not already set var rect = this.getBoundingClientRect(); if (!info.sizedBy.height) { this._sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom', 'Height'); } if (!info.sizedBy.width) { this._sizeDimension(rect, info.positionedBy.horizontally, 'left', 'right', 'Width'); } }, _sizeDimension: function(rect, positionedBy, start, end, extent) { var info = this._fitInfo; var max = extent === 'Width' ? this._fitWidth : this._fitHeight; var flip = (positionedBy === end); var offset = flip ? max - rect[end] : rect[start]; var margin = info.margin[flip ? start : end]; var offsetExtent = 'offset' + extent; var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent]; this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingOffset) + 'px'; }, /** * Centers horizontally and vertically if not already positioned. This also sets * `position:fixed`. */ center: function() { if (!this._fitInfo.positionedBy.vertically || !this._fitInfo.positionedBy.horizontally) { // need position:fixed to center this.style.position = 'fixed'; } if (!this._fitInfo.positionedBy.vertically) { var top = (this._fitHeight - this.offsetHeight) / 2 + this._fitTop; top -= this._fitInfo.margin.top; this.style.top = top + 'px'; } if (!this._fitInfo.positionedBy.horizontally) { var left = (this._fitWidth - this.offsetWidth) / 2 + this._fitLeft; left -= this._fitInfo.margin.left; this.style.left = left + 'px'; } } }; </script>