Skip to content

Commit

Permalink
Merge pull request #17 from patik/implement-getboundingclientrect
Browse files Browse the repository at this point in the history
Implement getboundingclientrect
  • Loading branch information
patik committed Aug 2, 2015
2 parents 10ac918 + 0e16b0c commit 87e8223
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 139 deletions.
25 changes: 8 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,20 @@ Individual elements may have their own settings embedded in a `data` attribute u

You can specify *negative threshold values* to allow elements to reside outside the viewport.

## Browser Support

- IE 7 and higher
- All the others except Opera Mini
+ Tested in the latest stable Chrome, Firefox, Safari, and IE
+ No "new" JavaScript or quirky techniques are employed so it should work in all other modern browsers not specifically mentioned above

## What's Next

*Please note that the camel case `withinViewport` method name is deprecated. It will be removed in a future release.*

- Option to **fire events** when elements pass in and out of the viewport
- Test against Firefox 3.6, Safari 5.0.1
- Support IE7
- ~~Support IE7~~

No IE6 support is planned — if you'd like to add it, feel free to make a pull request.

Expand All @@ -182,22 +189,6 @@ Within Viewport is inspired by these similar utilities which only reflect whethe
* Remy Sharp's [Element 'in view' Event Plugin](http://remysharp.com/2009/01/26/element-in-view-event-plugin/)
* Mike Tuupola's [Viewport Selectors for jQuery](http://www.appelsiini.net/projects/viewport)

## History

### 0.2 - November 5, 2011

- Standalone version now available, no jQuery or other dependencies
- Cleaned up and standardized the jQuery plugin
- Added optional shortcut methods
- Added support for testing multiple sides at once (eg, left and bottom)
- Removed requirement for Array.forEach and replaced with faster while() loops
- Tested against IE8-9, Firefox 7, Chrome 15, Safari 5.1, Opera 11.52; Mac & Windows
- Included a demo

### 0.1 - October 15, 2011

- Initial beta version

## License

Have fun with it — [ISC](http://choosealicense.com/licenses/isc/). See included LICENSE file.
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "within-viewport",
"version": "0.1.0",
"version": "1.0.0",
"homepage": "http://patik.github.io/within-viewport/",
"authors": [
"Craig Patik <[email protected]>"
Expand Down
2 changes: 1 addition & 1 deletion demo/demo-nojquery.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ <h1>Within Viewport</h1>
<label for="show-boundary">Show boundary regions</label>
</div>
</form>
<p style="display:none;">Press <code>shift + arrow key</code>to nudge the page by 1 pixel</p>
<p style="display:none;">Press <code>shift + arrow key</code> to nudge the page by 1 pixel</p>
</div>
</div>
<!-- Boundary lines -->
Expand Down
2 changes: 1 addition & 1 deletion demo/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ <h1>Within Viewport</h1>
<label for="show-boundary">Show boundary regions</label>
</div>
</form>
<p style="display:none;">Press <code>shift + arrow key</code>to nudge the page by 1 pixel</p>
<p style="display:none;">Press <code>shift + arrow key</code> to nudge the page by 1 pixel</p>
</div>
</div>
<!-- Boundary lines -->
Expand Down
2 changes: 2 additions & 0 deletions demo/inc/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
// Set the styles so everything is nice and proportional to this device's screen
$body.append('<style>#boxContainer div { width:' + boxWidth + 'px;height:' + boxWidth + 'px;line-height:' + boxWidth + 'px; }</style>');
$boxes = $('#boxContainer div');
// Mark a couple of boxes for testing and debugging
$boxes.get(4).id = 'test';
$boxes.get(52).id = 'test2';

$showBoundsCheck = $('#show-boundary');
events.init();
Expand Down
5 changes: 2 additions & 3 deletions jquery.withinviewport.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/*global withinviewport: true */
/**
* Within Viewport jQuery Plugin
*
* @description Companion plugin for withinviewport.js - determines whether an element is completely within the browser viewport
* @author Craig Patik, http://patik.com/
* @version 0.1.0
* @date 2015-04-10
* @version 1.0.0
* @date 2015-08-02
*/
(function ($) {
/**
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "withinviewport",
"version": "0.1.0",
"version": "1.0.0",
"description": "Determine whether an element is completely within the browser viewport",
"main": "withinviewport.js",
"scripts": {
Expand Down
177 changes: 62 additions & 115 deletions withinviewport.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
*
* @description Determines whether an element is completely within the browser viewport
* @author Craig Patik, http://patik.com/
* @version 0.1.0
* @date 2015-04-10
* @version 1.0.0
* @date 2015-08-02
*/
(function (root, name, factory) {
// AMD
Expand All @@ -18,33 +18,25 @@
// Browser global
else {
root[name] = factory();

// Legacy support for camelCase naming
// DEPRECATED: will be removed in v1.0
root.withinViewport = function (a, b) {
try { console.warn('DEPRECATED: use lowercase `withinviewport()` instead'); } catch(e) { }
return withinviewport(a, b);
};
root.withinViewport.defaults = factory().defaults;
}
}(this, 'withinviewport', function () {
var canUseWindowDimensions = 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 withinviewport = function withinviewport (elem, options) {
var result = false;
var metadata = {};
var config = {};
var settings;
var useHtmlElem;
var isWithin;
var scrollOffset;
var elemOffset;
var arr;
var elemBoundingRect;
var sideNamesPattern;
var sides;
var side;
var i;

Expand All @@ -70,128 +62,83 @@
settings = options || {};
}

// Build configuration from defaults and given settings
config.container = settings.container || metadata.container || withinviewport.defaults.container || document.body;
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;
// 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;
config.left = settings.left || metadata.left || withinviewport.defaults.left || 0;

// Whether we can use the `<html`> element for `scrollTop`
// Unfortunately at the moment I can't find a way to do this without UA-sniffing
useHtmlElem = !/Chrome/.test(navigator.userAgent);
// 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;
}

// Element testing methods
isWithin = {
// Element is below the top edge of the viewport
top: function _isWithin_top() {
return elemOffset[1] >= scrollOffset[1] + config.top;
top: function _isWithin_top () {
return elemBoundingRect.top >= config.top;
},

// Element is to the left of the right edge of the viewport
right: function _isWithin_right() {
var container = (config.container === document.body) ? window : config.container;

return elemOffset[0] + elem.offsetWidth <= container.innerWidth + scrollOffset[0] - config.right;
},

// Element is above the bottom edge of the viewport
bottom: function _isWithin_bottom() {
var container = (config.container === document.body) ? window : config.container;

return elemOffset[1] + elem.offsetHeight <= scrollOffset[1] + container.innerHeight - config.bottom;
},

// Element is to the right of the left edge of the viewport
left: function _isWithin_left() {
return elemOffset[0] >= scrollOffset[0] + config.left;
},

all: function _isWithin_all() {
return (isWithin.top() && isWithin.right() && isWithin.bottom() && isWithin.left());
}
};

// Current offset values
scrollOffset = (function _scrollOffset() {
var x = config.container.scrollLeft;
var y = config.container.scrollTop;
right: function _isWithin_right () {
var containerWidth;

if (y === 0) {
if (config.container.pageYOffset) {
y = config.container.pageYOffset;
}
else if (window.pageYOffset) {
y = window.pageYOffset;
if (canUseWindowDimensions || config.container !== window) {
containerWidth = config.container.innerWidth;
}
else {
if (config.container === document.body) {
if (useHtmlElem) {
y = (config.container.parentElement) ? config.container.parentElement.scrollTop : 0;
}
else {
y = (config.container.parentElement) ? config.container.parentElement.scrollTop : 0;
}
}
else {
y = (config.container.parentElement) ? config.container.parentElement.scrollTop : 0;
}
containerWidth = document.documentElement.clientWidth;
}
}

if (x === 0) {
if (config.container.pageXOffset) {
x = config.container.pageXOffset;
}
else if (window.pageXOffset) {
x = window.pageXOffset;
// Note that `elemBoundingRect.right` is the distance from the *left* of the viewport to the element's far right edge
return elemBoundingRect.right <= containerWidth - config.right;
},

// Element is above the bottom edge of the viewport
bottom: function _isWithin_bottom () {
var containerHeight;

if (canUseWindowDimensions || config.container !== window) {
containerHeight = config.container.innerHeight;
}
else {
if (config.container === document.body) {
x = (config.container.parentElement) ? config.container.parentElement.scrollLeft : 0;
}
else {
x = (config.container.parentElement) ? config.container.parentElement.scrollLeft : 0;
}
containerHeight = document.documentElement.clientHeight;
}
}

return [x, y];
}());

elemOffset = (function _elemOffset() {
var el = elem;
var x = 0;
var y = 0;

if (el.parentNode) {
x = el.offsetLeft;
y = el.offsetTop;

el = el.parentNode;
while (el) {
if (el === config.container) {
break;
}
// Note that `elemBoundingRect.bottom` is the distance from the *top* of the viewport to the element's bottom edge
return elemBoundingRect.bottom <= containerHeight - config.bottom;
},

x += el.offsetLeft;
y += el.offsetTop;
// Element is to the right of the left edge of the viewport
left: function _isWithin_left () {
return elemBoundingRect.left >= config.left;
},

el = el.parentNode;
}
// Element is within all four boundaries
all: function _isWithin_all () {
// Test each boundary in order of most efficient and most likely to be false so that we can avoid running all four functions on most elements
// Top: Quickest to calculate + most likely to be false
// Bottom: Note quite as quick to calculate, but also very likely to be false
// Left and right are both equally unlikely to be false since most sites only scroll vertically, but left is faster
return (isWithin.top() && isWithin.bottom() && isWithin.left() && isWithin.right());
}
};

return [x, y];
})();
// Get the element's bounding rectangle with respect to the viewport
elemBoundingRect = elem.getBoundingClientRect();

// Test the element against each side of the viewport that was requested
arr = config.sides.split(' ');
i = arr.length;
sideNamesPattern = /^top$|^right$|^bottom$|^left$|^all$/;
// Loop through all of the sides
sides = config.sides.split(' ');
i = sides.length;
while (i--) {
side = arr[i].toLowerCase();
side = sides[i].toLowerCase();

if (/top|right|bottom|left|all/.test(side)) {
if (sideNamesPattern.test(side)) {
if (isWithin[side]()) {
result = true;
}
Expand Down Expand Up @@ -227,19 +174,19 @@

// 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) {
withinviewport.prototype.top = function _withinviewport_top (element) {
return withinviewport(element, 'top');
};

withinviewport.prototype.right = function _withinviewport_right(element) {
withinviewport.prototype.right = function _withinviewport_right (element) {
return withinviewport(element, 'right');
};

withinviewport.prototype.bottom = function _withinviewport_bottom(element) {
withinviewport.prototype.bottom = function _withinviewport_bottom (element) {
return withinviewport(element, 'bottom');
};

withinviewport.prototype.left = function _withinviewport_left(element) {
withinviewport.prototype.left = function _withinviewport_left (element) {
return withinviewport(element, 'left');
};

Expand Down

0 comments on commit 87e8223

Please sign in to comment.