Statistics
| Branch: | Revision:

blinker / firefox.plugin / data / lib / jquery.nearest.js @ master

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);