如何使可拖动的 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 元素不相互堆叠的主要内容,如果未能解决你的问题,请参考以下文章
如何防止 Resizable 和 Draggable 元素相互折叠?