Repel Objects when mouse is near
javascript, jquery
Solution
A simple approach would be to wrap each span in another, larger span. Make it larger on each side by the minimal distance you want the mouse to be able to approach the inner spans. Bind a function (`evade`) that moves each wrapper to `mouseover` on the wrappers. This approach gives you a square border, so if the graphical elements in the inner spans aren't square, the distance from the mouse to the graphical element's border won't be constant, but is easy to implement.
Alternatively, use the bumper for a rough proximity test. Instead of binding the evade function to `mouseover`, bind a function (`beginEvade`) that binds `evade` on mousemove. Also, bind a function to `mouseout` that unbinds `evade`. Your `evade` can then perform a more precise proximity test.
First, find a good geometry library that provides a vector type. In absence of one, here's a sample implementation:
Math.Vector = function (x,y) {
this.x = x;
this.y = y;
}
Math.Vector.prototype = {
clone: function () {
return new Math.Vector(this.x, this.y);
},
negate: function () {
this.x = -this.x;
this.y = -this.y;
return this;
},
neg: function () {
return this.clone().negate();
},
addeq: function (v) {
this.x += v.x;
this.y += v.y;
return this;
},
subeq: function (v) {
return this.addeq(v.neg());
},
add: function (v) {
return this.clone().addeq(v);
},
sub: function (v) {
return this.clone().subeq(v);
},
multeq: function (c) {
this.x *= c;
this.y *= c;
return this;
},
diveq: function (c) {
this.x /= c;
this.y /= c;
return this;
},
mult: function (c) {
return this.clone().multeq(c);
},
div: function (c) {
return this.clone().diveq(c);
},
dot: function (v) {
return this.x * v.x + this.y * v.y;
},
length: function () {
return Math.sqrt(this.dot(this));
},
normal: function () {
return this.clone().diveq(this.length());
}
};
Next, a sample circular evasion function (which is the simplest to implement). Outline:
- calculate the bumper's center (the bumper's corner plus the outer dimensions divided in half)
- calculate the mouse offset vector (from the mouse cursor to the element's center)
- proximity test: if the distance is >= the minimum allowed distance, then return early.
- calculate delta: The distance to the mouse cursor is too small, so we need the vector from where the bumper is to where it should be (the delta). Lengthening the offset vector so it's the minimum allowed distance gives where the bumper's center should be, relative to the mouse's position. Subtracting the offset vector from that gives the delta from the proximity edge to the mouse, which also happens to be the delta.
- calculate new position:
- add the delta to the current position.
- bounds checking: keep all borders of the circle within the document.
- move the bumper
In code:
function evade(evt) {
var $this = $(this),
corner = $this.offset(),
center = {x: corner.left + $this.outerWidth() / 2, y: corner.top + $this.outerHeight() / 2},
dist = new Math.Vector(center.x - evt.pageX, center.y - evt.pageY),
closest = $this.outerWidth() / 2;
// proximity test
if (dist.length() >= closest) {
return;
}
// calculate new position
var delta = dist.normal().multeq(closest).sub(dist),
newCorner = {left: corner.left + delta.x, top: corner.top + delta.y};
// bounds check
var padding = parseInt($this.css('padding-left'));
if (newCorner.left < -padding) {
newCorner.left = -padding;
} else if (newCorner.left + $this.outerWidth() - padding > $(document).width()) {
newCorner.left = $(document).width() - $this.outerWidth() + padding;
}
if (newCorner.top < -padding) {
newCorner.top = -padding;
} else if (newCorner.top + $this.outerHeight() - padding > $(document).height()) {
newCorner.top = $(document).height() - $this.outerHeight() + padding;
}
// move bumper
$this.offset(newCorner);
}
After that, all that's left are functions to bind/unbind `evade`, and the calls to set everything up.
function beginEvade() {
$(this).bind('mousemove', evade);
}
function endEvade() {
$(this).unbind('mousemove', evade);
}
$(function () {
// you can also wrap the elements when creating them.
$('.circle').wrap('<span class="bumper" />')
$('.bumper').bind('mouseover', beginEvade);
$('.bumper').bind('mouseout', endEvade);
});
You can preview this in jsFiddle
Problem
I have a bunch of span elements in random positions enclosed inside a parent div called '.background'. These are generated with Javascript. Like this: ``` <span class="circle" style="width: 54px; height: 54px; background: #5061cf; top: 206px; left: 306px"></span> ``` I want them to move away (or repel) as the mouse draws near, but I have no idea how to do this! How would I go about accomplishing this in jQuery? I imagine you'd have to search for spans that were nearby, and then change their position if they were inside a certain radius surrounding the mouse, but I really don't know where to start. Any help is appreciated!