blinker / firefox.plugin / data / jquery.nearest.js @ a03cd52e
History | View | Annotate | Download (8.261 KB)
1 | a03cd52e | Thies Pfeiffer | /*!
|
---|---|---|---|
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); |