首页 > 解决方案 > 如何使可拖动的 div 元素不相互堆叠

问题描述

我正在使用 创建各种可拖动元素let divEx = document.createElement('div'),然后使用 将其添加到主 div 中main.appendChild(divEx),最后使用 将属性 draggable 设置为 true memDiv.setAttribute('draggable', 'true')。整个功能都在一个函数中,当按下按钮时在主 Js 文件中调用该函数。虽然拖放功能可以正常工作,但如果位置样式属性设置为绝对,则 div 元素会设置在每个元素上,如果在 CSS 中将其设置为相对,则会在每个元素上设置。在 DOM 中,它显示了不同的 div 元素,但是它们的位置都是相同的。我希望下面的截图能解释这个问题:

绝对案例:

屏幕上有三个 div 元素(class=memDiv),两个在一个上面,一个新创建的

这里是我移动第三个元素后显示的,它也堆叠在其他元素之上

这个问题的最佳解决方案是什么?

标签: javascripthtmlcssdraggable

解决方案


答:关键方法/解决方案是Collision Detection div 元素之间

看起来您正在尝试做游戏或类似的事情,请避免痛苦/不要重新发明轮子:),只需利用库并查找collision detection或将 Z 索引设置为 1 并检查重叠,我更喜欢 sat.js 库。

注意:这里是sat.js并从这里查看这个样本,从第 3 方/信用到 OSU 布莱克

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 });
        }
      }
    }());

推荐阅读