Bounce objects when the mouse is near
I have a group of span elements at random positions, enclosed inside a parent div called ".background". They are created using 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 push off) when the mouse gets closer, but I have no idea how to do that! How can I do this in jQuery?
I suppose you will have to look for gaps that were nearby and then reposition if they are within a certain radius around the mouse, but I really don't know where to start. Any help is appreciated!
source to share
A simple approach would be to wrap each span in a different, larger range. Make it larger on each side by the minimum distance you want the mouse to be able to get closer to the inner gaps. Bind a function ( evade
) that moves each wrapper on mouseover
to the wrappers. This approach gives a square border, so if the graphics in the inner spans are not square, the distance from the mouse to the border of the graphics will not be constant, but it is easy to implement.
Alternatively, use a bumper for a rough proximity check. Instead of binding the evade function to, bind mouseover
the ( beginEvade
) function that binds evade
to mousemove. Also, bind a function to mouseout
that unbinds evade
. Yours evade
can then perform a more accurate proximity test.
First, find a good geometry library that provides vector type. In the absence of one, here's an example 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 of the circular evasion function (which is the easiest to implement). Structure:
- calculate bumper center (bumper angle and outer dimensions are halved)
- calculate the mouse displacement vector (from the mouse cursor to the center of the element)
- Proximity test: If distance> = minimum distance allowed, then return earlier.
- calculate delta: the distance to the mouse cursor is too short, so we need a vector from which the bumper should be where it should be (delta). Extends the displacement vector so that the minimum distance allowed is where the center of the bumper is located relative to the mouse position. Subtracting the offset vector from it gives the delta from the edge of the proximity to the mouse, which is also the delta.
- calculate new position:
- add a delta to the current position.
- border check: preserve all circle borders in the document.
- move 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 the rest are bind / unbind functions evade
, and the calls to set all.
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 view this at jsFiddle
source to share