Repulsive force in Javascript with Mootools draggables
A couple of days ago I was reading about the Boids demo made by Ben from Coderholic. I though that was very interesting, specially since I hadn’t heard about boids before.
I wanted to create my own version but instead I decided to play only with the repulsive force, making it interactive using Mootools drag & drop functionality. When the demo loads, 20 elements will be created and they will start moving on their own, trying to keep the minimum distance with any other element. This distance is configurable, as it is the option to keep the element speed or to make them slow down until they stop. At any time, the user can move elements on the screen dragging them with the mouse.
See the demo in action.
Source code
-
var Item = new Class({
-
Implements: Options,
-
-
options: {
-
radius: 10,
-
position: { x: window.clientWidth/2, y: window.clientHeight/|>2 },
-
speed: { x: 0, y: 0 },
-
color: '#333'
-
},
-
-
initialize: function(options) {
-
this.setOptions(options);
-
this.addToDOM();
-
-
var self = this;
-
this.dragController = new Drag(this.element, {
-
onStart: function() {
-
self.dragging = true;
-
},
-
onDrag: function() {
-
self.options.position = self.element.getPosition();
-
},
-
onComplete: function() {
-
self.options.position = self.element.getPosition();
-
self.speed = {x:0,y:0};
-
self.dragging = false;
-
}
-
});
-
this.dragging = false;
-
},
-
-
destroy: function() {
-
this.element.destroy();
-
delete this;
-
},
-
-
addToDOM: function() {
-
this.element = new Element('div', { 'class': 'item' });
-
this.element.setStyles({
-
background: this.options.color,
-
height: 2*this.options.radius,
-
width: 2*this.options.radius,
-
left: this.options.position.x,
-
top: this.options.position.y,
-
'-moz-border-radius': Math.round(this.options.radius) + 'px',
-
'-webkit-border-radius': Math.round(this.options.radius) + 'px',
-
'border-radius': Math.round(this.options.radius) + 'px'
-
}).inject(document.body);
-
},
-
-
moveTo: function(x, y) {
-
if (this.dragging) return;
-
-
this.options.position.x = x;
-
this.options.position.y = y;
-
-
// Check bounds
-
if (this.options.position.x < 0) {
-
this.options.position.x = 0;
-
this.options.speed.x = 0;
-
}
-
if (this.options.position.x > window.getWidth() – 2*this.options.radius) {
-
this.options.position.x = window.getWidth() – 2*this.options.radius;
-
this.options.speed.x = 0;
-
}
-
if (this.options.position.y < $('header').getHeight()) {
-
this.options.position.y = $('header').getHeight();
-
this.options.speed.y = 0;
-
}
-
if (this.options.position.y > window.getHeight() – 2*this.options.radius) {
-
this.options.position.y = window.getHeight() – 2*this.options.radius;
-
this.options.speed.y = 0;
-
}
-
-
// Slow down
-
if ($('chk-slowdown').get('checked')) {
-
this.options.speed.x *= 0.7;
-
this.options.speed.y *= 0.7;
-
}
-
-
if (this.options.speed.x < 0.001) this.options.speed.x = 0;
-
if (this.options.speed.y < 0.001) this.options.speed.y = 0;
-
},
-
-
draw: function() {
-
if (this.dragging) return;
-
this.element.setStyles({
-
left: Math.round(this.options.position.x),
-
top: Math.round(this.options.position.y)
-
});
-
}
-
})
-
-
var ItemController = new Class({
-
Extends: Thread,
-
items: [],
-
-
initialize: function(options) {
-
this.parent(options);
-
},
-
-
addItem: function(options) {
-
this.items.push(new Item(options));
-
},
-
-
execute: function(){
-
this.compute();
-
this.draw();
-
},
-
-
compute: function() {
-
var self = this;
-
self.items.each(function(item1, index1) {
-
self.items.each(function(item2, index2) {
-
if (index1 != index2) {
-
var distance = {}
-
distance.x = (item2.options.position.x + item2.options.radius) -
-
(item1.options.position.x + item1.options.radius);
-
distance.y = (item2.options.position.y + item2.options.radius) -
-
(item1.options.position.y + item1.options.radius);
-
-
distance.total = Math.sqrt(Math.pow(distance.x, 2) + Math.pow(distance.y, 2));
-
if (distance.total < $('distance').get('value')) {
-
-
var angle = Math.atan2(distance.y, distance.x);
-
var speed = 1000 / distance.total;
-
-
item1.options.speed.x += -speed * Math.cos(angle);
-
item1.options.speed.y += -speed * Math.sin(angle);
-
}
-
}
-
});
-
-
if (item1.options.speed.x || item1.options.speed.y) {
-
item1.moveTo(item1.options.position.x + item1.options.speed.x * (self.options.interval/1000),
-
item1.options.position.y + item1.options.speed.y * (self.options.interval/1000));
-
}
-
});
-
},
-
-
draw: function() {
-
this.items.each(function(item) { item.draw(); });
-
},
-
-
addRandom: function() {
-
this.addItem({
-
position: { x: Random.get(100,500), y: Random.get(100,400) },
-
color: 'rgb('+Random.get(0,255)+','+Random.get(0,255)+','+Random.get(0,255)+')'
-
});
-
}
-
});
-
-
window.addEvent('domready', function() {
-
window.system = new ItemController({autostart: true, interval: 50});
-
for (var i=0;i<20;i++)
-
system.addRandom();
-
-
$('btnAddRandom').addEvent('click', function(){
-
system.addRandom();
-
});
-
});
The code is very similar to the Gravity demo, although in this case the attraction becomes repulsion.
