##// END OF EJS Templates
reviewers: added observers as another way to define reviewers....
reviewers: added observers as another way to define reviewers. - fixes #4183

File last commit:

r4441:114e65cb default
r4500:bfede169 stable
Show More
within_viewport.js
234 lines | 9.1 KiB | application/javascript | JavascriptLexer
/**
* Within Viewport
*
* @description Determines whether an element is completely within the browser viewport
* @author Craig Patik, http://patik.com/
* @version 2.1.2
* @date 2019-08-16
*/
(function (root, name, factory) {
// AMD
if (typeof define === 'function' && define.amd) {
define([], factory);
}
// Node and CommonJS-like environments
else if (typeof module !== 'undefined' && typeof exports === 'object') {
module.exports = factory();
}
// Browser global
else {
root[name] = factory();
}
}(this, 'withinviewport', function () {
var canUseWindowDimensions = typeof window !== 'undefined' && window.innerHeight !== undefined; // IE 8 and lower fail this
/**
* Determines whether an element is within the viewport
* @param {Object} elem DOM Element (required)
* @param {Object} options Optional settings
* @return {Boolean} Whether the element was completely within the viewport
*/
var withinviewport = function withinviewport(elem, options) {
var result = false;
var metadata = {};
var config = {};
var settings;
var isWithin;
var isContainerTheWindow;
var elemBoundingRect;
var containerBoundingRect;
var containerScrollTop;
var containerScrollLeft;
var scrollBarWidths = [0, 0];
var sideNamesPattern;
var sides;
var side;
var i;
// If invoked by the jQuery plugin, get the actual DOM element
if (typeof jQuery !== 'undefined' && elem instanceof jQuery) {
elem = elem.get(0);
}
if (typeof elem !== 'object' || elem.nodeType !== 1) {
throw new Error('First argument must be an element');
}
// Look for inline settings on the element
if (elem.getAttribute('data-withinviewport-settings') && window.JSON) {
metadata = JSON.parse(elem.getAttribute('data-withinviewport-settings'));
}
// Settings argument may be a simple string (`top`, `right`, etc)
if (typeof options === 'string') {
settings = {
sides: options
};
} else {
settings = options || {};
}
// Build configuration from defaults and user-provided settings and metadata
config.container = settings.container || metadata.container || withinviewport.defaults.container || window;
config.sides = settings.sides || metadata.sides || withinviewport.defaults.sides || 'all';
config.top = settings.top || metadata.top || withinviewport.defaults.top || 0;
config.right = settings.right || metadata.right || withinviewport.defaults.right || 0;
config.bottom = settings.bottom || metadata.bottom || withinviewport.defaults.bottom || 0;
config.left = settings.left || metadata.left || withinviewport.defaults.left || 0;
// Extract the DOM node from a jQuery collection
if (typeof jQuery !== 'undefined' && config.container instanceof jQuery) {
config.container = config.container.get(0);
}
// Use the window as the container if the user specified the body or a non-element
if (config.container === document.body || config.container.nodeType !== 1) {
config.container = window;
}
isContainerTheWindow = (config.container === window);
// Element testing methods
isWithin = {
// Element is below the top edge of the viewport
top: function _isWithin_top() {
if (isContainerTheWindow) {
return (elemBoundingRect.top >= config.top);
} else {
return (elemBoundingRect.top >= containerScrollTop - (containerScrollTop - containerBoundingRect.top) + config.top);
}
},
// Element is to the left of the right edge of the viewport
right: function _isWithin_right() {
// Note that `elemBoundingRect.right` is the distance from the *left* of the viewport to the element's far right edge
if (isContainerTheWindow) {
return (elemBoundingRect.right <= (containerBoundingRect.right + containerScrollLeft) - config.right);
} else {
return (elemBoundingRect.right <= containerBoundingRect.right - scrollBarWidths[0] - config.right);
}
},
// Element is above the bottom edge of the viewport
bottom: function _isWithin_bottom() {
var containerHeight = 0;
if (isContainerTheWindow) {
if (canUseWindowDimensions) {
containerHeight = config.container.innerHeight;
} else if (document && document.documentElement) {
containerHeight = document.documentElement.clientHeight;
}
} else {
containerHeight = containerBoundingRect.bottom;
}
// Note that `elemBoundingRect.bottom` is the distance from the *top* of the viewport to the element's bottom edge
return (elemBoundingRect.bottom <= containerHeight - scrollBarWidths[1] - config.bottom);
},
// Element is to the right of the left edge of the viewport
left: function _isWithin_left() {
if (isContainerTheWindow) {
return (elemBoundingRect.left >= config.left);
} else {
return (elemBoundingRect.left >= containerScrollLeft - (containerScrollLeft - containerBoundingRect.left) + config.left);
}
},
// Element is within all four boundaries
all: function _isWithin_all() {
// Test each boundary in order of efficiency and likeliness to be false. This way we can avoid running all four functions on most elements.
// 1. Top: Quickest to calculate + most likely to be false
// 2. Bottom: Note quite as quick to calculate, but also very likely to be false
// 3-4. Left and right are both equally unlikely to be false since most sites only scroll vertically, but left is faster to calculate
return (isWithin.top() && isWithin.bottom() && isWithin.left() && isWithin.right());
}
};
// Get the element's bounding rectangle with respect to the viewport
elemBoundingRect = elem.getBoundingClientRect();
// Get viewport dimensions and offsets
if (isContainerTheWindow) {
containerBoundingRect = document.documentElement.getBoundingClientRect();
containerScrollTop = document.body.scrollTop;
containerScrollLeft = window.scrollX || document.body.scrollLeft;
} else {
containerBoundingRect = config.container.getBoundingClientRect();
containerScrollTop = config.container.scrollTop;
containerScrollLeft = config.container.scrollLeft;
}
// Don't count the space consumed by scrollbars
if (containerScrollLeft) {
scrollBarWidths[0] = 18;
}
if (containerScrollTop) {
scrollBarWidths[1] = 16;
}
// Test the element against each side of the viewport that was requested
sideNamesPattern = /^top$|^right$|^bottom$|^left$|^all$/;
// Loop through all of the sides
sides = config.sides.split(' ');
i = sides.length;
while (i--) {
side = sides[i].toLowerCase();
if (sideNamesPattern.test(side)) {
if (isWithin[side]()) {
result = true;
} else {
result = false;
// Quit as soon as the first failure is found
break;
}
}
}
return result;
};
// Default settings
withinviewport.prototype.defaults = {
container: typeof document !== 'undefined' ? document.body : {},
sides: 'all',
top: 0,
right: 0,
bottom: 0,
left: 0
};
withinviewport.defaults = withinviewport.prototype.defaults;
/**
* Optional enhancements and shortcuts
*
* @description Uncomment or comment these pieces as they apply to your project and coding preferences
*/
// Shortcut methods for each side of the viewport
// Example: `withinviewport.top(elem)` is the same as `withinviewport(elem, 'top')`
withinviewport.prototype.top = function _withinviewport_top(element) {
return withinviewport(element, 'top');
};
withinviewport.prototype.right = function _withinviewport_right(element) {
return withinviewport(element, 'right');
};
withinviewport.prototype.bottom = function _withinviewport_bottom(element) {
return withinviewport(element, 'bottom');
};
withinviewport.prototype.left = function _withinviewport_left(element) {
return withinviewport(element, 'left');
};
return withinviewport;
}));