Search code examples
javascripthtmlimagecross-browserclipboard

Pasting directly to an HTML img element


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:


Solution

  • 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">