With Google Chrome and Internet Explorer 11 you can use the Clipboard API. I created a "Paster" class that you can pass a DOM object and a callback function. So when you paste an image into that object the file is uploaded to a PHP script and then the callback function is called with the response.
In this working example it will call the PHP page itself and then put the response in the textarea. It should work on any PHP enabled web server.
You can add an onpaste event to any DOM element in Google Chrome. Then the "Paster" loops through all clipboard items and checks if there's an image:
Paster.prototype.handlePaste = function(paster, e) { //don't do this twice if (paster.processing) { return; } //loop through all clipBoardData items and upload it if it's a file. for (var i = 0; i < e.clipboardData.items.length; i++) { var item = e.clipboardData.items[i]; if (item.kind === "file") { paster.processing = true; e.preventDefault(); paster.uploadFile(paster, item.getAsFile()); } } };
Then it uploads the file using an "XMLHttpRequest" call. When the upload is finished the callback function is called.
Paster.prototype.uploadFile = function(paster, file) { var xhr = new XMLHttpRequest(); //progress logging xhr.upload.onprogress = function(e) { var percentComplete = (e.loaded / e.total) * 100; console.log(percentComplete); }; //called when finished xhr.onload = function() { if (xhr.status === 200) { alert("Sucess! Upload completed. PHP response will be put in the textarea."); } else { alert("Error! Upload failed"); } paster.processing = false; }; //error handling xhr.onerror = function() { alert("Error! Upload failed. Can not connect to server."); }; //trigger a callback when it's successful xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { if (paster.callback) { paster.callback.call(paster.scope || paster, paster, xhr); } } }; //prompt for the filename var filename = prompt("Please enter the file name", "Pasted image"); if (filename) { //upload the file xhr.open("POST", "", { filename: filename, filetype: file.type }); var formData = new FormData(); formData.append("pastedFile", file); xhr.send(formData); } }
Firefox
So far it was pretty simple! But then I started testing this in Firefox. Unfortunately it didn't work because Firefox doesn't support files in the clipboard. I came up with a work around though.You can paste images in a content editable div in Firefox. The blob data is then available in the image source tag: <img src="data:image/png;base64,DATA" />. So in my workaround I create a hidden content editable div. When ever the textarea blurs, it will focus this hidden element. So when you click out of the textarea and then hit CTRL+V, the image is pasted in the hidden div. The paster then grabs that image source and creates a file blob object. This file can be uploaded just like in Google Chrome. Not as user friendly because you have to paste outside the textarea but it works:
Paster.prototype.init=function() { var paster = this; if (window.Clipboard) { //IE11, Chrome, Safari this.pasteEl.onpaste=function(e){ paster.handlePaste(paster, e); }; } else { //On Firefox use the contenteditable div hack this.canvas = document.createElement('canvas'); this.pasteCatcher = document.createElement("div"); this.pasteCatcher.setAttribute("id", "paste_ff"); this.pasteCatcher.setAttribute("contenteditable", ""); this.pasteCatcher.style.cssText = 'opacity:0;position:fixed;top:0px;left:0px;'; this.pasteCatcher.style.marginLeft = "-20px"; document.body.appendChild(this.pasteCatcher); this.pasteEl.onblur=function(e) { paster.pasteCatcher.focus(); }; this.pasteCatcher.onpaste=function(e) { paster.findImageEl(paster); }; } }; Paster.prototype.dataURItoBlob=function(dataURI, callback) { // convert base64 to raw binary data held in a string // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this console.log(dataURI); var byteString = atob(dataURI.split(',')[1]); // separate out the mime component var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] // write the bytes of the string to an ArrayBuffer var ab = new ArrayBuffer(byteString.length); var ia = new Uint8Array(ab); for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } // write the ArrayBuffer to a blob, and you're done return new Blob([ia], {type: mimeString}); }; Paster.prototype.findImageEl = function(paster) { if (paster.pasteCatcher.children.length > 0) { var dataURI = paster.pasteCatcher.firstElementChild.src; if (dataURI) { if (dataURI.indexOf('base64') === -1) { alert("Sorry, with Firefox you can only paste local screenshots and files. Use Chrome or IE11 if you need paster feature."); return; } var file = paster.dataURItoBlob(dataURI); paster.uploadFile(paster, file); } paster.pasteCatcher.innerHTML = ''; } else { setTimeout(function() { paster.findImageEl(paster); }, 100); } };
Complete example
I hope this is useful for you! It sure is useful for the Group-Office Content Management System and E-mail module! Here's the fully functional example that should work on any php server. Save it to pasteupload.php:<?php /** * * The MIT License (MIT) * * Copyright (c) 2014 Intermesh BV * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * */ if($_SERVER['REQUEST_METHOD']=='POST'){ var_dump($_FILES); exit(); } ?> <html> <head> <script type="text/javascript"> //Define paster object with contructor var Paster = function(config) { for(var key in config){ this[key]=config[key]; } this.init(); }; Paster.prototype.pasteEl=null; Paster.prototype.init=function() { var paster = this; if (window.Clipboard) { //IE11, Chrome, Safari this.pasteEl.onpaste=function(e){ paster.handlePaste(paster, e); }; } else { //On Firefox use the contenteditable div hack this.canvas = document.createElement('canvas'); this.pasteCatcher = document.createElement("div"); this.pasteCatcher.setAttribute("id", "paste_ff"); this.pasteCatcher.setAttribute("contenteditable", ""); this.pasteCatcher.style.cssText = 'opacity:0;position:fixed;top:0px;left:0px;'; this.pasteCatcher.style.marginLeft = "-20px"; document.body.appendChild(this.pasteCatcher); this.pasteEl.onblur=function(e) { paster.pasteCatcher.focus(); }; this.pasteCatcher.onpaste=function(e) { paster.findImageEl(paster); }; } }; Paster.prototype.dataURItoBlob=function(dataURI, callback) { // convert base64 to raw binary data held in a string // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this console.log(dataURI); var byteString = atob(dataURI.split(',')[1]); // separate out the mime component var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0] // write the bytes of the string to an ArrayBuffer var ab = new ArrayBuffer(byteString.length); var ia = new Uint8Array(ab); for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } // write the ArrayBuffer to a blob, and you're done return new Blob([ia], {type: mimeString}); }; Paster.prototype.findImageEl = function(paster) { if (paster.pasteCatcher.children.length > 0) { var dataURI = paster.pasteCatcher.firstElementChild.src; if (dataURI) { if (dataURI.indexOf('base64') === -1) { alert("Sorry, with Firefox you can only paste local screenshots and files. Use Chrome or IE11 if you need paster feature."); return; } var file = paster.dataURItoBlob(dataURI); paster.uploadFile(paster, file); } paster.pasteCatcher.innerHTML = ''; } else { setTimeout(function() { paster.findImageEl(paster); }, 100); } }; Paster.prototype.processing = false; //some wierd chrome bug makes the paste event fire twice when using javascript prompt for the filename Paster.prototype.handlePaste = function(paster, e) { //don't do this twice if (paster.processing) { return; } //loop through all clipBoardData items and upload it if it's a file. for (var i = 0; i < e.clipboardData.items.length; i++) { var item = e.clipboardData.items[i]; if (item.kind === "file") { paster.processing = true; e.preventDefault(); paster.uploadFile(paster, item.getAsFile()); } } }; Paster.prototype.uploadFile = function(paster, file) { var xhr = new XMLHttpRequest(); //progress logging xhr.upload.onprogress = function(e) { var percentComplete = (e.loaded / e.total) * 100; console.log(percentComplete); }; //called when finished xhr.onload = function() { if (xhr.status === 200) { alert("Sucess! Upload completed. PHP response will be put in the textarea."); } else { alert("Error! Upload failed"); } paster.processing = false; }; //error handling xhr.onerror = function() { alert("Error! Upload failed. Can not connect to server."); }; //trigger a callback when it's successful xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { if (paster.callback) { paster.callback.call(paster.scope || paster, paster, xhr); } } }; //prompt for the filename var filename = prompt("Please enter the file name", "Pasted image"); if (filename) { //upload the file xhr.open("POST", "<?php echo $_SERVER['PHP_SELF']; ?>", { filename: filename, filetype: file.type }); //send it as multipart/form-data var formData = new FormData(); formData.append("pastedFile", file); xhr.send(formData); } }; </script> </head> <body> <textarea id="text" style="width:500px;height:500px;"> On chrome paste a screenshot in here and it will be uploaded. On firefox first click outside the text area and paste the screenshot with CTRL+V </textarea> <script> //Create the paster object and connect it to the textarea. var paster = new Paster({ pasteEl:document.getElementById("text"), callback:function(paster, xhr){ //put the response in the textarea paster.pasteEl.value=xhr.responseText; } }); //focus the textarea paster.pasteEl.focus(); </script> </body> </html>