Clipboard的使用和CKEditor在编辑框粘贴截图并上传(Java版)

近来需要完成一个在文本编辑框中直接粘贴截图的功能。但是发现现有的CKEditor并不能做到。但是发现知乎,还有CSDN本身,都能直接在编辑框中直接Ctrl+V将图片粘贴上。所以,有实现的地方,就有相应的技术。后面再回去看新浪微博,支持得刚好,Ctrl+V还会弹个框出来。这次结合了好多网友的回答和文章,结合之后完成了自己想要的功能。


粘贴图片到文本框雏形。

首先是在知乎上受到Clipboard的启发。知乎回答问题编辑框用 Ctrl+V 粘贴图片是如何实现的?

发现了大体的思路。参照其中的一个回答,修改之后再写一个demo。是简单的textarea实现粘贴截图还有html的功能。

<html>
<head>
	<title>test chrome paste image</title>
<style>
	DIV#editable {
		width: 400px;
		height: 300px;
		border: 1px dashed blue;
	}
</style>
<script type="text/javascript">

window.onload=function() {
	function paste_img(e) {
		if ( e.clipboardData.items ) {
			ele = e.clipboardData.items
			for (var i = 0; i < ele.length; ++i) {
				//输出元素的位置,种类,
				console.info(i+ele[i].kind+ele[i].type);
				if ( ele[i].kind == 'file' && ele[i].type.indexOf('image/') !== -1 ) {
					//返回一个文件对象
					var blob = ele[i].getAsFile();

					//这两个查不出区别
					window.URL = window.URL || window.webkitURL;

					//创建一个代表对象的url,类似<img src="blob:null/62c75a84-ae51-41d2-a6f6-983446bc23d0">
					var blobUrl = window.URL.createObjectURL(blob);
					console.log(blob);
					var new_img= document.createElement('img');
					new_img.setAttribute('src', blobUrl);
					document.getElementById('editable').appendChild(new_img);				
				}
				
				//为文字时。文字时还区分文本和hmtl,返回字符串getAsString 的时候,需要一个回调函数。
				if ( ele[i].kind == 'string' && ele[i].type.indexOf('plain') != -1 && ele.length<2) {
					ele[i].getAsString(function(s) {
						console.info(s);
						var temp = document.getElementById('editable').innerHTML;
						document.getElementById('editable').innerHTML=temp+s;
                    });
				}
				if ( ele[i].kind == 'string' && ele[i].type.indexOf('html') != -1   ) {
					ele[i].getAsString(function(s) {
						console.info(s);
						var temp = document.getElementById('editable').innerHTML;
						document.getElementById('editable').innerHTML=temp+s;
                    });
				}

			}
		} else {
			alert('non-chrome');
		}
	}
	document.getElementById('editable').onpaste=function(){paste_img(event);return false;};
}

function paste(){
	document.getElementById('editable').paste;
}

</script>
</head>
<body >
	<h2>test image paste in browser</h2>
	<div id="non-editable"  style="width:250px; height:100px; border:none; overflow:hidden;" >
		<p>copy the following img, then paste it into the area below</p>
	</div>
	<div id="editable" contenteditable="true" style="width:250px; height:100px;  overflow:auto;">
	
	</div>
</body>
</html>

getAsString方法可以参照 Clipboard object, is there a way to capture pasted text?


用Java后台接收参数并保存图片到本地

这边的原理好多东西自己查找大量资料,发现主要是webapi这块很薄弱。

1、如何将文件发送请求处理。

这里通过ajax请求。不过首先,我们要先了解一下。FormData是什么?

扫描二维码关注公众号,回复: 2445182 查看本文章

现在blog为getAsFile方法返回,为File对象。


FormData介绍:

https://developer.mozilla.org/zh-CN/docs/Web/API/FormData


普通的Ajax请求代码,这是一篇博客里面写的,具体出处忘了开了太多东西了:

var createStandardXHR = function () {
    try {
        return new window.XMLHttpRequest();
    } catch (e) {
        return false;
    }
};
var createActiveXHR = function () {
    try {
        return new window.ActiveXObject("Microsoft.XMLHTTP");
    } catch (e) {
        return false;
    }
};

var xhr;

function createXHR() {
    var temp = createStandardXHR() || createActiveXHR();
    if (window.XDomainRequest === undefined) {
        return temp;
    } else {
        return new XDomainRequest();
    }
}

function uploadImg(obj) {
    xhr = createXHR();
    var fd = new FormData();
    fd.append("image", obj, "test.png");
    xhr.open('POST', 'xx.action', true);
    xhr.send(fd);
}

上面的例子页面,加上uploadImg(blob);即可。


再来看看FormData的方法介绍:

append()  给当前FormData对象添加一个键/值对.
void append(DOMString name, Blob value, optional DOMString filename);
void append(DOMString name, DOMString value);
参数值
name 字段名称.
value 字段值.可以是,或者一个字符串,如果全都不是,则该值会被自动转换成字符串.
filename
(可选) 指定文件的文件名,当value参数被指定为一个Blob对象或者一个File对象时,该文件名会被发送到服务器上,对于Blob对象来说,这个值默认为"blob"。


上述普通的ajax方法比较复杂,引入jQuery的js文件,使用封装方法就简单很多了。

 var fd = new FormData();
 fd.append("image", blob,"a.png");
 $.ajax({
  type: 'POST',
  url: 'xx.action',
  data: fd,
    processData: false,
    contentType: false
 })


最后两个参数一定要设: https://developer.mozilla.org/zh-CN/docs/Web/Guide/Using_FormData_Objects
不让jquery处理数据和修改请求头。不然直接普通的ajax请求,会报Uncaught TypeError: Illegal invocation 错误。


2、后台接收,通过Struts2来获取请求的参数。

public String upload() throws Exception {
	ActionContext context=ActionContext.getContext();    
    Map parameterMap=context.getParameters(); 
    Set s = parameterMap.keySet();
    for (Object object : s) {
		System.out.println(object);
		System.out.println(parameterMap.get(object));
	}
    
    String filename =((String[])parameterMap.get("imageFileName"))[0] ;
    System.out.println(((String[]) parameterMap.get("imageContentType"))[0]);
    System.out.println(filename);
    File f = ((File[]) parameterMap.get("image"))[0];
    BufferedImage image = ImageIO.read(f);
    ImageIO.write(image, "png", new File("e:\\test.png"));
    return "ok";
}


输出
image
[Ljava.io.File;@3b382278
imageContentType
[Ljava.lang.String;@625f12a7
imageFileName
[Ljava.lang.String;@4fc1c465
image/png
a.png

这里文件都获取到了,具体想保存在服务器还是本地,接下来就好办多了。之前是卡在了这步上面。

这里有东西是需要注意的,如果File f = ((File) parameterMap.get("image")); 这样的强制转换是异常的。

原因是类型为Ljava.io.File,而不是java.io.File。


区别是什么呢?

[Ljava.io.File;是 File[].class的名字,java.io.File表示的File.class。所以需要类型转换是将其转换File数组对象。


CKEditor添加该功能

到CKEditor添加该功能的时候,我就被卡死了一次。

CKEditor相当于一个内嵌iframe,然后上面的document.getElementById('editable').innerHTML这种方式获取修改内容就做不到了。


多亏看了这位仁兄的做法:Ckeditor and ckfinder 配置实现截图上传图片到远程服务器

然后成功地让CKEditor能够顺利的粘贴到截图,后续发现同事用的CKEditor,只要配置一下东西就有这个功能了。囧。


不过刚开始整个原理没有弄清楚。现在终于把这些FileReader等对象的方法给理顺了。


//instances是保存editor实例的引用
var editor = CKEDITOR.instances["Editor"];
//如果存在,则销毁。
if (editor2) {editor2.destroy(true)};
//用 CKEditor实例替换<textarea>或者<div>
editor=CKEDITOR.replace('Editor',{width:'850',height:'250',resizable:false});
//instanceReady监听CKEditor实例被创建并完全初始化
CKEDITOR.instances["Editor"].on("instanceReady", function () {    
//监听CKEditor里面的document对象粘贴事件,这样就没有前面那个问题了。
         this.document.on("paste", Paste);     
});  


function Paste(e){  
   var items = e.data.$.clipboardData.items;  
   for (var i = 0; i < items.length; ++i) {  
       var item = e.data.$.clipboardData.items[i];          
       if (items[i].kind == 'file' && items[i].type == 'image/png') {               
		//FileReader可以读取文件内容
		    var fileReader = new FileReader();  	 			  
	       //readAsDataURL是一个异步过程 这里自己捣鼓了好久 终于弄懂了 其实这个东西 可以放在上面
		   //readAsDataURL result以base64编码用Data URL的形式保存了File数据  所以 这句是必须的 //不然下面的result是没有的	因为我们是要有img src+Data URL 形式读取出来 所以选用这种形式	

		   //同理,换成readAsText方法,是以text形式存储到result 那么你看到的只会是一串乱码
	       fileReader.readAsDataURL(item.getAsFile());  

		   //读取完成触发
	        fileReader.onloadend = function () {  
	           //var d = this.result.substr( this.result.indexOf(',')+1);  
	           //往ckeditor中插入图片,base64编码过的,这里我优化了一下,本身this.result存储了 readAsDataURL方法后
	           //保存的内容
	           //格式为 data:image/png;base64,iVBORw0KGgoAAAA  所以插入直接插入result就可以了。
	           CKEDITOR.instances.Editor.insertHtml("<img src='"+this.result+"'>");   
	       };  

	       //uploadImg(blob);
       }  
	 }  
}

后面上传图片的就一样了。

不过终于把整个给搞清楚了。

整个HTML的一些东西太强大了,从这次功能上的实现上,学到了好多新的东西。

还有就是别人的实现是实现到了,但是背后的原理什么的,自己要主动去摸索弄懂。

猜你喜欢

转载自blog.csdn.net/iaiti/article/details/52459175