好久没有编程了,最近需要完成一个小功能,为了方便,需要粘贴图片后上传到后台。前台编辑器用tinymce(N年前用过,我也就知道这个编辑器而已。这次使用下来感觉文档更丰富了),后台我用的Flask。昨天从下午4点开始一直捣鼓到半夜2点,终于完成了,这里大致记录一下遇到的问题和解决的办法。
1. 使用的tinymce版本是4.7.4,稍微熟悉了一下,advlist 插件不能用。 粘贴图片主要用的是自带的paste插件。
tinymce.init({ selector:'#mycontent', menubar:false, plugins: [ 'code paste', ], toolbar:'code', height:400, paste_data_images: true });
2. 上面的代码已经能成功粘贴图片,并且显示在编辑框里面了。我查看了源代码,图片的src是这样的
<img src="blob:http://127.0.0.1:5000/e8b7743b-6637-45f5-8473-9cf5271cc841">
3. 我懵逼了,这是什么玩意儿,但是似乎听说过Blob这个东西。我又想了下我的思路,我是要把这个图像上传到后台,然后把后台的地址返回给客户端,然后把这个src给替换掉。 因此我的问题就是把这个图片读出来,然后上传。一开始我网上找到不少解决方法,可以把blob读出来,主要是用了HTML5的canvas 对象。用context 画图,然后输出DataURL,转化为Base64。 这一段耗费了我大量的时间。 下面的代码是有问题的,贴出来仅仅是为了表达完整的过程。
var Img = new Image(); Img.src=url;
width = Img.width; // 实际上是拿不到长宽的。
height = Img.height; var canvas = document.createElement("canvas"); canvas.getContext("2d").drawImage(Img,0,0, width, height); dataURL=canvas.toDataURL('image/jpeg');
大致的核心代码如上。rul: 就是上面 blob:http://.... 这一段。关键在于drawImage这个函数上,我为了看看这个画布上画出来的图像是否跟我粘贴的图像是一样的,因此在页面下部让这个画布显示出来,我遇到了几个问题
1. 第一次粘贴,画布无法显示图像,第二次粘贴后,画布会一次显示2个图像,而且错误 -__-!
2. 画布大小和图像大小不匹配。 打印了width和height 之后发现都是0。原来blob图像无法获得长宽。
3. 我粘贴的图像比较大,1800×3600以上,Base64太长了,传到后台,保存成图像之后,数据丢失,出来的是一片白色。
因此这条路基本上是走不通了。
4. 解决方法:换成纯二进制数据进行操作,不再转换。
直接看下面的代码吧,写的很粗,因为我不是很懂javascript。基本上全是复制粘贴过来的。
<script> globalcounter = 1; tinymce.init({ selector:'#mycontent', menubar:false, plugins: [ 'code paste', ], toolbar:'code', height:400, paste_data_images: true, paste_preprocess: function(plugin, args) { args.content = args.content.replace("<img", "<img id=\"pasted_image_" + parseInt(globalcounter) + "\""); console.log(args.content) var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if (this.readyState == 4 && this.status == 200){ upload(this.response); } }; xhr.open('GET', args.content.split('"')[3]); xhr.responseType = 'blob'; xhr.send(); function upload(BlobFile){ var x = new XMLHttpRequest(); x.onreadystatechange = function(){ if( this.readyState == 4 && this.status == 200 ){ data = this.responseText; console.log('response data: ' + data); id = parseInt(globalcounter++); // function setimg(id, data){ // if( document.getElementById("pasted_image_" + id) == null){ // setTimeout( setimg , 5000); // }else{ // document.getElementById("pasted_image_" + id).setAttribute("src", data); // } // } document.getElementById("mycontent_ifr").contentWindow.document.getElementById("pasted_image_" + id).setAttribute("src", data); } }; x.open('POST', '/pasteimg/'); x.send(BlobFile); } } }); </script>
这里是后台的python代码,框架用的是Flask
@app.route('/pasteimg/', methods=['GET','POST']) def paste_upload(): if request.method == 'POST': imgdata = request.get_data() file = open('test.png', 'wb') file.write(imgdata) file.close() imgsrc = "/static/img/60_1.png" return Response( imgsrc, mimetype='application/text')
上面的代码有几个我遇到的问题,解决了。也有一些问题,我只是绕过了,但是没有真正解决;:
1. 用XMLHttpRequest 可以直接把img 读出来成为一段二进制数据 -- blob。而且没有大小限制,我贴的图片有时候size超过7位数。。也能够顺利上传到后台,相比之下,我觉得比Base64一串常常的字符串好多了。
2. tinymce 的paste_postprocess 中无法给插入对象设置id,这样我之后就没有办法获取图片了,因此,我只能在preprocess中设置了id,由于要插入多张图片,因此我用一个全局的计数器来递增id号。以后如果有多个textarea,那么还要再想办法。
3. jquery 的ajax方法不能传二进制,因此用XMLHttpRequest.
4. XMLHttpRequest 不能设置同步操作,设置了之后contenttype就不能设置blob,否则出错。我没时间去处理了,就用异步了。
5. 由于服务器端我命名规则还没有订好,因此我只是返回固定的一个图片,这样只要在前端能够顺利显示,说明整个路子都走通了。
6. 获取图片对象费了老大劲,原来tinymce自己是一个iframe,因此要先找到iframe,然后再找到里面的图片。 在chrome里面我直接用getElementById(”图片的id“)却始终找不到,奇怪。 后来我到Firefox中就能找到了。
7. 当服务器端把图像返回回来时,如果paste事件还没有完成,那么DOM对象还没有生成的话,图片对象是找不到的。虽然这种可能性很小,所以我设置了timeout,找不到就一直给我找,直到找到位置。当然也可以用一个wait 的gif,类似 博客园这么干。
8. 我的性子是不求甚解,能拼凑就拼凑,能猜就猜。。。。反正捣鼓出来就行了,什么代码优化之类的与我无缘。 只是我用了好多关键字搜索,但是搜出来的解决方案都有问题,所以我才写了这篇文章,希望也能帮助看文章的你节省宝贵的时间。