如何使可拖动的 div 元素不相互堆叠

Posted

技术标签:

【中文标题】如何使可拖动的 div 元素不相互堆叠【英文标题】:How to make draggable div elements not stack upon each other 【发布时间】:2021-12-14 22:03:27 【问题描述】:

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

绝对案例:

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

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

【问题讨论】:

【参考方案1】:

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

看起来你正在尝试做一个游戏或类似的东西,请省去你的痛苦/不要重新发明***:),只需利用一个库并查找 collision detection 或将 Z 索引设置为 1 和检查过圈,我更喜欢 sat.js 库。

注意:这里是sat.js,请查看来自here 的来自第 3 方/归功于 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 );
        
      
    ());

【讨论】:

以上是关于如何使可拖动的 div 元素不相互堆叠的主要内容,如果未能解决你的问题,请参考以下文章

jQuery可拖动嵌套div并再次拖动

使用 jQuery UI 使可拖动元素在可放置中可排序

如何防止 Resizable 和 Draggable 元素相互折叠?

Jquery ui - 可排序:在可排序元素中按图标“句柄”拖动

如何将子元素以外的元素用作拖动手柄?

使一个新的 div 可拖动,尝试了一切