1

Re:question. Given that the following only applies to Firefox, is there a cross-browser solution? or is the Clipboard API too new (2020).

Original question:


In playing with the Clipboard API in Firefox. I was able to copy an image and paste it into a canvas.

const canvas = document.createElement("canvas");
canvas.setAttribute("contenteditable", "true")
document.body.appendChild(canvas);
canvas.style = "border:solid black 1px";
canvas.addEventListener("paste", element_paste(canvas, "canvas"));

function element_paste(element, type) {
  switch (type) {
    case "image":
      return function(e) {
        element.src = image_data_transfer(e.clipboardData || window.clipboardData);
      }
    case "canvas":
      return function(e) {
        const im = new Image();
        im.src = image_data_transfer(e.clipboardData || window.clipboardData);
        im.onload = () => {
          element.width = im.width;
          element.height = im.height;
          element.getContext("2d").drawImage(im, 0, 0);
          URL.revokeObjectURL(im.src);
          delete im;
        };
      }
    default:
      return function(e) {
        var p = (e.clipboardData || window.clipboardData).getData("text");
        var t = document.createTextNode(p);
        element.appendChild(t);
      }
  }
}

function image_data_transfer(e) {
  const p = e.items;
  try {
    return URL
      .createObjectURL(Array
        .apply(null, Array(p.length))
        .map((_, i) => p[i])
        .find(e => e.kind == "file" && /image/.test(e.type))
        .getAsFile()
      );
  } catch (e) {
    console.log(e);
    return "";
  }
}

This works exactly as expected for canvas. If canvas is changed to an img however; using "image" as the type in element_paste which would work if the html element would allow pasting to it.

I'm aware that I could "easily" overly a canvas over the image and it works exactly as expected. My issue with settling for that is ((I'm juggling another object only to serve as a workaround, I might as well use the canvas to save DOM mess.) (it's ugly.))

Is there any flag experimental or otherwise that would allow pasting directly to an img?


const img = document.createElement("img");
const image_paste = element_paste(img,"image");

document.body.appendChild(img);
img.setAttribute("contenteditable","true");
img.addEventListener("paste",image_paste);

img.style="border:solid black 1px;min-width:100px;min-height:100px";

The above is what I would like to work; below is a hack that does.


const canvas = document.createElement("canvas");
const img = document.createElement("img");
const image_paste = element_paste(img,"image");

document.body.appendChild(img);
document.body.appendChild(canvas);
canvas.setAttribute("contenteditable","true");
canvas.addEventListener("paste",image_paste);

canvas.style="border:solid black 1px;";
img.style="border:solid black 1px;min-width:100px;min-height:100px";

If there's a way for this to work without any script, even better!

sources:

haelmic
  • 541
  • 4
  • 18
  • Seems like canvases have a `toDataURL()` (https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL), which is very easy to then set as the image url, since it's already formatted for you. Having an off-screen / hidden canvas to handle the paste event before converting to the data URL may still be the easiest option, even if its "more work". There may be a way to format the clipboard data as a dataURL and skip the canvas altogether though. – mix3d Jun 19 '20 at 20:55
  • the canvas in the final example canvas is only required as an entry point for the paste event as I can't find a way to allow the img to accept one. Otherwise the img is perfectly capable – haelmic Jun 19 '20 at 21:03
  • https://ourcodeworld.com/articles/read/491/how-to-retrieve-images-from-the-clipboard-with-javascript-in-the-browser This seems to get the image data as a blob or base64 and set it directly to the image element. To get the base64, though, it IS still using a canvas first. If you're struggling with where to put the paste event handler though, you can always put it further up the DOM chain, even on the document `body` itself. – mix3d Jun 19 '20 at 21:06
  • as you say, attaching the paste to body would work... It is a solution, although I don't know if it's any cleaner then using a canvas overlay; atleast for multiple objects, as the currently selected object has to be provided. and I think you might lose rightclick paste which would destroy that particular type of usability. – haelmic Jun 19 '20 at 21:24
  • Even with body, you still have the event.target which would help you determine which image to apply the data to. You wouldn't need multiple overlays for each image in that case. – mix3d Jun 19 '20 at 21:27
  • My expectation of the attribute `contenteditable` is that the element is editable. This appears to have what I would call the correct behavior with canvas. it doesn't seem to be so of the img element. Adding the `contenteditable` tag to the image it doesn't seem to make it editable, and doesn't appear to know how to handle pasteing. In spite of this super unfortunate behavior, my hope is there would be some html tag nesting or webkit/firefox flag that would allow such behavior. I have yet to find one. – haelmic Jun 19 '20 at 22:07
  • Interestingly though, if you have a `
    `, pasting an image WILL paste an image object into the DOM structure, at least in Chrome.
    – mix3d Jun 19 '20 at 22:34
  • I didn't realize my snip-it only works in firefox. Which is even less fortunate. – haelmic Jun 20 '20 at 01:01

1 Answers1

2

This takes a couple different ideas and puts them together: Your clipboard data filter (modified slightly), and uses the FileReader class to turn it into a dataurl, which can be applied to an img object.

Interestingly though... it does not seem to work when the image object itself is selected.

const image = document.getElementById('img')
document.addEventListener('paste', convertToImage)

function convertToImage(e) {
  var blob = image_data_transfer(e.clipboardData)
  if (blob !== null) {
    var reader = new FileReader();
    reader.onload = function(event) {
      console.log(event.target.result);
      image.src = event.target.result
      alert('pasted!')
    }; // data url!
    reader.readAsDataURL(blob);
  } else {
    console.log("couldn't read image data from clipboard")
  }


}

function image_data_transfer(clipboardData) {
  const p = clipboardData.items;
  try {
    return Array
      .apply(null, Array(p.length))
      .map((_, i) => p[i])
      .find(e => e.kind == "file" && /image/.test(e.type))
      .getAsFile()
    // URL.createObjectURL();
  } catch (e) {
    console.log(e);
    return null;
  }
}
img{
  width: 300px;
  height: 300px;
  border: 1px solid;
  background: #ccc;
}
<div>
  Paste here!
</div>

<img id="img" src="" onpaste="convertToImage">
mix3d
  • 4,122
  • 2
  • 25
  • 47
  • Although, because image tags don't seem to be able to get `focus`, you may want to consider using a div and `.style.backgroundImage = "url('theDataURL')"` property. – mix3d Jun 19 '20 at 21:50
  • I'm having trouble getting an image to paste into your current snip-it. Perhaps something was left out by mistake? – haelmic Jun 20 '20 at 00:49
  • It worked for me in chrome, but not when the image itself was focused, even though the event still fired and you technically can't focus images. – mix3d Jun 20 '20 at 01:26
  • I can try to put the listener on document – mix3d Jun 20 '20 at 01:27
  • Moved listener to document, works in both FF and Chrome. Give it a try, @haelmic – mix3d Jun 30 '20 at 20:59