Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does HTML/Javascript support dragging an image onto a webpage?

Tags:

Does HTML5/Javascript support dragging an image onto a webpage?

i.e.

Can I create a webpage to allow user to drag an image from another site directly onto a placeholder on my site (all client side), then use another button to upload that image back to server?

like image 627
Paul Taylor Avatar asked Mar 10 '18 10:03

Paul Taylor


1 Answers

I can't tell for sure how all browsers did / do / will behave in such a case, but what I've seen is that when you drag an image from a webpage and drop it to a webpage (the same or an other one), browsers won't create a File (or Blob) from it, but only its markup.

This means that you will still be tied by cross-origin policies in this case, i.e you won't be able to access the data of the image file, and thus won't be able to send it to your server either.

But this won't prevent you from displaying this image on your page, nor to grab its src.

So what you can do, is a two cases handling:

  1. Your user drops a File from its file-system: you will have to send it to your server, best through a FormData as multipart. To display it, create a new <img> element and set its src to a blobURI made from the File.
  2. They drop from an other webpage: The dataTransfer should contain a text/html item. If so, try to parse it and look for existing <img> elements. If there are, you can adopt them in your current document for display. However to save it on your server, you will have to fetch from your server directly (which is not tied by the same-origin policies), and thus you will have to send this URIs to your server.

var dropzone = document.getElementById('dropzone'),
  send_btn = document.getElementById('send'),
  res = document.getElementById('res'),
  imgList = [];

dropzone.ondragover = function ondragover(e) {
  e.preventDefault();
  dropzone.classList.add('dragover');
};
dropzone.ondrop = function ondrop(e) {
  e.preventDefault();
  dropzone.classList.remove('dragover');
  // try to get images from this dropevent
  var imageObjects = retrieveImageData(e.dataTransfer);
  if (!imageObjects) return;
  imageObjects.forEach(function appendToDoc(imgObj) {
    res.appendChild(imgObj.element);
  });
  // store it
  imgList = imgList.concat(imageObjects);
  if (imageObjects.length)
    send_btn.disabled = false;
};
dropzone.ondragexit = function(ondragexit) {
  dropzone.classList.remove('dragover');
};

function retrieveImageData(dT) {
  // first try to get Files
  var files = getFiles(dT);
  if (files.length) {
    return files;
  }
  // if none, try to get HTMLImage or SVGImage
  var elems = getHTMLMarkup(dT);
  if (elems && elems.length) {
    return elems;
  }
  // we could also try to getData('text/plain') hoping for an url
  // but this might not be such a good idea...
  console.warn('unable to retrieve any image in dropped data');
}

function getFiles(dT) {
  // quite simple: won't traverse folders
  var files = [],
    imgObj;
  if (dT.files && dT.files.length) {
    for (var i = 0; i < dT.files.length; i++) {
      // only image Files
      if (dT.files[i].type.indexOf('image/') === 0) {
        imgObj = {
          type: 'file',
          element: new Image(),
          file: dT.files[i]
        };
        imgObj.element.onerror = onIMGError;
        imgObj.element.src = URL.createObjectURL(imgObj.file);
        files.push(imgObj);
      }
    }
  }
  return files;
}

function getHTMLMarkup(dT) {
  var markup = dT.getData('text/html');
  if (markup) {
    var doc = new DOMParser().parseFromString(markup, 'text/html');
    var imgs = doc && doc.querySelectorAll('img,image') ||  [];
    imgs.forEach(toImageObject);
    return Array.prototype.map.call(imgs, toImageObject);
  }

  function toImageObject(element) {
    var img;
    if (element instanceof SVGImageElement) {
      img = new Image();
      img.src = element.getAttributeNS('http://www.w3.org/1999/xlink', 'href') ||
        element.getAttribute('href');
    } else {
      img = document.adoptNode(element);
    }
    img.onerror = onIMGError;
    return {
      type: 'element',
      element: img
    };
  }
}
// Once we got everything, time to retrieve our objects
send_btn.onclick = function sendData() {
  var fD = new FormData();
  // send Files data directly
  var files = imgList.filter(function isFile(obj) {
    return obj.type === 'file';
  });
  files.forEach(function appendToFD(obj) {
    fD.append('files[]', obj.file);
  });
  // for elems, we will need to grab the data from the server
  var elems = imgList.filter(function isElem(obj) {
    return obj.type === "element";
  });
  var urls = elems.map(function grabURL(obj) {
    return obj.element.src;
  });
  if (urls.length)
    fD.append('urls', JSON.stringify(urls));

  sendFormData(fD);
};

function sendFormData(fD) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', 'your_url');
  // you would normally send it
  //xhr.send(fD);

  // but here we will just log the formData's content
  var files = fD.getAll('files[]');
  console.log('files: ', files);
  var urls = fD.get('urls');
  console.log('urls', urls);
}
// in case we can't load it
function onIMGError() {
  var img = this;
  var index = -1;
  imgList.forEach(function search(obj, i) {
    if (index < 0 && obj && obj.element === img)
      index = i;
  });
  // remove from our list
  if (index > -1) {
    imgList.splice(index, 1);
    if (img.parentNode) img.parentNode.removeChild(img);
  }
}
#dropzone {
  width: 300px;
  height: 150px;
  border: 1px solid black;
}

#dropzone.dragover {
  background: rgba(0, 0, 0, .5);
}

#res {
  border: 1px solid black;
}
<div id="dropzone">drop here</div>
<button id="send" disabled>log saved objects</button>
<div id="res">results:<br></div>

Also note that the default of a contenteditable container would be to display this <img>. I didn't wanted to integrate this case in this long enough example, but I'm sure you'll find a way to handle it if needed.

like image 158
Kaiido Avatar answered Sep 19 '22 12:09

Kaiido