|
|
/**
|
|
|
* 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;
|
|
|
}));
|