├── .gitignore
├── Gruntfile.js
├── README.md
├── examples
├── dropdown.html
├── header.html
├── index.html
├── menu.html
└── style.css
├── img
└── demo.gif
├── index.html
├── jquery.aim.js
├── jquery.aim.min.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 |
3 | // Project configuration.
4 | grunt.initConfig({
5 | pkg: grunt.file.readJSON('package.json'),
6 | uglify: {
7 | options: {
8 | banner: '<%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> \n'
9 | },
10 | build: {
11 | src: 'jquery.aim.js',
12 | dest: 'jquery.aim.min.js'
13 | }
14 | }
15 | });
16 |
17 | // Load the plugin that provides the "uglify" task.
18 | grunt.loadNpmTasks('grunt-contrib-uglify');
19 |
20 | // Default task(s).
21 | grunt.registerTask('default', ['uglify']);
22 |
23 | };
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | jquery.aim
2 | ==========
3 |
4 | jQuery plugin anticipates on which element user is going to hover/click.
5 |
6 | 
7 |
8 | ## Examples
9 | A couple of examples can be found the [examples page](http://cihadturhan.github.io/jquery-aim/examples/index.html)
10 |
11 | ## Usage
12 | Call the function on the elements to catch user aim and add a class which will be added or removed when aiming starts or ends
13 | ```javascript
14 | $('#target').aim({
15 | className: 'open'
16 | });
17 | ```
18 |
19 | If you want to execute a function on aim starts or ends, use the `aimEnter` and `aimExit` options
20 | ```javascript
21 | $('#hamburger').aim({
22 |
23 | aimEnter: function() {
24 | $('#menu').show();
25 | },
26 |
27 | aimExit: function(){
28 | $('#menu').hide();
29 | }
30 | });
31 |
32 | ```
33 |
34 |
35 | ## Debugging
36 | To see where your cursor is aiming and check if it intersects with elements use
37 | ```javascript
38 | $.aim.setDebug(true);
39 | ```
40 | and you will see a rectangle moving around.
41 |
42 | ## Defining own function
43 | If you don't like the default algorithm, define your own by the following procedure
44 |
45 | ```javascript
46 |
47 |
48 | function anticipateFunc(p, v, mouseX, mouseY, anticipator) {
49 | /*
50 | Calculate the new position of anticipator using inputs
51 | p = {x:number,y:number}
52 | v = {x:number,y:number}
53 | mouseX = number
54 | mouseY = number
55 |
56 | Anticipator has some readonly values like the following
57 |
58 | {
59 | size: 50,
60 | center: {x: 0, y: 0},
61 | effectiveSize: 1,
62 | rect : {x0: 0, y0: 0, x1: 50, y1: 50}
63 | }
64 |
65 | */
66 | }
67 |
68 | $.aim.setAnticipateFunction(anticipateFunc);
69 |
70 | ```
71 |
--------------------------------------------------------------------------------
/examples/dropdown.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ')
158 | .attr({
159 | id: 'jquery-aim-debug'
160 | })
161 | .css({
162 | width: 2 * s + 'px',
163 | height: 2 * s + 'px',
164 | 'margin-left': -s + 'px',
165 | 'margin-top': -s + 'px',
166 | top: 0,
167 | left: 0,
168 | border: '2px solid #333',
169 | opacity: 0.3,
170 | 'background-color': 'yellowgreen',
171 | position: 'absolute',
172 | 'pointer-events':'none'
173 | })
174 | .appendTo($('body'));
175 | return elem;
176 | }
177 |
178 | /*
179 | * Tests rectangle - rectangle intersection and gives the ratio of intersection. Max 1, min 0.
180 | *
181 | * @param {type} rect The first rectange
182 | * @param {type} rect2 The second rectange
183 | * @returns {Number} Ratio of intersection area to area of tailblazer
184 | */
185 |
186 | function intersectRatio(rect, rect2) {
187 |
188 | var x_overlap = Math.max(0, Math.min(rect.x1, rect2.x1) - Math.max(rect.x0, rect2.x0));
189 | var y_overlap = Math.max(0, Math.min(rect.y1, rect2.y1) - Math.max(rect.y0, rect2.y0));
190 |
191 | return x_overlap * y_overlap / (anticipator.effectiveSize * anticipator.effectiveSize);
192 | }
193 |
194 | function init(opts) {
195 | var $this = $(this);
196 | if ($.inArray($this, elementList) > -1)
197 | return;
198 |
199 | elementList.push($this);
200 | addProperties($this);
201 | $this.data('aim-data').options = opts;
202 | }
203 |
204 | $().ready(function() {
205 | document.addEventListener('mousemove', function(e) {
206 | mouseX = e.clientX,
207 | mouseY = e.clientY;
208 | }, false);
209 | });
210 |
211 |
212 | var timer = setInterval(function() {
213 | var a = anticipator;
214 |
215 | if (!elementList.length)
216 | return;
217 |
218 | anticipateFunc(p, v, mouseX, mouseY, a);
219 |
220 |
221 | var prop = 'translate(' + a.center.x + 'px,' + a.center.y + 'px) scale(' + a.effectiveSize / a.size + ')';
222 |
223 | DEBUG && a.elem.css({
224 | '-webkit-transform': prop,
225 | '-moz-transform': prop,
226 | '-ms-transform': prop,
227 | 'transform': prop
228 | /*width: tbRad * 2,
229 | height: tbRad * 2,
230 | marginLeft: -tbRad + 'px',
231 | marginTop: -tbRad + 'px'*/
232 | });
233 |
234 | /*
235 | * Iterate over each elements and calculate increment for all
236 | * In each cycle, it increases by a value between 0 - 0.2 (reaches max if it fully intersects) and decreases by 0.05
237 | * Increment can be between 0 and 2
238 | * If it's greater than 1, aimEnter function will be called
239 | * if it's less than or equal to 0, aimExit function will be called
240 | */
241 | for (var i = 0; i < elementList.length; i++) {
242 |
243 | var target = elementList[i];
244 |
245 | var data = target.data('aim-data');
246 |
247 | var isctRat = intersectRatio(data.rect, a.rect);
248 |
249 | //check if they intersects and mouse is not on the element
250 | if (isctRat && vd !== 0) {
251 |
252 | data.increment = data.increment + isctRat * 0.2;
253 | if (data.increment > 1 && data.increment < 2) {
254 | if (data.options.className)
255 | target.addClass(data.options.className);
256 | else if (data.options.aimEnter && typeof data.options.aimEnter === 'function')
257 | data.options.aimEnter.call(target, true);
258 |
259 | if (data.increment > 2) {
260 | data.increment = 2;
261 | }
262 | DEBUG && a.elem.css('background-color', 'tomato');
263 | } else if (data.increment > 2) {
264 | data.increment = 2;
265 | DEBUG && a.elem.css('background-color', 'tomato');
266 | }
267 | break;
268 | } else {
269 | DEBUG && a.elem.css('background-color', 'yellowgreen');
270 | }
271 |
272 | if (data.increment !== 0) {
273 | data.increment = data.increment - 0.05;
274 | if (data.increment < 0) {
275 | data.increment = 0;
276 | if (data.options.className)
277 | target.removeClass(data.options.className);
278 | else if (data.options.aimExit && typeof data.options.aimExit === 'function')
279 | data.options.aimExit.call(target, true);
280 | }
281 | }
282 | }
283 |
284 | }, 16); //~60 FPS
285 |
286 | })(jQuery);
--------------------------------------------------------------------------------
/jquery.aim.min.js:
--------------------------------------------------------------------------------
1 | jquery-aim 2014-08-13
2 | !function(a){function b(b,c,d,e,f){var g=f;b.x&&b.y&&(c.x=.7*c.x+.3*(d-b.x),c.y=.7*c.y+.3*(e-b.y)),b.x=d,b.y=e,i=Math.sqrt(c.x*c.x+c.y*c.y),.1>i&&(c.x=0,c.y=0),g.effectiveSize=Math.sqrt(g.size*i+1),g.center.x=.7*g.center.x+.3*(b.x+c.x*k),g.center.x<0&&(g.center.x=0),g.center.x>a(window).width()-g.effectiveSize&&(g.center.x=a(window).width()-g.effectiveSize),g.rect.x0=g.center.x-g.effectiveSize,g.rect.x1=g.center.x+g.effectiveSize,g.center.y=.7*g.center.y+.3*(b.y+c.y*k),g.center.y<0&&(g.center.y=0),g.center.y>a(window).height()-g.effectiveSize&&(g.center.y=a(window).height()-g.effectiveSize),g.rect.y0=g.center.y-g.effectiveSize,g.rect.y1=g.center.y+g.effectiveSize}function c(a){{var b=a.outerWidth(),c=a.outerHeight(),d=a.offset().left,e=a.offset().top;Math.sqrt(b*b+c*c)}a.data("aim-data",{rect:{x0:d,y0:e,x1:d+b,y1:e+c},center:{x:d,y:e},increment:0})}function d(){var b=o.size,c=a("
").attr({id:"jquery-aim-debug"}).css({width:2*b+"px",height:2*b+"px","margin-left":-b+"px","margin-top":-b+"px",top:0,left:0,border:"2px solid #333",opacity:.3,"background-color":"yellowgreen",position:"absolute","pointer-events":"none"}).appendTo(a("body"));return c}function e(a,b){var c=Math.max(0,Math.min(a.x1,b.x1)-Math.max(a.x0,b.x0)),d=Math.max(0,Math.min(a.y1,b.y1)-Math.max(a.y0,b.y0));return c*d/(o.effectiveSize*o.effectiveSize)}function f(b){var d=a(this);a.inArray(d,g)>-1||(g.push(d),c(d),d.data("aim-data").options=b)}var g=[],h={x:0,y:0},i=0,j={x:0,y:0},k=12,l=0,m=0,n=!1,o={size:50,center:{x:0,y:0},effectiveSize:1};o.rect={x0:0,y0:0,x1:o.size,y1:o.size},a.fn.aim=function(a){return this.each(function(){f.call(this,a)}),this},a.aim={},a.aim.setDebug=function(b){if(b){if(a("#jquery-aim-debug").length)return;o.elem=d()}else a("#jquery-aim-debug").remove(),o.elem=null;n=b},a.aim.setAnticipateFunction=function(a){"function"==typeof a&&(b=a)},a().ready(function(){document.addEventListener("mousemove",function(a){l=a.clientX,m=a.clientY},!1)});setInterval(function(){var a=o;if(g.length){b(j,h,l,m,a);var c="translate("+a.center.x+"px,"+a.center.y+"px) scale("+a.effectiveSize/a.size+")";n&&a.elem.css({"-webkit-transform":c,"-moz-transform":c,"-ms-transform":c,transform:c});for(var d=0;d1&&k.increment<2?(k.options.className?f.addClass(k.options.className):k.options.aimEnter&&"function"==typeof k.options.aimEnter&&k.options.aimEnter.call(f,!0),k.increment>2&&(k.increment=2),n&&a.elem.css("background-color","tomato")):k.increment>2&&(k.increment=2,n&&a.elem.css("background-color","tomato"));break}n&&a.elem.css("background-color","yellowgreen"),0!==k.increment&&(k.increment=k.increment-.05,k.increment<0&&(k.increment=0,k.options.className?f.removeClass(k.options.className):k.options.aimExit&&"function"==typeof k.options.aimExit&&k.options.aimExit.call(f,!0)))}}},16)}(jQuery);
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jquery-aim",
3 | "version": "0.0.0",
4 | "description": "A jQuery plugin to make webistes more usable by guessing which element is going to be hovered/clicked",
5 | "main": "jquery.aim.js",
6 | "directories": {
7 | "example": "examples"
8 | },
9 | "dependencies": {
10 | "grunt": "~0.4.5"
11 | },
12 | "devDependencies": {
13 | "grunt": "~0.4.5",
14 | "grunt-contrib-uglify": "~0.5.0"
15 | },
16 | "scripts": {
17 | "test": "echo \"Error: no test specified\" && exit 1"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git://github.com/cihadturhan/jquery-aim.git"
22 | },
23 | "keywords": [
24 | "jquery-aim"
25 | ],
26 | "author": "Cihad Turhan",
27 | "license": "BSD",
28 | "bugs": {
29 | "url": "https://github.com/cihadturhan/jquery-aim/issues"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------