within_viewport.js
234 lines
| 9.1 KiB
| application/javascript
|
JavascriptLexer
r4441 | /** | ||
* 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; | |||
})); |