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