HTML5 DnD - 为啥在拖放元素后重影图像只能按预期工作?

Posted

技术标签:

【中文标题】HTML5 DnD - 为啥在拖放元素后重影图像只能按预期工作?【英文标题】:HTML5 DnD - Why is ghost image only working as expected AFTER dragging & dropping an element?HTML5 DnD - 为什么在拖放元素后重影图像只能按预期工作? 【发布时间】:2020-09-11 04:25:32 【问题描述】:

我正在尝试获取带有嵌套 imgp 元素,以便在拖动时显示为幻像。

我不确定如何调试它,但我已经注意到,一旦图像被缓存或被拖放到页面上的某个位置,它就会按预期工作。我在这里做了一个 MWE:

第一次加载页面时会拖动笑脸并显示错误行为 - 表情符号在拖动过程中显示。悲伤的脸被拖动、释放,然后重新拖动,这会导致预期的行为 - 表情符号确实显示为幻影图像的一部分。所有图像都是如此。

我尝试过的: 我认为这可能是页面元素加载方式的问题,所以我将 javascript 移动到正文的底部(试图确保在脚本运行之前加载所有元素)。这并不能解决问题。

MWE 代码: 我得到了表情符号 from here,但我猜你的机器上的任何 png 都可以重现这个。

index.php:

<html>
<head>
  <link rel="stylesheet" type="text/css" href="stylesheet.css">
</head>
<body>
  <h2>Order these items</h2>
  <div id="main_wrapper">
    <?php
      $json = json_decode(file_get_contents("image_set.json"), true);
      echo '<div id="home_container" ondrop="drop(event, this)" ondragover="allowDrop(event)">';
      $i = 0;
      foreach($json as $k => $v)  
        echo '<p class="drag_item" draggable="true" ondragstart="drag(event)" id="drag'.$i.'"><img draggable="false" src="/images/'.$v['fn'].'" width=200 height=200>'.$v['text'].'</p>';
        $i++;
      
      echo '</div>';
    ?>
    <div id="buffer" style="min-height:100px; width:100%;"></div>
    <div id="dropzone_wrapper">
    <?php
      for($i = 0; $i < count($json); $i++) 
        echo '<div class="dropzone" id="dropzone'.$i.'" ondrop="drop(event, this)" ondragover="allowDrop(event)"></div>';
        if($i < count($json)-1)echo '&lt;';
      
    ?>
    </div>
    <div id="msg"></div>
  </div>
  <script>
  function allowDrop(ev) 
    ev.preventDefault();
  

  function drag(ev) 
    var dataList = ev.dataTransfer.items;
    dataList.add(ev.target.id, "text/plain");
  

  function drop(ev, el) 
    ev.preventDefault();
    var data            = ev.dataTransfer.getData("text");
    var element_to_drop = document.getElementById(data);
    let droppable       = true;

    // If the dropzone already contains something (not text due to
    // spaces in markup being counted as text), don't allow 
    // another drop to occur.
    if (el.childNodes.length > 0) 
      el.childNodes.forEach(function(obj) 
        if(obj.nodeName != '#text') 
          droppable = false;
        
      );
    

    if(droppable)
      el.appendChild(document.getElementById(data));
  

  function reset() 
    // Put all drag items back into the home container
    let home  = document.getElementById('home_container');
    let cards = document.querySelectorAll('.drag_item');

    for(var i = 0; i < cards.length; i++) 
      home.appendChild(cards[i]);
    
  
  </script>
</body>
</html>

image_set.json:


  "happy": 
    "fn":"happy.png",
    "text":"A happy face"
  ,
  "sad": 
    "fn":"sad.png",
    "text":"A sad face"
  ,
  "angry": 
    "fn":"angry.png",
    "text":"An angry face"
  ,
  "confused": 
    "fn":"confused.png",
    "text":"A confused face"
  ,
  "sleepy": 
    "fn":"sleepy.png",
    "text":"A sleepy face"
  

stylesheet.css:

* 
  box-sizing:border-box;
  padding:0px;
  margin:0px;
  font-family:sans-serif;
  font-weight:100;


body 
  padding:20px;


h2 
  padding:20px 0;
  font-size:4em;


p.drag_item 
  text-align:center;
  transition:0.5s;
  width:200px;
  height:200px;


.drag_item:hover 
  cursor:move;


#home_container, #dropzone_wrapper 
  min-height:200px;
  width:100%;
  display:flex;
  flex-direction:row;
  justify-content:space-around;
  margin:20px 0;
  align-items:center;


#dropzone_wrapper 
  font-size:3em;


#dropzone_wrapper p 
  font-size:initial;


#home_container 
  border:1px solid black;
  border-radius:8px;
  background-color:#e5e5e5;


#home_container p 
  width:200px;
  font-size:16px;


#msg 
  display:block;
  font-size:2.5em;


.dropzone 
  min-height:200px;
  width:200px;
  border:1px dashed black;
  background-color:#00a8bd;

【问题讨论】:

我认为它取决于浏览器,因为我已经尝试过了,它在 Chrome 81 中适用于我,但在 Firefox 76 中会产生意外行为。 @KriskóTamás 但这并不能解释为什么在 Firefox 中拖动、释放和再次拖动后它会起作用。 昨天我设法找到了解决您问题的方法。请检查您是否对此有任何问题,如果没有,请点赞并接受答案。如果您对此有任何疑问,或者遇到了问题,请在此处发表评论。 【参考方案1】:

我做了一些研究来找出问题所在。这对我来说有点困难,因为 Firefox 是唯一一个在第一次加载页面和第一次拖动时没有显示重影图像的浏览器。我打开Network 选项卡,发现仅在第一次拖动时才请求图像(我不太明白,因为图像已完全加载)。

无论如何,我终于设法通过将可拖动元素更改为图像而不是段落来实现这一点。

index.php:

<div id="main_wrapper">
    <?php
        $json = json_decode(file_get_contents("image_set.json"), true);
        echo '<div id="home_container" ondrop="drop(event, this)" ondragover="allowDrop(event)">';
        $i = 0;
        foreach($json as $k => $v)  
            echo '<p class="drag_item"><img ondragstart="drag(event)" id="drag'.$i.'" draggable="true" src="images/'.$v['fn'].'" width=200 height=200>'.$v['text'].'</p>';
            $i++;
        
        echo '</div>';
    ?>
    <div id="buffer" style="min-height:100px; width:100%;"></div>
    <div id="dropzone_wrapper">
        <?php
            for($i = 0; $i < count($json); $i++) 
                echo '<div class="dropzone" id="dropzone'.$i.'" ondrop="drop(event, this)" ondragover="allowDrop(event)"></div>';
                if($i < count($json)-1)echo '&lt;';
            
        ?>
    </div>
    <div id="msg"></div>
</div>

JS:

function allowDrop(ev) 
    ev.preventDefault();


function drag(ev) 
    // get the cursor position relative to the element
    var x = (ev.pageX - ev.target.offsetLeft) + document.body.scrollLeft;
    var y = (ev.pageY - ev.target.offsetTop) + document.body.scrollTop;

    ev.dataTransfer.setData("text", ev.target.id);

    // set the parent element (the paragraph) as the custom ghost image and set the position of the ghost image (x, y)
    ev.dataTransfer.setDragImage(ev.target.parentElement, x, y);
  

function drop(ev, el) 
    ev.preventDefault();
    var data = ev.dataTransfer.getData("text");
    var element_to_drop = document.getElementById(data);
    let droppable = true;

    if (el.childNodes.length > 0) 
        el.childNodes.forEach(function(obj) 
            if(obj.nodeName != '#text') 
                droppable = false;
            
        );
    

    if(droppable)
        el.appendChild(document.getElementById(data).parentElement);


function reset() 
    let home = document.getElementById('home_container');
    let cards = document.querySelectorAll('.drag_item');

    for(var i = 0; i < cards.length; i++) 
        home.appendChild(cards[i]);
    

这在 Chrome、Edge、IE 11 中运行良好

注意:这只能在 Firefox 中完美运行(段落文本只出现在这个浏览器中)

【讨论】:

谢谢,@KriskóTamás。我遇到的问题是我希望 p 元素中的文本也出现在幻像中。 所以,我设法让它在 Firefox 中工作,但在其他浏览器中,段落文本仍然没有显示在幻像中。 我已对帖子进行了编辑。如您所见,我使用了event.dataTransfer.setDragImage() 方法来设置自定义图像。

以上是关于HTML5 DnD - 为啥在拖放元素后重影图像只能按预期工作?的主要内容,如果未能解决你的问题,请参考以下文章

Firefox 在拖放幽灵预览中不显示图像

如何在拖放源中创建 Dojo onDrop 事件

在 Internet Explorer 9 中进行 Dart HTML5 拖放

Internet Explorer 9 拖放 (DnD)

HTML 5 拖放 如何判断一个元素是不是接受拖放

js拖放事件详解及实战