Home > Net >  How to make draggable div elements not stack upon each other
How to make draggable div elements not stack upon each other

Time:10-30

I am creating various draggable elements using let divEx = document.createElement('div'), then add it to the main div with main.appendChild(divEx) and finally setting the attribute draggable to true using memDiv.setAttribute('draggable', 'true'). The whole functionality is in a function that is called in the main Js file when a button is pressed. Although, the drag and drop functionality works fine, the div elements are set upon each if the position style attribute is set to absolute or under each other if it's set to relative in the CSS. In the DOM it shows the different div elements, however the positions are the same for all of them. I hope the following screenshots explain the issue:

Absolute case:

There are three div elements (class=memDiv) on the screen two on top of each other and a newly created

Here is displayed after I move the third element, it is also stacked on top of the others

What is the best solution for this issue?

CodePudding user response:

Answer: The key approach/solution is Collision Detection between div elements

It looks like you are trying to do a game or something similar, please save your self the pain/don't reinvent the wheel :) , just leverage a library and look for collision detection or set Z index to 1 and check of over lap, I prefer the sat.js lib.

note: Here is sat.js and look at this sample from here from a 3rd party/credit to OSU blake

console.clear();
var log = console.log.bind(console);



// Alias a few things in SAT.js to make the code shorter
var V = function (x, y) { return new SAT.Vector(x, y); };
var P = function (pos, points) { return new SAT.Polygon(pos, points); };
var C = function (pos, r) { return new SAT.Circle(pos, r); };
var B = function (pos, w, h) { return new SAT.Box(pos, w, h); };

// Converts a SAT.Polygon into a SVG path string.
function poly2path(polygon) {
  var pos = polygon.pos;
  var points = polygon.calcPoints;
  var result = 'M'   pos.x   ' '   pos.y;
  result  = 'M'   (pos.x   points[0].x)   ' '   (pos.y   points[0].y);
  for (var i = 1; i < points.length; i  ) {
    var point = points[i];
    result  = 'L'   (pos.x   point.x)   ' '   (pos.y   point.y);
  }
  result  = 'Z';
  return result;
}

// Create a Raphael start drag handler for specified entity
function startDrag(entity) {
  return function () {
    this.ox = entity.data.pos.x;
    this.oy = entity.data.pos.y;
  };
}
// Create a Raphael move drag handler for specified entity
function moveDrag(entity, world) {
  return function (dx, dy) {
    // This position updating is fairly naive - it lets objects tunnel through each other, but it suffices for these examples.
    entity.data.pos.x = this.ox   dx;
    entity.data.pos.y = this.oy   dy;
    world.simulate();
  };
}
// Create a Raphael end drag handler for specified entity
function endDrag(entity) {
  return function () {
    entity.updateDisplay();
  };
}

var idCounter = 0;

function noop() {}

function Entity(data, display, options) {
  options = _.defaults(options || {}, {
    solid: false, // Whether this object is "solid" and therefore should participate in responses.
    heavy: false, // Whether this object is "heavy" and can't be moved by other objects.
    displayAttrs: {}, // Raphael attrs to set on the display object
    onCollide: noop, // Function to execute when this entity collides with another - arguments are (otherEntity, response)
    onTick: noop // Function called at the start of every simulation tick - no arguments
  });
  this.id = idCounter  ;
  this.data = data;
  this.display = display;
  this.displayAttrs = _.extend({
    fill: '#CCC',
    stroke: '#000'
  }, options.displayAttrs);
  this.isSolid = options.solid;
  this.isHeavy = options.heavy;
  this.onCollide = options.onCollide;
  this.onTick = options.onTick;
}
Entity.prototype = {
  remove: function () {
    this.display.remove();
  },
  // Call this to update the display after changing the underlying data.
  updateDisplay: function () {
    if (this.data instanceof SAT.Circle) {
      this.displayAttrs.cx = this.data.pos.x;
      this.displayAttrs.cy = this.data.pos.y;
      this.displayAttrs.r = this.data.r;
    } else {
      this.displayAttrs.path = poly2path(this.data);
    }
    this.display.attr(this.displayAttrs);
  },
  tick: function () {
    this.onTick();
  },
  respondToCollision: function (other, response) {
    this.onCollide(other, response);
    // Collisions between "ghostly" objects don't matter, and
    // two "heavy" objects will just remain where they are.
    if (this.isSolid && other.isSolid &&
      !(this.isHeavy && other.isHeavy)) {
      if (this.isHeavy) {
        // Move the other object out of us
        other.data.pos.add(response.overlapV);
      } else if (other.isHeavy) {
        // Move us out of the other object
        this.data.pos.sub(response.overlapV);
      } else {
        // Move equally out of each other
        response.overlapV.scale(0.5);
        this.data.pos.sub(response.overlapV);
        other.data.pos.add(response.overlapV);
      }
    }
  }
};

function World(canvas, options) {
  options = _.defaults(options || {},  {
    loopCount: 1 // number of loops to do each time simulation is called. The higher the more accurate the simulation, but slowers.
  });
  this.canvas = canvas; // A raphael.js canvas
  this.response = new SAT.Response(); // Response reused for collisions
  this.loopCount = options.loopCount;
  this.entities = {};
}
World.prototype = {
  addEntity: function(data, options) {
    var entity = new Entity(
      data,
      data instanceof SAT.Circle ? this.canvas.circle() : this.canvas.path(),
      options
    );
    // Make the display item draggable if requested.
    if (options.draggable) {
      entity.display.drag(moveDrag(entity, this), startDrag(entity), endDrag(entity));
    }
    entity.updateDisplay();
    this.entities[entity.id] = entity;
    return entity;
  },
  removeEntity: function (entity) {
    entity.remove();
    delete this.entities[entity.id];
  },
  simulate: function () {
    var entities = _.values(this.entities);
    var entitiesLen = entities.length;
    // Let the entity do something every simulation tick
    _.each(entities, function (entity) {
      entity.tick();
    });
    // Handle collisions - loop a configurable number of times to let things "settle"
    var loopCount = this.loopCount;
    for (var i = 0; i < loopCount; i  ) {
      // Naively check for collision between all pairs of entities 
      // E.g if there are 4 entities: (0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)
      for (var aCount = 0; aCount < entitiesLen; aCount  ) {
        var a = entities[aCount];
        for (var bCount = aCount   1; bCount < entitiesLen; bCount  ) {
          var b = entities[bCount];
          this.response.clear();
          var collided;
          var aData = a.data;
          var bData = b.data;
          if (aData instanceof SAT.Circle) {
            if (bData instanceof SAT.Circle) {
              collided = SAT.testCircleCircle(aData, bData, this.response);
            } else {
              collided = SAT.testCirclePolygon(aData, bData, this.response);
            }
          } else {
            if (bData instanceof SAT.Circle) {
              collided = SAT.testPolygonCircle(aData, bData, this.response);
            } else {
              collided = SAT.testPolygonPolygon(aData, bData, this.response);
            }
          }
          if (collided) {
            a.respondToCollision(b, this.response);
          }
        }
      }
    }
    // Finally, update the display of each entity now that the simulation step is done.
    _.each(entities, function (entity) {
      entity.updateDisplay();
    });
  }
};





(function () {
      var canvas = Raphael('example1', 640, 480);
      var world = new World(canvas);
      var poly = world.addEntity(P(V(160,120), [V(0,0), V(60, 0), V(100, 40), V(60, 80), V(0, 80)]).translate(-50, -40), { solid: true, draggable: true });
      var poly2 = world.addEntity(P(V(10,10), [V(0,0), V(30, 0), V(30, 30), V(0, 30)]), { solid: true, draggable: true });
      var circle1 = world.addEntity(C(V(50, 200), 30), { solid: true, heavy: true, draggable: true });
      function doRotate() {
        poly.data.setAngle(poly.data.angle   (Math.PI / 60)); // Assuming 60fps this will take ~2 seconds for a full rotation
        world.simulate();
        window.requestAnimationFrame(doRotate);
      }
      window.requestAnimationFrame(doRotate);
    }());


(function () {
      var canvas = Raphael('example2', 640, 640);
      var world = new World(canvas, {
        loopCount: 5
      });
      for (var i = 0; i < 16; i  ) {
        for (var j = 0; j < 16; j  ) {
          var displayAttrs = {
            fill: 'rgba('   Math.floor(Math.random() * 255)   ','    Math.floor(Math.random() * 255)   ','    Math.floor(Math.random() * 255)   ')'
          }
          var c = world.addEntity(C(V((40 * i)   20, (40 * j)   20), 18), { solid: true, draggable: true, displayAttrs: displayAttrs });
        }
      }
    }());
  • Related