近来需要完成一个在文本编辑框中直接粘贴截图的功能。但是发现现有的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是什么?
现在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
})
不让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的一些东西太强大了,从这次功能上的实现上,学到了好多新的东西。