45

I have done my best to find a simple, relevant, and up-to-date example that works for the latest version of Firefox and I'm really struggling.

Titles says it all really. I want the user to able to copy part of an image from an editor such as Windows Paint or use the Print Screen button and then paste that into a canvas element. Bonus points if the canvas resizes to fit exactly the pasted image (literally).

Want to avoid Flash or Java based solutions if reasonable.

I'm half-decent at Javascript but relatively inexperienced with the latest HTML5 features and totally new to the Canvas element. Please help!

DG.
  • 3,417
  • 2
  • 23
  • 28
  • [How to manage image pasting from clipboard in html5?](http://stackoverflow.com/questions/6219197/how-to-manage-image-pasting-from-clipboard-in-html5) – Andreas Aug 22 '13 at 10:34

3 Answers3

73

Version 2.0: Smaller, cleaner code works on Chrome, Firefox, Edge, Opera. No more hacks. But if you need support IE and Safari, check v1 version.

http://jsfiddle.net/viliusl/xq2aLj4b/5/


Version 1.0 Chrome implementation is simple. Firefox (and IE) has restrictions that user must give command to do paste like keyboard event and editable input must be focused, so we do tricks here - on ctrl down we focusthat input field, on release unfocus.

Browser support (image data):

  • Firefox
  • Chrome
  • Edge
  • IE-11
  • Opera

var CLIPBOARD = new CLIPBOARD_CLASS("my_canvas", true);

/**
 * image pasting into canvas
 * 
 * @param {string} canvas_id - canvas id
 * @param {boolean} autoresize - if canvas will be resized
 */
function CLIPBOARD_CLASS(canvas_id, autoresize) {
 var _self = this;
 var canvas = document.getElementById(canvas_id);
 var ctx = document.getElementById(canvas_id).getContext("2d");
 var ctrl_pressed = false;
 var command_pressed = false;
 var paste_event_support;
 var pasteCatcher;

 //handlers
 document.addEventListener('keydown', function (e) {
  _self.on_keyboard_action(e);
 }, false); //firefox fix
 document.addEventListener('keyup', function (e) {
  _self.on_keyboardup_action(e);
 }, false); //firefox fix
 document.addEventListener('paste', function (e) {
  _self.paste_auto(e);
 }, false); //official paste handler

 //constructor - we ignore security checks here
 this.init = function () {
  pasteCatcher = document.createElement("div");
  pasteCatcher.setAttribute("id", "paste_ff");
  pasteCatcher.setAttribute("contenteditable", "");
  pasteCatcher.style.cssText = 'opacity:0;position:fixed;top:0px;left:0px;width:10px;margin-left:-20px;';
  document.body.appendChild(pasteCatcher);

  // create an observer instance
  var observer = new MutationObserver(function(mutations) {
   mutations.forEach(function(mutation) {
    if (paste_event_support === true || ctrl_pressed == false || mutation.type != 'childList'){
     //we already got data in paste_auto()
     return true;
    }

    //if paste handle failed - capture pasted object manually
    if(mutation.addedNodes.length == 1) {
     if (mutation.addedNodes[0].src != undefined) {
      //image
      _self.paste_createImage(mutation.addedNodes[0].src);
     }
     //register cleanup after some time.
     setTimeout(function () {
      pasteCatcher.innerHTML = '';
     }, 20);
    }
   });
  });
  var target = document.getElementById('paste_ff');
  var config = { attributes: true, childList: true, characterData: true };
  observer.observe(target, config);
 }();
 //default paste action
 this.paste_auto = function (e) {
  paste_event_support = false;
  if(pasteCatcher != undefined){
   pasteCatcher.innerHTML = '';
  }
  if (e.clipboardData) {
   var items = e.clipboardData.items;
   if (items) {
    paste_event_support = true;
    //access data directly
    for (var i = 0; i < items.length; i++) {
     if (items[i].type.indexOf("image") !== -1) {
      //image
      var blob = items[i].getAsFile();
      var URLObj = window.URL || window.webkitURL;
      var source = URLObj.createObjectURL(blob);
      this.paste_createImage(source);
     }
    }
    e.preventDefault();
   }
   else {
    //wait for DOMSubtreeModified event
    //https://bugzilla.mozilla.org/show_bug.cgi?id=891247
   }
  }
 };
 //on keyboard press
 this.on_keyboard_action = function (event) {
  k = event.keyCode;
  //ctrl
  if (k == 17 || event.metaKey || event.ctrlKey) {
   if (ctrl_pressed == false)
    ctrl_pressed = true;
  }
  //v
  if (k == 86) {
   if (document.activeElement != undefined && document.activeElement.type == 'text') {
    //let user paste into some input
    return false;
   }

   if (ctrl_pressed == true && pasteCatcher != undefined){
    pasteCatcher.focus();
   }
  }
 };
 //on kaybord release
 this.on_keyboardup_action = function (event) {
  //ctrl
  if (event.ctrlKey == false && ctrl_pressed == true) {
   ctrl_pressed = false;
  }
  //command
  else if(event.metaKey == false && command_pressed == true){
   command_pressed = false;
   ctrl_pressed = false;
  }
 };
 //draw pasted image to canvas
 this.paste_createImage = function (source) {
  var pastedImage = new Image();
  pastedImage.onload = function () {
   if(autoresize == true){
    //resize
    canvas.width = pastedImage.width;
    canvas.height = pastedImage.height;
   }
   else{
    //clear canvas
    ctx.clearRect(0, 0, canvas.width, canvas.height);
   }
   ctx.drawImage(pastedImage, 0, 0);
  };
  pastedImage.src = source;
 };
}
1. Copy image data into clipboard or press Print Screen <br>
2. Press Ctrl+V (page/iframe must be focused):
<br /><br />
<canvas style="border:1px solid grey;" id="my_canvas" width="300" height="300"></canvas>
ViliusL
  • 4,589
  • 26
  • 28
  • Thanks! What a relief! – DG. Aug 23 '13 at 02:48
  • 4
    Is there any simpler solution in April 2014? without distinguishing browsers? – Ivan Kuckir Apr 04 '14 at 19:20
  • 3
    It should be simple: http://jsfiddle.net/KJW4E/222/ - for now chrome only. In the future there is chance that this example will work on all browsers. Sadly other browsers have some restrictions, that needs to be bypassed. – ViliusL Apr 29 '14 at 12:09
  • Ctrl + v is not working sometimes in firefox v-29 browser. If I resize the browser window then I am able to paste. (paste event is triggering). Is there any problem with firefox 29 browser with contenteditable or paste event with keyboard short cuts ? – Jak May 15 '14 at 20:13
  • @Jak, i was not able to reproduce your situation on firefox v29. Plugins, lots of html elements, or not valid code maybe? – ViliusL May 20 '14 at 12:42
  • So when I try both of your links and copy an image from Finder (OS X) to the box, it uploads the generic image preview icon instead of the image itself. Do you encounter this as well? – Lukas Oct 27 '15 at 17:38
  • @Lukas sometimes icon is pasted, becouse script gets icon data, instead of image data. Yes, this is stupid, but i was debuging this long ago, and could not find solution. – ViliusL Oct 28 '15 at 09:32
  • @ViliusL, thank you for your answer. At least I now know, that it is not caused by any local configuration here. – Lukas Oct 28 '15 at 14:36
  • @ViliusL This is absolutely perfect! – Hobbes Jan 04 '16 at 15:47
  • thank you very much, this really helps me alot =D note* adjust the canvas size before drawing it could be better? – Valen Feb 21 '16 at 15:51
  • @MayurPatel latest chrome works but only with image data, not actual image (image != image data), firefox works in both cases. – ViliusL Feb 25 '16 at 19:25
  • @ViliusL thanks for your code but what happened to Images??where there are saved? how can i save images after paste to the specific path in my website(ex:ww.site.com/images) – AminM May 17 '16 at 11:29
  • @AminM image data is saved in canvas element on user browser. Saving canvas to server is another topic. – ViliusL May 18 '16 at 07:57
  • @ViliusL but i wont save it in canvas element!,i have code that successfully past image in my editor but i want to save pasted image to my web server – AminM May 19 '16 at 05:03
  • @AminM retrieve the data uri of the image from the canvas, then create a form to do a POST with the data uri as payload, this is what I do – Valen Jun 15 '16 at 12:22
  • Can you explain the purpose of `if (window.Clipboard)` what does it mean to be "using auto" ? – Walter Stabosz Sep 19 '16 at 00:35
  • @WalterStabosz that is for future browsers, now condition is always false. "Auto" means turning off hacks and using standard way to get data. – ViliusL Sep 19 '16 at 11:24
  • (using 2.0) I experienced that this paste listener is in fact capturing ALL paste events, not just to the canvas. Since I had alot of other inputs in the page, I moved `e.preventDefault();` right under `this.paste_createImage(source);`, so only when an image is being pasted it will stop the pasting action. – Arie Nov 22 '18 at 19:56
  • I'm working on a version of this here: https://stackoverflow.com/questions/54182727/drag-and-drop-image-convert-to-base64/54184328#comment106993753_54184328 – Ryan Mar 14 '20 at 20:38
  • When copying a transparent image from photoshop (clipboard) and pasting it, it loses the transparency and instead shows a white background. Any solution for this? – Elron Sep 11 '21 at 02:56
  • @Elron You will not loose transparency. Transparent pixels will remain transparent, and you will see white color here, because document body is white. – ViliusL Sep 11 '21 at 17:35
  • @ViliusL Thanks for the answer, but even if the document background is red, the pasted data that was originally transparent, becomes white. – Elron Sep 11 '21 at 22:37
  • @Elron there is problem on your side, maybe check for canvas background color. I could not reproduce it on demo code. – ViliusL Sep 13 '21 at 09:00
7

ViliusL's answer is great, but for those looking for a simple cross-browser way to capture a pasted image:

window.addEventListener("paste", async function(e) {
  e.preventDefault();
  e.stopPropagation();
  let file = e.clipboardData.items[0].getAsFile();
  let objectUrl = URL.createObjectURL(file);
  // do something with url here
});

You'll probably want to do some error checking (like in ViliusL's answer), in case they paste something that's not an image. According to MDN, clipboardData works in all modern browsers. I've tested on Chrome and Firefox, and they work fine.

0

Here's a solution for modern browsers:

document.addEventListener('paste', async (ev: ClipboardEvent) => {
    if(!ev.clipboardData?.items?.length) return
    const {items} = ev.clipboardData
    for(const item of items) {
        if(!item.type.startsWith('image/')) continue
        const file = item.getAsFile()!
        const bitmap = await createImageBitmap(file)

        // ...

        context2d.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height)
    }
})

No need for object URLs. You can convert the clipboard image directly to a ImageBitmap and then draw it with drawImage.

mpen
  • 272,448
  • 266
  • 850
  • 1,236