Repulsive force in Javascript with Mootools draggables

4 January 2009 | 3 Comments | 1,074 view(s)

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.

Repulsive Force Demo

See the demo in action.

Source code

  1. var Item = new Class({
  2.   Implements: Options,
  3.  
  4.   options: {
  5.     radius: 10,
  6.     position: { x: window.clientWidth/2, y: window.clientHeight/|>2 },
  7.     speed: { x: 0, y: 0 },
  8.     color: '#333'
  9.   },
  10.  
  11.   initialize: function(options) {
  12.     this.setOptions(options);
  13.     this.addToDOM();
  14.    
  15.     var self = this;
  16.     this.dragController = new Drag(this.element, {
  17.       onStart: function() {
  18.         self.dragging = true;
  19.       },
  20.       onDrag: function() {
  21.         self.options.position = self.element.getPosition();
  22.       },
  23.       onComplete: function() {
  24.         self.options.position = self.element.getPosition();
  25.         self.speed = {x:0,y:0};
  26.         self.dragging = false;
  27.       }
  28.     });
  29.     this.dragging = false;
  30.   },
  31.  
  32.   destroy: function() {
  33.     this.element.destroy();
  34.     delete this;
  35.   },
  36.  
  37.   addToDOM: function() {
  38.     this.element = new Element('div', { 'class': 'item' });
  39.     this.element.setStyles({
  40.       background: this.options.color,
  41.       height: 2*this.options.radius,
  42.       width:  2*this.options.radius,
  43.       left: this.options.position.x,
  44.       top:  this.options.position.y,
  45.       '-moz-border-radius': Math.round(this.options.radius) + 'px',
  46.       '-webkit-border-radius': Math.round(this.options.radius) + 'px',
  47.       'border-radius': Math.round(this.options.radius) + 'px'
  48.     }).inject(document.body);
  49.   },
  50.  
  51.   moveTo: function(x, y) {
  52.     if (this.dragging) return;
  53.  
  54.     this.options.position.x = x;
  55.     this.options.position.y = y;
  56.  
  57.     // Check bounds
  58.     if (this.options.position.x < 0) {
  59.       this.options.position.x = 0;
  60.       this.options.speed.x = 0;
  61.     }
  62.     if (this.options.position.x > window.getWidth()2*this.options.radius) {
  63.       this.options.position.x = window.getWidth()2*this.options.radius;
  64.       this.options.speed.x = 0;
  65.     }
  66.     if (this.options.position.y < $('header').getHeight()) {
  67.       this.options.position.y = $('header').getHeight();
  68.       this.options.speed.y = 0;
  69.     }
  70.     if (this.options.position.y > window.getHeight()2*this.options.radius) {
  71.       this.options.position.y = window.getHeight()2*this.options.radius;
  72.       this.options.speed.y = 0;
  73.     }
  74.  
  75.     // Slow down
  76.     if ($('chk-slowdown').get('checked')) {
  77.       this.options.speed.x *= 0.7;
  78.       this.options.speed.y *= 0.7;
  79.     }
  80.    
  81.     if (this.options.speed.x < 0.001) this.options.speed.x = 0;
  82.     if (this.options.speed.y < 0.001) this.options.speed.y = 0;
  83.   },
  84.  
  85.   draw: function() {
  86.     if (this.dragging) return;
  87.     this.element.setStyles({
  88.       left: Math.round(this.options.position.x),
  89.       top:  Math.round(this.options.position.y)
  90.     });
  91.   }
  92. })
  93.  
  94. var ItemController = new Class({
  95.   Extends: Thread,
  96.   items: [],
  97.  
  98.   initialize: function(options) {
  99.     this.parent(options);
  100.   },
  101.  
  102.   addItem: function(options) {
  103.     this.items.push(new Item(options));
  104.   },
  105.  
  106.   execute: function(){
  107.     this.compute();
  108.     this.draw();
  109.   },
  110.  
  111.   compute: function() {
  112.     var self = this;
  113.     self.items.each(function(item1, index1) {
  114.       self.items.each(function(item2, index2) {
  115.         if (index1 != index2) {
  116.           var distance = {}
  117.           distance.x = (item2.options.position.x + item2.options.radius) -
  118.                  (item1.options.position.x + item1.options.radius);
  119.           distance.y = (item2.options.position.y + item2.options.radius) -
  120.                   (item1.options.position.y + item1.options.radius);
  121.  
  122.           distance.total = Math.sqrt(Math.pow(distance.x, 2) + Math.pow(distance.y, 2));
  123.           if (distance.total < $('distance').get('value')) {
  124.        
  125.             var angle = Math.atan2(distance.y, distance.x);
  126.             var speed = 1000 / distance.total;
  127.  
  128.             item1.options.speed.x += -speed * Math.cos(angle);
  129.             item1.options.speed.y += -speed * Math.sin(angle);
  130.           }
  131.         }
  132.       });
  133.      
  134.       if (item1.options.speed.x || item1.options.speed.y) {
  135.         item1.moveTo(item1.options.position.x + item1.options.speed.x * (self.options.interval/1000),
  136.                item1.options.position.y + item1.options.speed.y * (self.options.interval/1000));
  137.       }
  138.     });
  139.   },
  140.  
  141.   draw: function() {
  142.     this.items.each(function(item) { item.draw(); });
  143.   },
  144.  
  145.   addRandom: function() {
  146.     this.addItem({
  147.       position: { x: Random.get(100,500), y: Random.get(100,400) },
  148.       color: 'rgb('+Random.get(0,255)+','+Random.get(0,255)+','+Random.get(0,255)+')'
  149.     });
  150.   }
  151. });
  152.  
  153. window.addEvent('domready', function() {
  154.   window.system = new ItemController({autostart: true, interval: 50});
  155.   for (var i=0;i<20;i++)
  156.     system.addRandom();
  157.  
  158.   $('btnAddRandom').addEvent('click', function(){
  159.     system.addRandom();
  160.   });
  161. });

The code is very similar to the Gravity demo, although in this case the attraction becomes repulsion.

Related Posts:

Tags: , , , , ,

3 Comments

  1. cssProdigy said on 5 Jan 2009 at 14:08:

    that’s just cool, another great example of what’s possible using MooTools.

  2. alberto said on 8 Jan 2009 at 10:42:

    Me “caguen sos” jajja, ya es dificil llegar aqui. Pulsè por curiosidad en un blog de un español en irlanda, a ver si vivias y lleguè hasta aqui. Como comprenderàs lei por encima y entre informatica e ingles no me enterè de nada.

    Yo te doy la enhorabuena porque me medio enterè que te has hecho padre y te felicito el año nuevo. Siento dar la nota y escribir en español pero es lo que hay. Un abrazo Eneko y espero que todo te siga yendo bien.

  3. Eneko Alonso said on 8 Jan 2009 at 20:00:

    Gracias Alberto :)

Leave a Reply