having fun with code

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.

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:

3 Comments to Repulsive force in Javascript with Mootools draggables

  1. cssProdigy's Gravatar cssProdigy
    January 5, 2009 at 14:08 | Permalink

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

  2. alberto's Gravatar alberto
    January 8, 2009 at 10:42 | Permalink

    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.

Leave a Reply

You can use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Additional comments powered by BackType

About the blog

This is a blog about development, focused mainly on Javascript but also other languages like python, shell scripts and more.

About the author

Eneko Alonso is a software engineer and UI developer with more than eight years of experience in software and web development. He lives in San Luis Obispo, California and works at LEVEL Studios.

Contact Info

Contact Info