├── .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 | ![test](img/demo.gif "lorem") 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 | jQuery Aim Dropdown Example 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 |
16 | 17 |
18 |
19 |
20 |
21 | 24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 |
32 | 36 | 43 |
44 |
45 |
46 |
47 | 51 | 57 |
58 | 59 |
60 |
61 |
62 |
63 |
64 | 65 | 66 | 67 | 68 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /examples/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jQuery Aim Header Example 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 |
Move mouse upwards to see header
15 |
16 |
17 |
18 | 21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TODO supply a title 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |

See it in action

20 | 21 | 26 | 27 |
28 |
29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/menu.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jQuery Aim Example Menu 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 21 |
22 |
23 | 32 |
33 | 34 |
35 |
36 |
37 |
38 |
39 | 42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | 50 | 51 | 52 | 53 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /examples/style.css: -------------------------------------------------------------------------------- 1 | .full-width{ 2 | width: 100% !important; 3 | } 4 | 5 | .buffer-top{ 6 | margin-top: 15px; 7 | } 8 | 9 | .navbar-toggle .icon-bar{ 10 | background-color: dodgerblue 11 | } 12 | 13 | .navbar-toggle{ 14 | display: block !important; 15 | } 16 | 17 | #menu{ 18 | display: none; 19 | } 20 | 21 | header{ 22 | position: fixed !important; 23 | top: 0; 24 | left:0; 25 | width: 100%; 26 | -webkit-transition: transform 0.3s; 27 | -moz-transition: transform 0.3s; 28 | -ms-transition: transform 0.3s; 29 | transition: transform 0.3s; 30 | -webkit-transform: translateY(-60px); 31 | -moz-transform: translateY(-60px); 32 | -ms-transform: translateY(-60px); 33 | transform: translateY(-60px); 34 | } 35 | 36 | header.shown{ 37 | -webkit-transform: translateY(0); 38 | -moz-transform: translateY(0); 39 | -ms-transform: translateY(0); 40 | transform: translateY(0); 41 | } -------------------------------------------------------------------------------- /img/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cihadturhan/jquery-aim/dff1f13049842cedfeafba20ee2dad54c118f6cd/img/demo.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | TODO supply a title 10 | 11 | 12 | 13 | 14 | 15 |
TODO write content
16 | 17 | 18 | -------------------------------------------------------------------------------- /jquery.aim.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | var elementList = []; 4 | /** 5 | * v Velocity of the mouse pointer 6 | * vd Magnitude of velocity 7 | * p Position of the mouse pointer 8 | * t Average delta time for a simple calculation of new position, x = x0 + v * t 9 | * mouseX the last retrived x coordinate of mouse cursor 10 | * mouseY the last retrived y coordinate of mouse cursor 11 | * anticipator a jquery object to debug where mouse is aiming 12 | * anticipator.size, anticipator.radius, anticipator.center, anticipator.rect anticipator related properties 13 | * anRad Radius (or size) of the anticipator, increases as mouse move faster 14 | */ 15 | 16 | var v = {x: 0, y: 0}, 17 | vd = 0, 18 | p = {x: 0, y: 0}, 19 | t = 12, 20 | mouseX = 0, 21 | mouseY = 0, 22 | DEBUG = false, 23 | anticipator = { 24 | size: 50, 25 | center: {x: 0, y: 0}, 26 | effectiveSize: 1 27 | }; 28 | anticipator.rect = {x0: 0, y0: 0, x1: anticipator.size, y1: anticipator.size}; 29 | 30 | /* 31 | * Default anticipate function 32 | * 33 | * @function anticipateFunc 34 | * 35 | * @param {type} p position of anticipator 36 | * @param {type} v velocity of anticipator 37 | * @param {type} mouseX mouse X coordinate 38 | * @param {type} mouseY mouse Y coordinate 39 | * @param {type} anticipator anticipator object 40 | * @returns {undefined} 41 | */ 42 | 43 | function anticipateFunc(p, v, mouseX, mouseY, anticipator) { 44 | var a = anticipator; 45 | 46 | //smoothen velocity values with ratio 0.7/0.3 47 | if (p.x && p.y) { 48 | v.x = v.x * 0.7 + (mouseX - p.x) * 0.3; 49 | v.y = v.y * 0.7 + (mouseY - p.y) * 0.3; 50 | } 51 | 52 | p.x = mouseX; 53 | p.y = mouseY; 54 | 55 | //find velocity magnitude 56 | vd = Math.sqrt(v.x * v.x + v.y * v.y); 57 | vd < 0.1 && (v.x = 0, v.y = 0); 58 | 59 | //change radius according to velocity magnitude 60 | a.effectiveSize = Math.sqrt(a.size * vd + 1); 61 | 62 | //assign anticipator coordinates according to new velocity values and smoothen it with ratio 0.7/0.3 63 | a.center.x = a.center.x * 0.7 + (p.x + v.x * t) * 0.3; 64 | a.center.x < 0 && (a.center.x = 0); 65 | (a.center.x > $(window).width() - a.effectiveSize) && (a.center.x = $(window).width() - a.effectiveSize); 66 | 67 | a.rect.x0 = a.center.x - a.effectiveSize; 68 | a.rect.x1 = a.center.x + a.effectiveSize; 69 | 70 | a.center.y = a.center.y * 0.7 + (p.y + v.y * t) * 0.3; 71 | a.center.y < 0 && (a.center.y = 0); 72 | (a.center.y > $(window).height() - a.effectiveSize) && (a.center.y = $(window).height() - a.effectiveSize); 73 | 74 | a.rect.y0 = a.center.y - a.effectiveSize; 75 | a.rect.y1 = a.center.y + a.effectiveSize; 76 | } 77 | 78 | 79 | $.fn.aim = function(opts) { 80 | // Initialize menu-aim for all elements in jQuery collection 81 | this.each(function() { 82 | init.call(this, opts); 83 | }); 84 | 85 | return this; 86 | }; 87 | 88 | /* 89 | * Sets debug mode to true or false. If debug mode is set to true, a circle showing the 90 | * position and radius of anticipator will be created 91 | * 92 | * @param {type} val 93 | * @returns {undefined} 94 | */ 95 | 96 | $.aim = {}; 97 | 98 | $.aim.setDebug = function(val) { 99 | if (val) { 100 | if ($('#jquery-aim-debug').length) { 101 | return; 102 | } 103 | anticipator.elem = createDebugObject(); 104 | 105 | } else { 106 | $('#jquery-aim-debug').remove(); 107 | anticipator.elem = null; 108 | } 109 | DEBUG = val; 110 | }; 111 | 112 | 113 | $.aim.setAnticipateFunction = function(func) { 114 | if (typeof func === 'function') { 115 | anticipateFunc = func; 116 | } 117 | }; 118 | 119 | /* 120 | * Adds properties with jquery `.data()` function so each time it doesn't recalculate every property 121 | * 122 | * @param {type} elem Jquery element to add properties 123 | * @returns {undefined} none 124 | */ 125 | 126 | function addProperties($elem) { 127 | var percent = 0.25; 128 | var w = $elem.outerWidth(); 129 | var h = $elem.outerHeight(); 130 | var x = $elem.offset().left; 131 | var y = $elem.offset().top; 132 | 133 | var max = Math.sqrt(w * w + h * h); 134 | var r = max / 2 * (1 + percent); 135 | 136 | $elem.data('aim-data', { 137 | rect: { 138 | x0: x, 139 | y0: y, 140 | x1: x + w, 141 | y1: y + h 142 | }, 143 | center: {x: x, y: y}, 144 | increment: 0 145 | } 146 | ); 147 | } 148 | 149 | /* 150 | * Creates a circle jquery object which is to be used to 151 | * show where the anticipator is at any time 152 | * 153 | * @returns {Object} 154 | */ 155 | function createDebugObject() { 156 | var s = anticipator.size; 157 | var elem = $('
') 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 | --------------------------------------------------------------------------------