blinker / firefox.plugin / data / lib / jquery.nearest.js @ master
History | View | Annotate | Download (8.261 KB)
1 |
/*!
|
---|---|
2 |
* jQuery Nearest plugin v1.3.0
|
3 |
*
|
4 |
* Finds elements closest to a single point based on screen location and pixel dimensions
|
5 |
* http://gilmoreorless.github.io/jquery-nearest/
|
6 |
* Open source under the MIT licence: http://gilmoreorless.mit-license.org/2011/
|
7 |
*
|
8 |
* Requires jQuery 1.4 or above
|
9 |
* Also supports Ben Alman's "each2" plugin for faster looping (if available)
|
10 |
*/
|
11 |
|
12 |
/**
|
13 |
* Method signatures:
|
14 |
*
|
15 |
* $.nearest({x, y}, selector) - find $(selector) closest to point
|
16 |
* $(elem).nearest(selector) - find $(selector) closest to elem
|
17 |
* $(elemSet).nearest({x, y}) - filter $(elemSet) and return closest to point
|
18 |
*
|
19 |
* Also:
|
20 |
* $.furthest()
|
21 |
* $(elem).furthest()
|
22 |
*
|
23 |
* $.touching()
|
24 |
* $(elem).touching()
|
25 |
*/
|
26 |
;(function ($, undefined) { |
27 |
|
28 |
/**
|
29 |
* Internal method that does the grunt work
|
30 |
*
|
31 |
* @param mixed selector Any valid jQuery selector providing elements to filter
|
32 |
* @param hash options Key/value list of options for matching elements
|
33 |
* @param mixed thisObj (optional) Any valid jQuery selector that represents self
|
34 |
* for the "includeSelf" option
|
35 |
* @return array List of matching elements, can be zero length
|
36 |
*/
|
37 |
var rPerc = /^([\d.]+)%$/; |
38 |
function nearest(selector, options, thisObj) { |
39 |
// Normalise selector and dimensions
|
40 |
selector || (selector = 'div'); // I STRONGLY recommend passing in a selector |
41 |
var $container = $(options.container), |
42 |
containerOffset = $container.offset() || {left: 0, top: 0}, |
43 |
containerWH = [ |
44 |
$container.width() || 0, |
45 |
$container.height() || 0 |
46 |
], |
47 |
containerProps = { |
48 |
// prop: [min, max]
|
49 |
x: [containerOffset.left, containerOffset.left + containerWH[0]], |
50 |
y: [containerOffset.top, containerOffset.top + containerWH[1]], |
51 |
w: [0, containerWH[0]], |
52 |
h: [0, containerWH[1]] |
53 |
}, |
54 |
prop, dims, match; |
55 |
for (prop in containerProps) if (containerProps.hasOwnProperty(prop)) { |
56 |
match = rPerc.exec(options[prop]); |
57 |
if (match) {
|
58 |
dims = containerProps[prop]; |
59 |
options[prop] = (dims[1] - dims[0]) * match[1] / 100 + dims[0]; |
60 |
} |
61 |
} |
62 |
|
63 |
// Deprecated options - remove in 2.0
|
64 |
if (options.sameX === false && options.checkHoriz === false) { |
65 |
options.sameX = !options.checkHoriz; |
66 |
} |
67 |
if (options.sameY === false && options.checkVert === false) { |
68 |
options.sameY = !options.checkVert; |
69 |
} |
70 |
|
71 |
// Get elements and work out x/y points
|
72 |
var $all = $container.find(selector), |
73 |
cache = [], |
74 |
furthest = !!options.furthest, |
75 |
checkX = !options.sameX, |
76 |
checkY = !options.sameY, |
77 |
onlyX = !!options.onlyX, |
78 |
onlyY = !!options.onlyY, |
79 |
compDist = furthest ? 0 : Infinity, |
80 |
point1x = parseFloat(options.x) || 0,
|
81 |
point1y = parseFloat(options.y) || 0,
|
82 |
point2x = parseFloat(point1x + options.w) || point1x, |
83 |
point2y = parseFloat(point1y + options.h) || point1y, |
84 |
tolerance = parseFloat(options.tolerance) || 0,
|
85 |
hasEach2 = !!$.fn.each2,
|
86 |
// Shortcuts to help with compression
|
87 |
min = Math.min, |
88 |
max = Math.max; |
89 |
|
90 |
// Normalise the remaining options
|
91 |
if (!options.includeSelf && thisObj) {
|
92 |
$all = $all.not(thisObj); |
93 |
} |
94 |
if (tolerance < 0) { |
95 |
tolerance = 0;
|
96 |
} |
97 |
// Loop through all elements and check their positions
|
98 |
$all[hasEach2 ? 'each2' : 'each'](function (i, elem) { |
99 |
var $this = hasEach2 ? elem : $(this), |
100 |
off = $this.offset(),
|
101 |
x = off.left, |
102 |
y = off.top, |
103 |
w = $this.outerWidth(),
|
104 |
h = $this.outerHeight(),
|
105 |
x2 = x + w, |
106 |
y2 = y + h, |
107 |
maxX1 = max(x, point1x), |
108 |
minX2 = min(x2, point2x), |
109 |
maxY1 = max(y, point1y), |
110 |
minY2 = min(y2, point2y), |
111 |
intersectX = minX2 >= maxX1, |
112 |
intersectY = minY2 >= maxY1, |
113 |
distX, distY, distT, isValid; |
114 |
if (
|
115 |
// .nearest() / .furthest()
|
116 |
(checkX && checkY) || |
117 |
// .touching()
|
118 |
(!checkX && !checkY && intersectX && intersectY) || |
119 |
// .nearest({sameY: true})
|
120 |
(checkX && intersectY) || |
121 |
// .nearest({sameX: true})
|
122 |
(checkY && intersectX) || |
123 |
// .nearest({onlyX: true})
|
124 |
(checkX && onlyX) || |
125 |
// .nearest({onlyY: true})
|
126 |
(checkY && onlyY) |
127 |
) { |
128 |
distX = intersectX ? 0 : maxX1 - minX2;
|
129 |
distY = intersectY ? 0 : maxY1 - minY2;
|
130 |
if (onlyX || onlyY) {
|
131 |
distT = onlyX ? distX : distY; |
132 |
} else {
|
133 |
distT = intersectX || intersectY ? |
134 |
max(distX, distY) : |
135 |
Math.sqrt(distX * distX + distY * distY); |
136 |
} |
137 |
isValid = furthest ? |
138 |
distT >= compDist - tolerance : |
139 |
distT <= compDist + tolerance; |
140 |
if (isValid) {
|
141 |
compDist = furthest ? |
142 |
max(compDist, distT) : |
143 |
min(compDist, distT); |
144 |
cache.push({ |
145 |
node: this, |
146 |
dist: distT
|
147 |
}); |
148 |
} |
149 |
} |
150 |
}); |
151 |
// Make sure all cached items are within tolerance range
|
152 |
var len = cache.length,
|
153 |
filtered = [], |
154 |
compMin, compMax, |
155 |
i, item; |
156 |
if (len) {
|
157 |
if (furthest) {
|
158 |
compMin = compDist - tolerance; |
159 |
compMax = compDist; |
160 |
} else {
|
161 |
compMin = compDist; |
162 |
compMax = compDist + tolerance; |
163 |
} |
164 |
for (i = 0; i < len; i++) { |
165 |
item = cache[i]; |
166 |
if (item.dist >= compMin && item.dist <= compMax) {
|
167 |
filtered.push(item.node); |
168 |
} |
169 |
} |
170 |
} |
171 |
return filtered;
|
172 |
} |
173 |
|
174 |
$.each(['nearest', 'furthest', 'touching'], function (i, name) { |
175 |
|
176 |
// Internal default options
|
177 |
// Not exposed publicly because they're method-dependent and easily overwritten anyway
|
178 |
var defaults = {
|
179 |
x: 0, // X position of top left corner of point/region |
180 |
y: 0, // Y position of top left corner of point/region |
181 |
w: 0, // Width of region |
182 |
h: 0, // Height of region |
183 |
tolerance: 1, // Distance tolerance in pixels, mainly to handle fractional pixel rounding bugs |
184 |
container: document, // Container of objects for calculating %-based dimensions |
185 |
furthest: name == 'furthest', // Find max distance (true) or min distance (false) |
186 |
includeSelf: false, // Include 'this' in search results (t/f) - only applies to $(elem).func(selector) syntax |
187 |
sameX: name === 'touching', // Only match for the same X axis values (t/f) |
188 |
sameY: name === 'touching', // Only match for the same Y axis values (t/f) |
189 |
onlyX: false, // Only check X axis variations (t/f) |
190 |
onlyY: false // Only check Y axis variations (t/f) |
191 |
}; |
192 |
|
193 |
/**
|
194 |
* $.nearest() / $.furthest() / $.touching()
|
195 |
*
|
196 |
* Utility functions for finding elements near a specific point or region on screen
|
197 |
*
|
198 |
* @param hash point Co-ordinates for the point or region to measure from
|
199 |
* "x" and "y" keys are required, "w" and "h" keys are optional
|
200 |
* @param mixed selector Any valid jQuery selector that provides elements to filter
|
201 |
* @param hash options (optional) Extra filtering options
|
202 |
* Not technically needed as the options could go on the point object,
|
203 |
* but it's good to have a consistent API
|
204 |
* @return jQuery object containing matching elements in selector
|
205 |
*/
|
206 |
$[name] = function (point, selector, options) { |
207 |
if (!point || point.x === undefined || point.y === undefined) { |
208 |
return $([]); |
209 |
} |
210 |
var opts = $.extend({}, defaults, point, options || {}); |
211 |
return $(nearest(selector, opts)); |
212 |
}; |
213 |
|
214 |
/**
|
215 |
* SIGNATURE 1:
|
216 |
* $(elem).nearest(selector) / $(elem).furthest(selector) / $(elem).touching(selector)
|
217 |
*
|
218 |
* Finds all elements in selector that are nearest to/furthest from elem
|
219 |
*
|
220 |
* @param mixed selector Any valid jQuery selector that provides elements to filter
|
221 |
* @param hash options (optional) Extra filtering options
|
222 |
* @return jQuery object containing matching elements in selector
|
223 |
*
|
224 |
* SIGNATURE 2:
|
225 |
* $(elemSet).nearest(point) / $(elemSet).furthest(point) / $(elemSet).touching(point)
|
226 |
*
|
227 |
* Filters elemSet to return only the elements nearest to/furthest from point
|
228 |
* Effectively a wrapper for $.nearest(point, elemSet) but with the benefits of method chaining
|
229 |
*
|
230 |
* @param hash point Co-ordinates for the point or region to measure from
|
231 |
* @return jQuery object containing matching elements in elemSet
|
232 |
*/
|
233 |
$.fn[name] = function (selector, options) { |
234 |
if (!this.length) { |
235 |
return this.pushStack([]); |
236 |
} |
237 |
var opts;
|
238 |
if (selector && $.isPlainObject(selector)) { |
239 |
opts = $.extend({}, defaults, selector, options || {});
|
240 |
return this.pushStack(nearest(this, opts)); |
241 |
} |
242 |
var offset = this.offset(), |
243 |
dimensions = { |
244 |
x: offset.left,
|
245 |
y: offset.top,
|
246 |
w: this.outerWidth(), |
247 |
h: this.outerHeight() |
248 |
}; |
249 |
opts = $.extend({}, defaults, dimensions, options || {});
|
250 |
return this.pushStack(nearest(selector, opts, this)); |
251 |
}; |
252 |
}); |
253 |
})(jQuery); |