Repulsive force in Javascript with Mootools draggables

Jan 04 2009 Published by Eneko Alonso under uncategorized

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.

3 responses so far