类似于Trello的Javascript拖放悬停临时Div占位符?

Posted

技术标签:

【中文标题】类似于Trello的Javascript拖放悬停临时Div占位符?【英文标题】:Javascript Drag and Drop Hover Temporary Div Placeholder similar to Trello? 【发布时间】:2019-05-18 10:55:11 【问题描述】:

我有一个基本的拖放式 trello 式看板。您可以在不同的灰色框之间拖动任务。它使用在https://developer.mozilla.org/en-US/docs/Web/API/html_Drag_and_Drop_API 找到的 HTML 拖放 API。

var dropTarget = document.querySelector(".drop-target");
var draggables = document.querySelectorAll(".drag-task");

// Tells the other side what data is being passed (e.g. the ID is targeted)
draggables.forEach(item => 
  item.addEventListener("dragstart", function(ev)
    ev.dataTransfer.setData("srcId", ev.target.id);
  );
)
// The end destination, prevent browsers default drag and drop (disabling breaks feature)
// because it's disabled by browsers by default
dropTarget.addEventListener('dragover', function(ev) 
  ev.preventDefault();
);
// End destination where item is dropped into
dropTarget.addEventListener('drop', function(ev) 
  ev.preventDefault();
  let target = ev.target;
  let droppable  = target.classList.contains('drag-box');
  let srcId = ev.dataTransfer.getData("srcId");

  if (droppable) 
    ev.target.appendChild(document.getElementById(srcId));
  
);
/***********DRAGGABLE BACKGROUND ****************/
.drag-box 
  background-color: lightgray;
  float: right;
  width: 120px;
  min-height: 50px;
  padding-bottom: 30px;
  height: auto;
  margin: 30px;

.drag-task 
  background-color: white;
  margin: 15px;

.drop-active 
  border: 1px dashed red;
<div class="drop-target">
    <div class="drag-box">
      <div class="drag-card">
        <div draggable="true" id="task1" class="drag-task">Test Card 1</div>
      </div>
      <div class="drag-card">
        <div draggable="true" id="task2" class="drag-task">Test Card 2</div>
      </div>
      <div class="drag-card">
        <div draggable="true" id="task3" class="drag-task">Test Card 3</div>
      </div>
    </div>
    <div class="drag-box">
    </div>
    <div class="drag-box">
    </div>
  </div>

我想要实现的是类似于here 找到的这个 gif 的效果。这将创建另一个 &lt;div&gt; 元素,与拖动悬停效果上的 drag-card 类处于同一级别,并相应地重新定位。

我知道我必须使用dragoverdragleave 事件侦听器,但仅此而已。我在文件末尾添加了这段代码。我从来没有使用过拖动事件监听器,所以这对我来说是新的。

var makeHoverElement= true;
dropTarget.addEventListener("dragover", function(ev)
  if(makeHoverElement)
    let newNode =document.createElement('div');
    newNode.className ='drop-active'
    ev.target.parentElement.prepend(newNode);
    makeHoverElement = false;
  
);

dropTarget.addEventListener("dragleave", function(ev)
   // really I have no idea how to make this effect
);

到目前为止,结果并没有达到我的预期。 Dragover 正在应用于 task 项目源自的元素

【问题讨论】:

此链接与使用 react 拖放相关:rafaelquintanilha.com/sortable-targets-with-react-dnd。您不会发现所有相关内容,但我建议您阅读文章末尾附近的 card.js(关于悬停效果).. 【参考方案1】:

使用 jquery 和 jquery UI,我不久前做过类似的事情。我没有创建“制作新卡”功能,而是从“启动板”开始并创建了两个可放置的区域,可以将卡附加到这些区域并在它们之间切换 - 类似于您所拥有的。我记得使用“相交”是让它按我想要的方式工作的一个转折点——能够在列表中上下移动元素(因此它们不一定会移回它们的起源位置)。也许它可以成为您的起点? 这是fiddle(jquery 是旧的.. 建议更新到较新的版本)

希望这会有所帮助。

编辑:我对您的代码进行了一些小调整,以添加轮廓并在移动时更改光标。根据another question 的评论,添加边框是创建视觉“轮廓”效果的最有效方法。有一种更长的方法可以创建“可排序”效果,在我发现的 codepen 中进行了演示,并简单解释了,该功能基于计算悬停位置以及拖动的元素是否位于列表中某个项目的一半, 效果显示并且项目可以放在列表项之间。

希望这已经足够清楚了!

// Tells the other side what data is being passed (e.g. the ID is targeted)
var dropTarget = document.querySelector(".drop-target");
var draggables = document.querySelectorAll(".drag-task");

// Tells the other side what data is being passed (e.g. the ID is targeted)
draggables.forEach(item => 
  item.addEventListener("dragstart", function(ev) 
    ev.dataTransfer.setData("srcId", ev.target.id);
  );
)
// The end destination, prevent browsers default drag and drop (disabling breaks feature)
// because it's disabled by browsers by default
dropTarget.addEventListener('dragover', function(ev) 
  ev.preventDefault();
);
// End destination where item is dropped into
dropTarget.addEventListener('drop', function(ev) 
  ev.preventDefault();
  let target = ev.target;
  let droppable = target.classList.contains('drag-box');
  let srcId = ev.dataTransfer.getData("srcId");

  if (droppable) 
    ev.target.appendChild(document.getElementById(srcId));
  
);
.drag-box 
  background-color: lightgray;
  float: left;
  width: 120px;
  min-height: 80px; /*lengthened the height slightly*/
  padding-bottom: 30px;
  height: auto;
  margin: 30px;
  cursor: move; /*added the 'cross' cursor*/

.drag-task 
  background-color: white;
  margin: 10px;
  padding: 5px; /*added padding to make tiles bigger*/
  border:1px dashed #000000; /*set an outline*/


.drop-active 
  border: 1px dashed red;
  cursor: pointer; /*change the pointer back to the default cursor while moving between lists*/
<div class="drop-target">
  <div class="drag-box">
    <div class="drag-card">
      <div draggable="true" id="task1" class="drag-task">Test Card 1</div>
    </div>
    <div class="drag-card">
      <div draggable="true" id="task2" class="drag-task">Test Card 2</div>
    </div>
    <div class="drag-card">
      <div draggable="true" id="task3" class="drag-task">Test Card 3</div>
    </div>
  </div>
  
  <!-- added tiles to the 2nd list (and deleted 3rd box)-->
  <div class="drag-box">
    <div class="drag-card">
      <div draggable="true" id="orange" class="drag-task">Orange</div>
    </div>
    <div class="drag-card">
      <div draggable="true" id="apple" class="drag-task">Apple</div>
    </div>
    <div class="drag-card">
      <div draggable="true" id="pear" class="drag-task">Pear</div>
    </div>
  </div>

$("#launchPad").height($(window).height() - 20);
var dropSpace = $(window).width() - $("#launchPad").width();
$("#dropZone").width(dropSpace - 70);
$("#dropZone").height($("#launchPad").height());

$(".card").draggable(
    appendTo: "#launchPad",
    cursor: "move",
    helper: 'clone',
    revert: "invalid",

);

$("#launchPad").droppable(
    tolerance: "intersect",
    accept: ".card",
    activeClass: "ui-state-default",
    hoverClass: "ui-state-hover",
    drop: function(event, ui) 
        $("#launchPad").append($(ui.draggable));
    
);

$(".stackDrop").droppable(
    tolerance: "intersect",
    accept: ".card",
    activeClass: "ui-state-default",
    hoverClass: "ui-state-hover",
    drop: function(event, ui)         
        $(this).append($(ui.draggable));
    
);
body  
    margin: 0;
    background-color: #ffffcc;

#launchPad 
    width:170px;
    float:left;
    border: 1px solid #eaeaea;
    background-color: #f5f5f5;

#dropZone 
    float:right;
    border: 1px solid #eaeaea;
    background-color: #ffffcc;

.card  
    width: 130px; 
    padding: 5px 10px;
    margin:5px;
    border:1px solid #ccc;
    background-color: #eaeaea;

.stack 
  display:inline-block;
   vertical-align:top;
    width: 180px;
    border: 1px solid #ccc;
    background-color: #f5f5f5;
    margin: 20px;

.stackHdr 
    background-color: #eaeaea;
    border: 1px solid #fff;
    padding: 5px 

.stackDrop 
    min-height:100px;
    padding: 15px;
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script src="http://code.jquery.com/ui/1.9.2/jquery-ui.js"></script>

<div id="launchPad">    
    <div class="card draggable" >
        apple
    </div> 
    <div class="card draggable">
        orange
    </div> 
    <div class="card draggable">
        banana
    </div> 
    <div class="card draggable">
        car
    </div> 
    <div class="card draggable">
        bus
    </div> 
</div>

<div id="dropZone">
    <div class="stack">
        <div class="stackHdr">
            Drop here 
        </div>
        <div class="stackDrop droppable">
            
        </div>
    </div>
    
    <div class="stack">
        <div class="stackHdr">
            Or here
        </div>
        <div class="stackDrop droppable">
            
        </div>
    </div>
</div>

【讨论】:

顺便说一句,我在我关注的回购链接中没有看到任何 gif? repo 链接也有一个指向实时站点的链接。我希望在 vanilla JS 中做到这一点,所以我可以理解它是如何工作的,我之前看过 jquery UI 示例【参考方案2】:

问题出在ev.target.parentElement.prepend(newNode); 您的ev.target 仍然是您将其拖动 的节点的子节点。这就是为什么将虚线边框 div 添加到“旧”框的原因。我建议在你的'dragover'函数中明确找到鼠标所在的元素并将你的 newNode 添加到它。例如,您可以通过document.querySelector(":hover" ) 选择它或尝试在那里处理“鼠标悬停”事件。

至于'dragleave'效果,我建议您使用Node.cloneNode() 方法克隆您的ev.target,并使用Node.insertBefore() 将克隆附加到ev.target.parentElement。 MDN on .insertBefore()

【讨论】:

以上是关于类似于Trello的Javascript拖放悬停临时Div占位符?的主要内容,如果未能解决你的问题,请参考以下文章

Trello 拖放效果

使用cdkDropList时元素样式不适用(Angluar cdk拖放)

css / html5:拖放后悬停状态保持不变

使用 cdkDropList 时元素样式不适用(角度 cdk 拖放)

如何确定鼠标相对于父元素的悬停位置

Trello 如何访问用户的剪贴板?