Skip to content

Commit 87e8223

Browse files
committed
Merge pull request #17 from patik/implement-getboundingclientrect
Implement getboundingclientrect
2 parents 10ac918 + 0e16b0c commit 87e8223

File tree

8 files changed

+78
-139
lines changed

8 files changed

+78
-139
lines changed

README.md

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,20 @@ Individual elements may have their own settings embedded in a `data` attribute u
165165

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

168+
## Browser Support
169+
170+
- IE 7 and higher
171+
- All the others except Opera Mini
172+
+ Tested in the latest stable Chrome, Firefox, Safari, and IE
173+
+ No "new" JavaScript or quirky techniques are employed so it should work in all other modern browsers not specifically mentioned above
174+
168175
## What's Next
169176

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

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

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

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

185-
## History
186-
187-
### 0.2 - November 5, 2011
188-
189-
- Standalone version now available, no jQuery or other dependencies
190-
- Cleaned up and standardized the jQuery plugin
191-
- Added optional shortcut methods
192-
- Added support for testing multiple sides at once (eg, left and bottom)
193-
- Removed requirement for Array.forEach and replaced with faster while() loops
194-
- Tested against IE8-9, Firefox 7, Chrome 15, Safari 5.1, Opera 11.52; Mac & Windows
195-
- Included a demo
196-
197-
### 0.1 - October 15, 2011
198-
199-
- Initial beta version
200-
201192
## License
202193

203194
Have fun with it — [ISC](http://choosealicense.com/licenses/isc/). See included LICENSE file.

bower.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "within-viewport",
3-
"version": "0.1.0",
3+
"version": "1.0.0",
44
"homepage": "http://patik.github.io/within-viewport/",
55
"authors": [
66
"Craig Patik <[email protected]>"

demo/demo-nojquery.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ <h1>Within Viewport</h1>
4141
<label for="show-boundary">Show boundary regions</label>
4242
</div>
4343
</form>
44-
<p style="display:none;">Press <code>shift + arrow key</code>to nudge the page by 1 pixel</p>
44+
<p style="display:none;">Press <code>shift + arrow key</code> to nudge the page by 1 pixel</p>
4545
</div>
4646
</div>
4747
<!-- Boundary lines -->

demo/demo.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ <h1>Within Viewport</h1>
4343
<label for="show-boundary">Show boundary regions</label>
4444
</div>
4545
</form>
46-
<p style="display:none;">Press <code>shift + arrow key</code>to nudge the page by 1 pixel</p>
46+
<p style="display:none;">Press <code>shift + arrow key</code> to nudge the page by 1 pixel</p>
4747
</div>
4848
</div>
4949
<!-- Boundary lines -->

demo/inc/demo.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@
3636
// Set the styles so everything is nice and proportional to this device's screen
3737
$body.append('<style>#boxContainer div { width:' + boxWidth + 'px;height:' + boxWidth + 'px;line-height:' + boxWidth + 'px; }</style>');
3838
$boxes = $('#boxContainer div');
39+
// Mark a couple of boxes for testing and debugging
3940
$boxes.get(4).id = 'test';
41+
$boxes.get(52).id = 'test2';
4042

4143
$showBoundsCheck = $('#show-boundary');
4244
events.init();

jquery.withinviewport.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
/*global withinviewport: true */
21
/**
32
* Within Viewport jQuery Plugin
43
*
54
* @description Companion plugin for withinviewport.js - determines whether an element is completely within the browser viewport
65
* @author Craig Patik, http://patik.com/
7-
* @version 0.1.0
8-
* @date 2015-04-10
6+
* @version 1.0.0
7+
* @date 2015-08-02
98
*/
109
(function ($) {
1110
/**

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "withinviewport",
3-
"version": "0.1.0",
3+
"version": "1.0.0",
44
"description": "Determine whether an element is completely within the browser viewport",
55
"main": "withinviewport.js",
66
"scripts": {

withinviewport.js

Lines changed: 62 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
*
44
* @description Determines whether an element is completely within the browser viewport
55
* @author Craig Patik, http://patik.com/
6-
* @version 0.1.0
7-
* @date 2015-04-10
6+
* @version 1.0.0
7+
* @date 2015-08-02
88
*/
99
(function (root, name, factory) {
1010
// AMD
@@ -18,33 +18,25 @@
1818
// Browser global
1919
else {
2020
root[name] = factory();
21-
22-
// Legacy support for camelCase naming
23-
// DEPRECATED: will be removed in v1.0
24-
root.withinViewport = function (a, b) {
25-
try { console.warn('DEPRECATED: use lowercase `withinviewport()` instead'); } catch(e) { }
26-
return withinviewport(a, b);
27-
};
28-
root.withinViewport.defaults = factory().defaults;
2921
}
3022
}(this, 'withinviewport', function () {
23+
var canUseWindowDimensions = window.innerHeight !== undefined; // IE 8 and lower fail this
3124

3225
/**
3326
* Determines whether an element is within the viewport
3427
* @param {Object} elem DOM Element (required)
3528
* @param {Object} options Optional settings
3629
* @return {Boolean} Whether the element was completely within the viewport
3730
*/
38-
var withinviewport = function withinviewport(elem, options) {
31+
var withinviewport = function withinviewport (elem, options) {
3932
var result = false;
4033
var metadata = {};
4134
var config = {};
4235
var settings;
43-
var useHtmlElem;
4436
var isWithin;
45-
var scrollOffset;
46-
var elemOffset;
47-
var arr;
37+
var elemBoundingRect;
38+
var sideNamesPattern;
39+
var sides;
4840
var side;
4941
var i;
5042

@@ -70,128 +62,83 @@
7062
settings = options || {};
7163
}
7264

73-
// Build configuration from defaults and given settings
74-
config.container = settings.container || metadata.container || withinviewport.defaults.container || document.body;
75-
config.sides = settings.sides || metadata.sides || withinviewport.defaults.sides || 'all';
76-
config.top = settings.top || metadata.top || withinviewport.defaults.top || 0;
77-
config.right = settings.right || metadata.right || withinviewport.defaults.right || 0;
65+
// Build configuration from defaults and user-provided settings and metadata
66+
config.container = settings.container || metadata.container || withinviewport.defaults.container || window;
67+
config.sides = settings.sides || metadata.sides || withinviewport.defaults.sides || 'all';
68+
config.top = settings.top || metadata.top || withinviewport.defaults.top || 0;
69+
config.right = settings.right || metadata.right || withinviewport.defaults.right || 0;
7870
config.bottom = settings.bottom || metadata.bottom || withinviewport.defaults.bottom || 0;
79-
config.left = settings.left || metadata.left || withinviewport.defaults.left || 0;
71+
config.left = settings.left || metadata.left || withinviewport.defaults.left || 0;
8072

81-
// Whether we can use the `<html`> element for `scrollTop`
82-
// Unfortunately at the moment I can't find a way to do this without UA-sniffing
83-
useHtmlElem = !/Chrome/.test(navigator.userAgent);
73+
// Use the window as the container if the user specified the body or a non-element
74+
if (config.container === document.body || !config.container.nodeType === 1) {
75+
config.container = window;
76+
}
8477

8578
// Element testing methods
8679
isWithin = {
8780
// Element is below the top edge of the viewport
88-
top: function _isWithin_top() {
89-
return elemOffset[1] >= scrollOffset[1] + config.top;
81+
top: function _isWithin_top () {
82+
return elemBoundingRect.top >= config.top;
9083
},
9184

9285
// Element is to the left of the right edge of the viewport
93-
right: function _isWithin_right() {
94-
var container = (config.container === document.body) ? window : config.container;
95-
96-
return elemOffset[0] + elem.offsetWidth <= container.innerWidth + scrollOffset[0] - config.right;
97-
},
98-
99-
// Element is above the bottom edge of the viewport
100-
bottom: function _isWithin_bottom() {
101-
var container = (config.container === document.body) ? window : config.container;
102-
103-
return elemOffset[1] + elem.offsetHeight <= scrollOffset[1] + container.innerHeight - config.bottom;
104-
},
105-
106-
// Element is to the right of the left edge of the viewport
107-
left: function _isWithin_left() {
108-
return elemOffset[0] >= scrollOffset[0] + config.left;
109-
},
110-
111-
all: function _isWithin_all() {
112-
return (isWithin.top() && isWithin.right() && isWithin.bottom() && isWithin.left());
113-
}
114-
};
115-
116-
// Current offset values
117-
scrollOffset = (function _scrollOffset() {
118-
var x = config.container.scrollLeft;
119-
var y = config.container.scrollTop;
86+
right: function _isWithin_right () {
87+
var containerWidth;
12088

121-
if (y === 0) {
122-
if (config.container.pageYOffset) {
123-
y = config.container.pageYOffset;
124-
}
125-
else if (window.pageYOffset) {
126-
y = window.pageYOffset;
89+
if (canUseWindowDimensions || config.container !== window) {
90+
containerWidth = config.container.innerWidth;
12791
}
12892
else {
129-
if (config.container === document.body) {
130-
if (useHtmlElem) {
131-
y = (config.container.parentElement) ? config.container.parentElement.scrollTop : 0;
132-
}
133-
else {
134-
y = (config.container.parentElement) ? config.container.parentElement.scrollTop : 0;
135-
}
136-
}
137-
else {
138-
y = (config.container.parentElement) ? config.container.parentElement.scrollTop : 0;
139-
}
93+
containerWidth = document.documentElement.clientWidth;
14094
}
141-
}
14295

143-
if (x === 0) {
144-
if (config.container.pageXOffset) {
145-
x = config.container.pageXOffset;
146-
}
147-
else if (window.pageXOffset) {
148-
x = window.pageXOffset;
96+
// Note that `elemBoundingRect.right` is the distance from the *left* of the viewport to the element's far right edge
97+
return elemBoundingRect.right <= containerWidth - config.right;
98+
},
99+
100+
// Element is above the bottom edge of the viewport
101+
bottom: function _isWithin_bottom () {
102+
var containerHeight;
103+
104+
if (canUseWindowDimensions || config.container !== window) {
105+
containerHeight = config.container.innerHeight;
149106
}
150107
else {
151-
if (config.container === document.body) {
152-
x = (config.container.parentElement) ? config.container.parentElement.scrollLeft : 0;
153-
}
154-
else {
155-
x = (config.container.parentElement) ? config.container.parentElement.scrollLeft : 0;
156-
}
108+
containerHeight = document.documentElement.clientHeight;
157109
}
158-
}
159110

160-
return [x, y];
161-
}());
162-
163-
elemOffset = (function _elemOffset() {
164-
var el = elem;
165-
var x = 0;
166-
var y = 0;
167-
168-
if (el.parentNode) {
169-
x = el.offsetLeft;
170-
y = el.offsetTop;
171-
172-
el = el.parentNode;
173-
while (el) {
174-
if (el === config.container) {
175-
break;
176-
}
111+
// Note that `elemBoundingRect.bottom` is the distance from the *top* of the viewport to the element's bottom edge
112+
return elemBoundingRect.bottom <= containerHeight - config.bottom;
113+
},
177114

178-
x += el.offsetLeft;
179-
y += el.offsetTop;
115+
// Element is to the right of the left edge of the viewport
116+
left: function _isWithin_left () {
117+
return elemBoundingRect.left >= config.left;
118+
},
180119

181-
el = el.parentNode;
182-
}
120+
// Element is within all four boundaries
121+
all: function _isWithin_all () {
122+
// 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
123+
// Top: Quickest to calculate + most likely to be false
124+
// Bottom: Note quite as quick to calculate, but also very likely to be false
125+
// Left and right are both equally unlikely to be false since most sites only scroll vertically, but left is faster
126+
return (isWithin.top() && isWithin.bottom() && isWithin.left() && isWithin.right());
183127
}
128+
};
184129

185-
return [x, y];
186-
})();
130+
// Get the element's bounding rectangle with respect to the viewport
131+
elemBoundingRect = elem.getBoundingClientRect();
187132

188133
// Test the element against each side of the viewport that was requested
189-
arr = config.sides.split(' ');
190-
i = arr.length;
134+
sideNamesPattern = /^top$|^right$|^bottom$|^left$|^all$/;
135+
// Loop through all of the sides
136+
sides = config.sides.split(' ');
137+
i = sides.length;
191138
while (i--) {
192-
side = arr[i].toLowerCase();
139+
side = sides[i].toLowerCase();
193140

194-
if (/top|right|bottom|left|all/.test(side)) {
141+
if (sideNamesPattern.test(side)) {
195142
if (isWithin[side]()) {
196143
result = true;
197144
}
@@ -227,19 +174,19 @@
227174

228175
// Shortcut methods for each side of the viewport
229176
// Example: `withinviewport.top(elem)` is the same as `withinviewport(elem, 'top')`
230-
withinviewport.prototype.top = function _withinviewport_top(element) {
177+
withinviewport.prototype.top = function _withinviewport_top (element) {
231178
return withinviewport(element, 'top');
232179
};
233180

234-
withinviewport.prototype.right = function _withinviewport_right(element) {
181+
withinviewport.prototype.right = function _withinviewport_right (element) {
235182
return withinviewport(element, 'right');
236183
};
237184

238-
withinviewport.prototype.bottom = function _withinviewport_bottom(element) {
185+
withinviewport.prototype.bottom = function _withinviewport_bottom (element) {
239186
return withinviewport(element, 'bottom');
240187
};
241188

242-
withinviewport.prototype.left = function _withinviewport_left(element) {
189+
withinviewport.prototype.left = function _withinviewport_left (element) {
243190
return withinviewport(element, 'left');
244191
};
245192

0 commit comments

Comments
 (0)