I'm trying to get a p
element with a nested img
to show as the ghost image when dragging.
I'm not sure how to debug this but I have noticed that once the images are cached or have been dragged and dropped somewhere on the page, it works as expected. I've made a MWE here:
The smiley face is dragged on first load of the page and shows the erroneous behavior - the emoji doesn't show during the drag. The sad face is dragged, released, and then redragged, which results in the expected behavior - the emoji does show as part of the ghost image. This is true of all the images.
What I've tried: I thought it might be an issue with the way the page elements are loaded, so I moved the javascript to the bottom of the body (trying to ensure all elements are loaded before the script runs). This doesn't solve the issue.
MWE code: I got the emojis from here, but I guess any pngs you have lying around on your machine will do to reproduce this.
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 '<';}
}
?>
</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;
}
I've done a bit research to find the problem. This was a bit hard for me, because Firefox was the only browser where the ghost image was not shown on the first load of the page and the first drag. I opened the Network
tab and found out that the image is only requested on the first drag (which I don't really understand, because the images were completely loaded).
Anyways, I finally I managed to get this to work, by changing the draggable element to the image instead of the paragraph.
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 '<';}
}
?>
</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]);
}
}
This works pretty well in: Chrome, Edge, IE 11
NOTE: This only works perfectly in Firefox (the paragraph text only appears in this browser)