好久沒有編程了,最近需要完成一個小功能,為了方便,需要粘貼圖片后上傳到后台。前台編輯器用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. 我的性子是不求甚解,能拼湊就拼湊,能猜就猜。。。。反正搗鼓出來就行了,什么代碼優化之類的與我無緣。 只是我用了好多關鍵字搜索,但是搜出來的解決方案都有問題,所以我才寫了這篇文章,希望也能幫助看文章的你節省寶貴的時間。