国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > JavaScript > 正文

WEB前端實現裁剪上傳圖片功能

2019-11-20 08:44:08
字體:
來源:轉載
供稿:網友

最后的效果如下:

這里面有幾個功能,第一個是支持拖拽,第二個壓縮,第三個是裁剪編輯,第四個是上傳和上傳進度顯示,下面依次介紹每個功能的實現:

1. 拖拽顯示圖片

拖拽讀取的功能主要是要兼聽html5的drag事件,這個沒什么好說的,查查api就知道怎么做了,主要在于怎么讀取用戶拖過來的圖片并把它轉成base64以在本地顯示。

var handler = {init: function($container){//需要把dragover的默認行為禁掉,不然會跳頁$container.on("dragover", function(event){event.preventDefault();});$container.on("drop", function(event){event.preventDefault();//這里獲取拖過來的圖片文件,為一個File對象var file = event.originalEvent.dataTransfer.files[0];handler.handleDrop($(this), file);});}}varhandler={init:function($container){//需要把dragover的默認行為禁掉,不然會跳頁$container.on("dragover",function(event){event.preventDefault();});$container.on("drop",function(event){event.preventDefault();//這里獲取拖過來的圖片文件,為一個File對象varfile=event.originalEvent.dataTransfer.files[0];handler.handleDrop($(this),file);});}}

代碼第10行獲取圖片文件,然后傳給11行處理。

如果使用input,則監聽input的change事件:

$container.on("change", "input[type=file]", function(event){if(!this.value) return;var file = this.files[0];handler.handleDrop($(this).closest(".container"), file);this.value = "";});$container.on("change","input[type=file]",function(event){if(!this.value)return;varfile=this.files[0];handler.handleDrop($(this).closest(".container"),file);this.value="";});

代碼第3行,獲取File對象,同樣傳給handleDrop進行處理

接下來在handleDrop函數里,讀取file的內容,并把它轉成base64的格式:

handleDrop: function($container, file){var $img = $container.find("img");handler.readImgFile(file, $img, $container);},handleDrop:function($container,file){var$img= $container.find("img");handler.readImgFile(file,$img,$container);},

我的代碼里面又調了個readImgFile的函數,helper的函數比較多,主要是為了拆解大模塊和復用小模塊。

在readImgFile里面讀取圖片文件內容:

使用FileReader讀取文件 JavaScript

readImgFile: function(file, $img, $container){var reader = new FileReader(file);//檢驗用戶是否選則是圖片文件if(file.type.split("/")[0] !== "image"){util.toast("You should choose an image file");return;} reader.onload = function(event) {var base64 = event.target.result;handler.compressAndUpload($img, base64, file, $container);} reader.readAsDataURL(file);}readImgFile:function(file,$img,$container){varreader=newFileReader(file);//檢驗用戶是否選則是圖片文件if(file.type.split("/")[0]!=="image"){util.toast("You should choose an image file");return;} reader.onload=function(event){varbase64=event.target.result;handler.compressAndUpload($img,base64,file, $container);} reader.readAsDataURL(file);}

這里是通過FileReader讀取文件內容,調的是readAsDataURL,這個api能夠把二進制圖片內容轉成base64的格式,讀取完之后會觸發onload事件,在onload里面進行顯示和上傳:

//獲取圖片base64內容var base64 = event.target.result;//如果圖片大于1MB,將body置半透明if(file.size > ONE_MB){$("body").css("opacity", 0.5);}//因為這里圖片太大會被卡一下,整個頁面會不可操作$img.attr("src", baseUrl);//還原if(file.size > ONE_MB){$("body").css("opacity", 1);}//然后再調一個壓縮和上傳的函數handler.compressAndUpload($img, file, $container);//獲取圖片base64內容varbase64=event.target.result;//如果圖片大于1MB,將body置半透明if(file.size>ONE_MB){$("body").css("opacity",0.5);}//因為這里圖片太大會被卡一下,整個頁面會不可操作$img.attr("src",baseUrl);//還原if(file.size>ONE_MB){$("body").css("opacity",1);}//然后再調一個壓縮和上傳的函數handler.compressAndUpload($img,file,$container);

如果圖片有幾個Mb的,在上面第8行展示的時候被卡一下,筆者曾嘗試使用web worker多線程解決,但是由于多線程沒有window對象,更不能操作dom,所以不能很好地解決這個問題。采取了一個補償措施:通過把頁面變虛告訴用戶現在在處理之中,頁面不可操作,稍等一會

這里還會有一個問題,就是ios系統拍攝的照片,如果不是橫著拍的,展示出來的照片旋轉角度會有問題,如下一張豎著拍的照片,讀出來是這樣的:


即不管你怎么拍,ios實際存的圖片都是橫著放的,因此需要用戶自己手動去旋轉。旋轉的角度放在了exif的數據結構里面,把這個讀出來就知道它的旋轉角度了,用一個EXIF的庫讀?。?/p>

讀取exif的信息

readImgFile: function(file, $img, $container){EXIF.getData(file, function(){var orientation = this.exifdata.Orientation,rotateDeg = 0;//如果不是ios拍的照片或者是橫拍的,則不用處理,直接讀取if(typeof orientation === "undefined" || orientation === 1){ //原本的readImgFile,添加一個rotateDeg的參數handler.doReadImgFile(file, $img, $container, rotateDeg);} //否則用canvas旋轉一下else{rotateDeg = orientation === 6 ? 90*Math.PI/180 : orientation === 8 ? -90*Math.PI/180 :orientation === 3 ? 180*Math.PI/180 : 0;handler.doReadImgFile(file, $img, $container, rotateDeg);} });}readImgFile:function(file,$img,$container){EXIF.getData(file,function(){varorientation=this.exifdata.Orientation,rotateDeg=0;//如果不是ios拍的照片或者是橫拍的,則不用處理,直接讀取if(typeoforientation==="undefined"||orientation===1){//原本的readImgFile,添加一個rotateDeg的參數handler.doReadImgFile(file,$img,$container,rotateDeg);} //否則用canvas旋轉一下else{rotateDeg=orientation===6?90*Math.PI/180:orientation===8?-90*Math.PI/180:orientation===3?180*Math.PI/180:0;handler.doReadImgFile(file,$img,$container,rotateDeg);} });}

知道角度之后,就可以用canvas處理了,在下面的壓縮圖片進行說明,因為壓縮也要用到canvas

2. 壓縮圖片

壓縮圖片可以借助canvas,canvas可以很方便地實現壓縮,其原理是把一張圖片畫到一個小的畫布,然后再把這個畫布的內容導出base64,就能夠拿到一張被壓小的圖片了:

//設定圖片最大壓縮寬度為1500pxvar maxWidth = 1500;var resultImg = handler.compress($img[0], maxWidth, file.type);//設定圖片最大壓縮寬度為1500pxvarmaxWidth=1500;varresultImg=handler.compress($img[0],maxWidth,file.type);

compress函數進行壓縮,在這個函數里首先創建一個canvas對象,然后計算這個畫布的大?。?/p>

compress: function(img, maxWidth, mimeType){//創建一個canvas對象var cvs = document.createElement('canvas');var width = img.naturalWidth,height = img.naturalHeight,imgRatio = width / height;//如果圖片維度超過了給定的maxWidth 1500,//為了保持圖片寬高比,計算畫布的大小if(width > maxWidth){width = maxWidth;height = width / imgRatio;} cvs.width = width;cvs.height = height;}compress:function(img,maxWidth,mimeType){//創建一個canvas對象varcvs=document.createElement('canvas');varwidth=img.naturalWidth,height=img.naturalHeight,imgRatio=width/height;//如果圖片維度超過了給定的maxWidth 1500,//為了保持圖片寬高比,計算畫布的大小if(width>maxWidth){width=maxWidth;height=width/imgRatio;} cvs.width=width;cvs.height=height;}

接下來把大的圖片畫到一個小的畫布上,再導出:

//把大圖片畫到一個小畫布var ctx = cvs.getContext("2d").drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight, 0, 0, width, height);//圖片質量進行適當壓縮var quality = width >= 1500 ? 0.5 :width > 600 ? 0.6 : 1;//導出圖片為base64var newImageData = cvs.toDataURL(mimeType, quality);var resultImg = new Image();resultImg.src = newImageData;return resultImg;//把大圖片畫到一個小畫布varctx=cvs.getContext("2d").drawImage(img,0,0,img.naturalWidth,img.naturalHeight,0,0,width,height);//圖片質量進行適當壓縮varquality=width>=1500?0.5:width>600?0.6:1;//導出圖片為base64varnewImageData=cvs.toDataURL(mimeType,quality);varresultImg=newImage();resultImg.src=newImageData;returnresultImg;

最后一行返回了一個被壓縮過的小圖片,就可對這個圖片進行裁剪了。

在說明裁剪之前,由于第二點提到ios拍的照片需要旋轉一下,在壓縮的時候可以一起處理。也就是說,如果需要旋轉的話,那么畫在canvas上面就把它旋轉好了:

var ctx = cvs.getContext("2d");var destX = 0,destY = 0;if(rotateDeg){ctx.translate(cvs.width / 2, cvs.height / 2);ctx.rotate(rotateDeg);destX = -width / 2,destY = -height / 2;}ctx.drawImage(img, 0, 0, img.naturalWidth, img.naturalHeight, destX, destY, width, height);varctx=cvs.getContext("2d");vardestX=0,destY=0;if(rotateDeg){ctx.translate(cvs.width/2,cvs.height/2);ctx.rotate(rotateDeg);destX=-width/2,destY=-height/2;}ctx.drawImage(img,0,0,img.naturalWidth,img.naturalHeight,destX,destY,width,height);

這樣就解決了ios圖片旋轉的問題,得到一張旋轉和壓縮調節過的圖片之后,再用它進行裁剪和編輯

3. 裁剪圖片

裁剪圖片,上網找到了一個插件cropper,這個插件還是挺強大,支持裁剪、旋轉、翻轉,但是它并沒有對圖片真正的處理,只是記錄了用戶做了哪些變換,然后你自己再去處理??梢园炎儞Q的數據傳給后端,讓后端去處理。這里我們在前端處理,因為我們不用去兼容IE8。

如下,我把一張圖片,旋轉了一下,同時翻轉了一下:

它的輸出是:

{height: 319.2000000000001,rotate: 45,scaleX: -1,scaleY: 1,width: 319.2000000000001x: 193.2462838120872y: 193.2462838120872}{height:319.2000000000001,rotate:45,scaleX:-1,scaleY:1,width:319.2000000000001x:193.2462838120872y:193.2462838120872}

通過這些信息就知道了:圖片被左右翻轉了一下,同時順時針轉了45度,還知道裁剪選框的位置和大小。通過這些完整的信息就可以做一對一的處理。

在展示的時候,插件使用的是img標簽,設置它的css的transform屬性進行變換。真正的處理還是要借助canvas,這里分三步說明:

1. 假設用戶沒有進行旋轉和翻轉,只是選了簡單地選了下區域裁剪了一下,那就簡單很多。最簡單的辦法就是創建一個canvas,它的大小就是選框的大小,然后根據起點x、y和寬高把圖片相應的位置畫到這個畫布,再導出圖片就可以了。由于考慮到需要翻轉,所以用第二種方法,創建一個和圖片一樣大小的canvas,把圖片原封不動地畫上去,然后把選中區域的數據imageData存起來,重新設置畫布的大小為選中框的大小,再把imageData畫上去,最后再導出就可以了:

var cvs = document.createElement('canvas');var img = $img[0];var width = img.naturalWidth,height = img.naturalHeight;cvs.width = width;cvs.height = height; var ctx = cvs.getContext("2d");var destX = 0,destY = 0;ctx.drawImage(img, destX, destY);//把選中框里的圖片內容存起來var imageData = ctx.getImageData(cropOptions.x, cropOptions.y, cropOptions.width, cropOptions.height);cvs.width = cropOptions.width;cvs.height = cropOptions.height;//然后再畫上去ctx.putImageData(imageData, 0, 0);varcvs=document.createElement('canvas');varimg=$img[0];varwidth=img.naturalWidth,height=img.naturalHeight;cvs.width=width;cvs.height=height;varctx=cvs.getContext("2d");vardestX=0,destY=0;ctx.drawImage(img,destX,destY);//把選中框里的圖片內容存起來varimageData=ctx.getImageData(cropOptions.x,cropOptions.y,cropOptions.width,cropOptions.height);cvs.width=cropOptions.width;cvs.height=cropOptions.height;//然后再畫上去ctx.putImageData(imageData,0,0);

代碼14行,通過插件給的數據,保存選中區域的圖片數據,18行再把它畫上去

2. 如果用戶做了翻轉,用上面的結構很容易可以實現,只需要在第11行drawImage之前對畫布做一下翻轉變化:

canvas flip實現 JavaScript

//fipif(cropOptions.scaleX === -1 || cropOptions.scaleY === -1){destX = cropOptions.scaleX === -1 ? width * -1 : 0; // Set x position to -100% if flip horizontaldestY = cropOptions.scaleY === -1 ? height * -1 : 0; // Set y position to -100% if flip verticalctx.scale(cropOptions.scaleX, cropOptions.scaleY);}

ctx.drawImage(img, destX, destY);

//fipif(cropOptions.scaleX===-1||cropOptions.scaleY===-1){destX=cropOptions.scaleX===-1?width*-1:0; // Set x position to -100% if flip horizontaldestY=cropOptions.scaleY===-1?height*-1:0; // Set y position to -100% if flip verticalctx.scale(cropOptions.scaleX,cropOptions.scaleY);}

ctx.drawImage(img,destX,destY);

其它的都不用變,就可以實現上下左右翻轉了,難點在于既要翻轉又要旋轉

3. 兩種變換疊加沒辦法直接通過變化canvas的坐標,一次性drawImage上去。還是有兩種辦法,第一種是用imageData進行數學變換,計算一遍得到imageData里面,從第一行到最后一行每個像素新的rgba值是多少,然后再畫上去;第二種辦法,就是創建第二個canvas,第一個canvas作翻轉,把它的結果畫到第二個canvas,然后再旋轉,最后導到。由于第二種辦法相對比較簡單,我們采取第二種辦法:

同上,在第一個canvas畫完之后:

實現旋轉、翻轉結合 JavaScript

ctx.drawImage(img, destX, destY);//rotateif(cropOptions.rotate !== 0){var newCanvas = document.createElement("canvas"),deg = cropOptions.rotate / 180 * Math.PI;//旋轉之后,導致畫布變大,需要計算一下newCanvas.width = Math.abs(width * Math.cos(deg)) + Math.abs(height * Math.sin(deg));newCanvas.height = Math.abs(width * Math.sin(deg)) + Math.abs(height * Math.cos(deg));var newContext = newCanvas.getContext("2d");newContext.save();newContext.translate(newCanvas.width / 2, newCanvas.height / 2);newContext.rotate(deg);destX = -width / 2,destY = -height / 2;//將第一個canvas的內容在經旋轉后的坐標系畫上來newContext.drawImage(cvs, destX, destY);newContext.restore();ctx = newContext;cvs = newCanvas;}ctx.drawImage(img,destX,destY);//rotateif(cropOptions.rotate!==0){varnewCanvas=document.createElement("canvas"),deg=cropOptions.rotate/180*Math.PI;//旋轉之后,導致畫布變大,需要計算一下newCanvas.width=Math.abs(width*Math.cos(deg))+Math.abs(height*Math.sin(deg));newCanvas.height=Math.abs(width*Math.sin(deg))+Math.abs(height*Math.cos(deg));varnewContext=newCanvas.getContext("2d");newContext.save();newContext.translate(newCanvas.width/2,newCanvas.height/2);newContext.rotate(deg);destX=-width/2,destY=-height/2;//將第一個canvas的內容在經旋轉后的坐標系畫上來newContext.drawImage(cvs,destX,destY);newContext.restore();ctx=newContext;cvs=newCanvas;}

將第二步的代碼插入第一步,再將第三步的代碼插入第二步,就是一個完整的處理過程了。

最后再介紹下上傳

4. 文件上傳和上傳進度

文件上傳只能通過表單提交的形式,編碼方式為multipart/form-data,這個我在《三種上傳文件不刷新頁面的方法討論:iframe/FormData/FileReader》已做詳細討論,可以通過寫一個form標簽進行提交,但也可以模擬表單提交的格式,表單提交的格式在那篇文章已提及。

首先創建一個ajax請求:

JavaScript

var xhr = new XMLHttpRequest();xhr.open('POST', upload_url, true);var boundary = 'someboundary';xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);varxhr=newXMLHttpRequest();xhr.open('POST',upload_url,true);varboundary='someboundary';xhr.setRequestHeader('Content-Type','multipart/form-data; boundary='+boundary);

并設置編碼方式,然后拼表單格式的數據進行上傳:

ajax上傳 JavaScript

var data = img.src;data = data.replace('data:' + file.type + ';base64,', '');xhr.sendAsBinary([//name=data'--' + boundary,'Content-Disposition: form-data; name="data"; filename="' + file.name + '"','Content-Type: ' + file.type, '',atob(data), '--' + boundary,//name=docName'--' + boundary,'Content-Disposition: form-data; name="docName"', '',file.name,'--' + boundary + '--'].join('/r/n'));vardata=img.src;data=data.replace('data:'+file.type+';base64,','');xhr.sendAsBinary([//name=data'--'+boundary,'Content-Disposition: form-data; name="data"; filename="'+file.name+'"','Content-Type: '+file.type,'',atob(data),'--'+boundary,//name=docName'--'+boundary,'Content-Disposition: form-data; name="docName"','',file.name,'--'+boundary+'--'].join('/r/n'));

表單數據不同的字段是用boundary的隨機字符串分隔的。拼好之后用sendAsBinary發出去,在調這個函數之前先監聽下它的事件,包括

1) 上傳的進度:

上傳進度 JavaScript

xhr.upload.onprogress = function(event){if(event.lengthComputable) {duringCallback((event.loaded / event.total) * 100);}};xhr.upload.onprogress=function(event){if(event.lengthComputable){duringCallback((event.loaded/event.total)*100);}};

這里凋duringCallback的回調函數,給這個回調函數傳了當前進度的參數,用這個參數就可以設置進度條的過程了。進度條可以自己實現,或者直接上網找一個,隨便一搜就有了。

2) 成功和失?。?/p>

成功和失敗回調 JavaScript

xhr.onreadystatechange = function() {if (this.readyState == 4){if (this.status == 200) {successCallback(this.responseText);}else if (this.status >= 400) {if (errorCallback && errorCallback instanceof Function) {errorCallback(this.responseText);} } }};xhr.onreadystatechange=function(){if(this.readyState==4){if(this.status==200){successCallback(this.responseText);}elseif(this.status>=400){if(errorCallback&& errorCallback instanceofFunction){errorCallback(this.responseText);} } }};

這個上傳功能參考了一個JIC插件

至此整個功能就拆解說明完了,上面的代碼可以兼容到IE10,FileReader的api到IE10才兼容,問題應該不大,因為微軟都已經放棄了IE11以下的瀏覽器,為啥我們還要去兼容呢。

這個東西一來減少了后端的壓力,二來不用和后端來回交互,對用戶來說還是比較好的,除了上面說的一個地方會被卡一下之外。核心代碼已在上面說明,完整代碼和demo就不再放出來了。

以上所述是小編給大家介紹的WEB前端實現裁剪上傳圖片功能,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對武林網網站的支持!

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 肃北| 城固县| 凯里市| 武宁县| 郸城县| 饶平县| 孟连| 泰安市| 唐海县| 太原市| 永善县| 广昌县| 当涂县| 托克托县| 镇雄县| 八宿县| 揭东县| 垣曲县| 精河县| 永顺县| 彭水| 建阳市| 通河县| 郴州市| 甘洛县| 视频| 清河县| 玛纳斯县| 香河县| 宜城市| 平潭县| 五河县| 桓台县| 恭城| 崇仁县| 昌宁县| 聂荣县| 云林县| 方城县| 乌拉特前旗| 上饶县|