|
|
@@ -10,6 +10,7 @@
|
|
|
import global from './global';
|
|
|
import isRange from './isrange';
|
|
|
import isElement from '../lib/lodash/isElement';
|
|
|
+import getBorderWidths from './getborderwidths';
|
|
|
|
|
|
/**
|
|
|
* A helper class representing a `ClientRect` object, e.g. value returned by
|
|
|
@@ -35,9 +36,17 @@ export default class Rect {
|
|
|
* // Rect out of a ClientRect.
|
|
|
* const rectE = new Rect( document.body.getClientRects().item( 0 ) );
|
|
|
*
|
|
|
+ * **Note**: By default `Rect` of `HTMLElement` includes its CSS borders and scrollbars (if any).
|
|
|
+ * Use `options.excludeScrollbarsAndBorders` to obtain an "inner rect".
|
|
|
+ *
|
|
|
+ * // Rect of an HTMLElement, scrollbars excluded.
|
|
|
+ * const rectF = new Rect( document.body, { excludeScrollbarsAndBorders: true } );
|
|
|
+ *
|
|
|
* @param {HTMLElement|Range|ClientRect|module:utils/dom/rect~Rect|Object} source A source object to create the rect.
|
|
|
+ * @param {Boolean} [options.excludeScrollbarsAndBorders] When set `true` the `Rect` will not include
|
|
|
+ * CSS borders and scrollbars. The option is valid for `HTMLElement` passed as a `source` only.
|
|
|
*/
|
|
|
- constructor( source ) {
|
|
|
+ constructor( source, options = {} ) {
|
|
|
/**
|
|
|
* The object this rect is for.
|
|
|
*
|
|
|
@@ -46,14 +55,18 @@ export default class Rect {
|
|
|
* @member {HTMLElement|Range|ClientRect|module:utils/dom/rect~Rect|Object} #_source
|
|
|
*/
|
|
|
Object.defineProperty( this, '_source', {
|
|
|
- // source._source if already the Rect instance
|
|
|
+ // If the source is a Rect instance, copy it's #_source.
|
|
|
value: source._source || source,
|
|
|
- writable: false,
|
|
|
+ writable: true,
|
|
|
enumerable: false
|
|
|
} );
|
|
|
|
|
|
if ( isElement( source ) ) {
|
|
|
copyRectProperties( this, source.getBoundingClientRect() );
|
|
|
+
|
|
|
+ if ( options.excludeScrollbarsAndBorders ) {
|
|
|
+ this._excludeScrollbarsAndBorders();
|
|
|
+ }
|
|
|
} else if ( isRange( source ) ) {
|
|
|
copyRectProperties( this, Rect.getDomRangeRects( source )[ 0 ] );
|
|
|
} else {
|
|
|
@@ -233,15 +246,49 @@ export default class Rect {
|
|
|
return visibleRect;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Checks if all properties ({@link #top}, {@link #left}, {@link #right},
|
|
|
+ * {@link #bottom}, {@link #width} and {@link #height}) are the same as in the other `Rect`.
|
|
|
+ *
|
|
|
+ * @param {Rect} rect A `Rect` instance to compare with.
|
|
|
+ * @returns {Boolean} `true` when Rects are equal. `false` otherwise.
|
|
|
+ */
|
|
|
+ isEqual( anotherRect ) {
|
|
|
+ for ( const prop of rectProperties ) {
|
|
|
+ if ( this[ prop ] !== anotherRect[ prop ] ) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Checks whether a `Rect` fully contains another `Rect` instance.
|
|
|
+ *
|
|
|
+ * @param {module:utils/dom/rect~Rect} anotherRect
|
|
|
+ * @returns {Boolean} `true` if contains, `false` otherwise.
|
|
|
+ */
|
|
|
+ contains( anotherRect ) {
|
|
|
+ const intersectRect = this.getIntersection( anotherRect );
|
|
|
+
|
|
|
+ if ( !intersectRect || !intersectRect.isEqual( anotherRect ) ) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Returns a rect of the web browser viewport.
|
|
|
*
|
|
|
* @returns {module:utils/dom/rect~Rect} A viewport rect.
|
|
|
+ * @param {Boolean} [options.excludeScrollbars] When set `true` the `Rect` will not include
|
|
|
+ * the scrollbars of the viewport.
|
|
|
*/
|
|
|
- static getViewportRect() {
|
|
|
+ static getViewportRect( options = {} ) {
|
|
|
const { innerWidth, innerHeight } = global.window;
|
|
|
-
|
|
|
- return new Rect( {
|
|
|
+ const rect = new Rect( {
|
|
|
top: 0,
|
|
|
right: innerWidth,
|
|
|
bottom: innerHeight,
|
|
|
@@ -249,6 +296,14 @@ export default class Rect {
|
|
|
width: innerWidth,
|
|
|
height: innerHeight
|
|
|
} );
|
|
|
+
|
|
|
+ rect._source = global.window;
|
|
|
+
|
|
|
+ if ( options.excludeScrollbars ) {
|
|
|
+ rect._excludeScrollbarsAndBorders();
|
|
|
+ }
|
|
|
+
|
|
|
+ return rect;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -280,6 +335,38 @@ export default class Rect {
|
|
|
|
|
|
return rects;
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Excludes scrollbars and CSS borders from the `Rect`.
|
|
|
+ *
|
|
|
+ * * Borders are removed when {@link #_source} is `HTMLElement`.
|
|
|
+ * * Scrollbars are excluded from `HTMLElements` and {@link #getViewportRect viewport rects}.
|
|
|
+ *
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ _excludeScrollbarsAndBorders() {
|
|
|
+ const source = this._source;
|
|
|
+ let scrollBarWidth, scrollBarHeight;
|
|
|
+
|
|
|
+ if ( source === global.window ) {
|
|
|
+ scrollBarWidth = global.window.innerWidth - global.document.documentElement.clientWidth;
|
|
|
+ scrollBarHeight = global.window.innerHeight - global.document.documentElement.clientHeight;
|
|
|
+ } else {
|
|
|
+ const borderWidths = getBorderWidths( this._source );
|
|
|
+
|
|
|
+ scrollBarWidth = source.offsetWidth - source.clientWidth;
|
|
|
+ scrollBarHeight = source.offsetHeight - source.clientHeight;
|
|
|
+
|
|
|
+ this.moveBy( borderWidths.left, borderWidths.top );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Assuming LTR scrollbars. TODO: RTL.
|
|
|
+ this.width -= scrollBarWidth;
|
|
|
+ this.right -= scrollBarWidth;
|
|
|
+
|
|
|
+ this.height -= scrollBarHeight;
|
|
|
+ this.bottom -= scrollBarHeight;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const rectProperties = [ 'top', 'right', 'bottom', 'left', 'width', 'height' ];
|