實戰:圖片上傳組件開發
寫在前面
圖片上傳,作為web端一個常用的功能,在不同的項目中有不同的需求,在這裡實現一個比價基本的上傳圖片插件,主要能實現圖片的瀏覽,剪裁,上傳這三個功能,同時也是為了讓自己對圖片/文件上傳和HTML5中名聲在外的 相關能夠有一些了解
我就要自行車 – 需求整理
放眼WWW,一般的圖片上傳模塊,主要就是實現了三個功能,圖片的預覽,圖片的剪裁及預覽,圖片的上傳,那我也就整這麼一個吧,再細化一下需求
圖片的預覽
用戶使用:用戶點擊「選擇圖片」,彈出文件瀏覽器,可以選擇本地的圖片,點擊確認後,所選圖片會按照原始比例出現在頁面的瀏覽區域中
組件調用:開發者可以自己定義圖片預覽區域的大小,並限定所傳圖片的文件大小和尺寸大小
圖片的剪裁
用戶使用:用戶根據提示,在預覽區域的圖片上拖動滑鼠框出想要上傳的圖片區域,並且能在結果預覽區域看到自己的剪裁結果
組件調用:開發者可以自定義是否剪裁圖片,並可以定義是否限定剪裁圖片的大小及比例,並且設定具體大小及比例
圖片的上傳
組件調用:開發者得到base64格式的urlData圖片,自己編寫調用Ajax的函數及其回調函數
扔出原型圖
作為設計師,扔圖是我的最愛,畫了一套全功能,包含剪裁及剪裁瀏覽的原型圖
state-1:初始狀態
state-2:點擊」選擇圖片」,瀏覽本地後載入圖片
state-3:剪裁,在圖片區域上拖動滑鼠選擇要剪裁的部分,確認要上傳的部分
一次歷史性的對話 – 本地圖片讀取
自打幹上web開發這活,就都是在搗鼓瀏覽器內部這點事,從沒想過跟瀏覽器之外計算機本地的一些文件能發生什麼關係。但是該來的總要來,既然要上傳圖片,就肯定要從計算機本地來選擇文件並在瀏覽器內打開,這歷史性的對話就要這麼開啟了…
圖片的選擇
其實在HTML中的 標籤就提供了瀏覽本地文件的功能,前提是 ,真是很講道理… 試過就知道一點擊就會打開文件瀏覽器
但這麼做有兩個經典的問題:
第一,會有一個輸入框傻乎乎的在那裡…
第二,我用的是Ajax,怎麼才能get到表單當中的文件呢
對於問題一,很好解決直接各種方式hide這個input標籤即可,再主動觸發
varimgFrom =
document.getElementById("inputArea");
functionloadImg(){ imgFrom.click(); }
對於問題二,這就要介紹一下 對象了
XMLHttpRequest Level 2添加了一個新的介面FormData.利用FormData對象,我們可以通過JavaScript用一些鍵值對來模擬一系列表單控制項,我們還可以使用XMLHttpRequest的send()方法來非同步的提交這個」表單」. 比起普通的ajax,使用FormData的最大優點就是我們可以非同步上傳一個二進位文件.
摘自 MDN Web docs – Web技術文檔/Web API 介面/FormData
正如上面的文檔所說 對象可以乾的事無非就是用javascript模擬表單控制項,也正因為如此所以可以在模擬的表單中放入一個文件
varmyFrom =newFormData();
varimageData = imgFrom.files[];
//獲取表單中第一個文件
myFrom.append("image",imageDate);
//向表單中添加一個鍵值對
console.log(myFrom.getAll("image"));
//獲取表單中image欄位對應的值,結果見下圖
正如我們所見,文件我們已經通過Web拿到手了
圖片的展現
既然是要上傳圖片,我們肯定得知道自己傳的是啥圖片啊,所以下一步就是如何把讀取的圖片展現在頁面上了,正如上圖中的顯示,我的得到的圖片是一個 對象,而 對象是特殊的 對象,那 對象又是個啥呢…
Blob 對象表示不可變的類似文件對象的原始數據。Blob表示不一定是JavaScript原生形式的數據。File 介面基於Blob,繼承了 blob的功能並將其擴展使其支持用戶系統上的文件。
摘自 MDN Web docs – Web技術文檔/Web API 介面/Blob
說實話,真是懵逼
但仔細理解下大概意思就是 對象是用來表示/承載文件對象的原始數據(二進位)的,藉助一些博文會有助於理解
js中關於Blob對象的介紹與使用 – 可樂Script
HTML5 Blob對象 – zdy0_2004
說到底,重點不在這,了解一下有個概念即可,重點在於我們怎麼展示這個 對象
這就要請出 對象了
FileReader 對象允許Web應用程序非同步讀取存儲在用戶計算機上的文件(或原始數據緩衝區)的內容,使用 File 或 Blob 對象指定要讀取的文件或數據。
摘自 MDN Web docs – Web技術文檔/Web API 介面/FileReader
不難看出, 對象就是用來讀取本地文件的,而這其方法 就是我們要用的東西啦
該方法會讀取指定的 Blob 或 File 對象。讀取操作完成的時候,readyState 會變成已完成(DONE),並觸發 loadend 事件,同時 result 屬性將包含一個data:URL格式的字元串(base64編碼)以表示所讀取文件的內容。
摘自 MDN Web docs – Web技術文檔/Web API 介面/FileReader/FileReader.readAsDataURL()
這裡面又提到一個新名詞data:URL,也就是說 的作用就是能把文件轉換為data:URL,不過這個data:URL又是什麼呢,執行來看看
varreader =newFileReader();//調用FileReader對象
reader.readAsDataURL(imgData);//通過DataURL的方式返回圖像
reader.onload =function(e){console.log(e.target.result);//看看你是個啥
}
控制台的結果全臉懵逼
可以通過這篇文章去大概了解一下 DATA URL簡介及DATA URL的利弊 – 薛陳磊
說到底這dataURL我就粗略的理解它為URL形式的data,也就是說這段URL並不是與普通的URL一樣指向某個地址,而是它本身就是數據,我們試著把這一堆字元粘到一個 的 屬性中
終於看到了,結果正如所料,將這段包含了數據的URL賦給一個 確實可以讓數據被展現為圖片
至此,我們實現了本地文件的讀取及展現
指哪兒截哪兒 – 利用canvas的圖片截取
溫馨提示-亂入:看明白這裡需要對canvas有基本的了解 MDN Web docs – Web技術文檔/Web API介面/Canvas/Canvas教程
在Web上對圖像進行操作,沒有比canvas相關技術更合適的了,所以本文用canvas技術來實現對圖片的截取
canvas中的圖片展現
在上文中,我們利用 展現出了我們選擇的圖片,但是我們的圖片截取功能可是要利用 來實現的,所以怎麼在 中展現我們剛才獲取的圖片就是下一步要乾的事情了
canvas的API中自帶 函數,其作用就是在 中渲染一張圖片出來,其可以支持多種圖片來源見 MDN Web docs – Web技術文檔/Web API介面/CanvasRenderingContext2D/CanvasRenderingContext2D.drawImage()
最簡單的,我們直接把剛剛顯示圖片的那個 傳入是不是就可以呢
vartheCanvas =document.getElementById("imgCanvas");
varcanvasImg = theCanvas.getContext("2d");//獲取2D渲染背景
varimg =document.getElementById("image");img.onload =function(){//確認圖片已載入
canvasImg.drawImage(img,,);}
結果如下
從圖中看,左側是之前的"』,右側是渲染了圖片信息的
這麼看來雖然成功?在 中渲染出了圖片但是有兩個明顯的問題
1.左邊的"』留著幹啥?
2.右邊看上去是不是有點不一樣?
這倆問題其實都好辦,針對第一個問題,我們其實可以根本不用實體的"』直接利用』Image』對象即可,第二個問題明顯是因為 的大小與獲取到的圖片大小不一致所產生的,綜合這兩點,對代碼進行進化!
vartheCanvas =document.getElementById("imgCanvas");
varcanvasImg = theCanvas.getContext("2d");
varimg =newImage();//創建img對象reader.onload =function(e){ img.src = e.target.result;} img.onload =function(){ theCanvas.Width = img.width;
//將img對象的長款賦給canvas標籤
theCanvas.height = img.height; canvasImg.drawImage(img,,);}
結果與我們所期待的一樣,至此我們成功的在"』中展現了從本地獲取的圖片
canvas中圖片的截取
其實截圖,說白了就是在一個圖像上,獲取某個區域中的圖像信息
canvas作為專門用來處理圖像及像素相關的一套API,獲取區域中的相關圖像信息可以說是再簡單不過的事情,利用 函數即可 //詳情 ,當然我們不光要把圖像信息獲取到,最好還能展現出來我們的截圖結果,這裡就要用到與之相對的 函數 //詳情
varresultCanvas =
document.getElementById("resultCanvas");
varresultImg = resultCanvas.getContext("2d");
varcutData = canvasImg.getImageData(100,100,200,200);resultImg.putImageData(cutData,,);
結果如圖
我也要畫一個圈/框
既然這個工具是面向用戶的,截圖的過程肯定是要所見即所得的,在函數 中有4個參數,分別是截圖起點的兩個坐標和區域的寬度及高度,所以問題就變成了如何更合理的讓用戶輸入這4個值。
其實現存的主流解決方案就做的非常好了: 在圖上拖動滑鼠,拉出一個框,這個框內就是用戶希望截取的區域。
在畫布上畫出一個框很簡單,只需用到 函數 //詳情
但是讓用戶自己拖出一個框就比較複雜了,先分析一下用戶的一套動作都有什麼
用戶選定起始點,點下滑鼠左鍵
用戶選定截圖區域的大小,保持滑鼠左鍵不抬起,同時移動滑鼠選擇
用戶完成選擇,抬起滑鼠左鍵
回過頭再來看程序需要幹什麼
獲取起始點的坐標,並記錄為已點擊狀態
判斷一下如果為已點擊狀態那麼,獲取每一次移動/幀的滑鼠坐標,並計算出與起始點之間的橫縱坐標距離,而這距離就是所畫框的長度和寬度,清除上一幀的整個畫面,再繪製一個新的圖片再畫一個新的框,同時按照框的起始坐標及寬高,截取圖像信息,再清除預覽區域的上一幀的畫布,再將這一幀的圖像信息載入
滑鼠抬起後,停止記錄及繪製,保持最終一幀的框停留在畫面上
在這裡,要說明一下,為什麼非要清除整個畫面不可,其實可以把通過 獲取到的2D 畫布的渲染上下文//詳情 就當作一塊畫布,已經渲染出來的東西就已經留在了上面,無法再修改,如果想要更改畫面上已經存在的元素的大小位置形狀等等屬性,那麼在程序層面,就只能(個人理解,不一定對,如果有問題請一定跟我嘮嘮)把之前的畫布清空再重新渲染。
這個思路與我們之前端開發中動畫相關的開發思路不同,並不是像之前那樣直接操作現有元素屬性就可以改變該元素在畫面上的呈現結果的,而在這裡其實更像是在現實生活中的動畫製作原理就是
每一幀都需要重新繪製整張畫面
而其實這是任何動畫渲染方式的最底層思路與行為
話說回來按照上文相關的開發思路,實現這個功能的代碼如下
結果如圖
能不能弄的高大上一點啊
主要吧,這個黑框太丑了,透露著一種原始和狂野,以及來自工科男審美的粗糙感…
能不能弄的好看點,起碼讓它看上去是一個工具不是一個實驗
我的想法是這樣的,待被截取的圖片上應該蒙上一層半透明白色遮罩,用戶框選出的部分是沒有遮罩的,這樣效果可以為功能增加視覺上的材質感及舒適感,同時顯得高端
具體效果是這樣的-下圖來自ps
是不是稍微好些了
可是,怎麼實現?
簡單來說,就是在原有的畫布上再蒙半透明的一層畫布,然後讓這一層有一部分是沒有的就可以實現了,總的來說就是蒙版和遮罩的思路,在canvas中也有相關的api,但是我愣是沒看明白
負責任的貼一個鏈接
不過開發就是這樣,條條大路出bug
我想到這個功能的瞬間腦子像抽了一樣,出現了這麼一種實現方法
見下圖
mask層可以分為A,B,C,D四個矩形區域,在圖中兩個藍色的點是已知的(用戶自己畫出來的),在下層圖片大小已知的前提下,這四個矩形區域的四個點都是可以計算出來的,從而其高度和寬度也可以計算出來,這樣就可以利用這些數據畫出一個半透明的矩形,將四個半透明矩形都畫出來後,就能夠實現之前設計出的效果了,具體代碼如下
theCanvas.addEventListener("mousemove", e => {if(flag){ canvasImg.clearRect(,,W,H); resultImg.clearRect(,,
cutData.width,cutData.height); canvasImg.drawImage(img,,); canvasImg.fillStyle ="rgba(255,255,255,0.6)";
//設定為半透明的白色
canvasImg.fillRect(,, e.clientX, startY);
//矩形A
canvasImg.fillRect(e.clientX,, W, e.clientY);
//矩形B
canvasImg.fillRect(startX,
e.clientY, W-startX, H-e.clientY);
//矩形C
canvasImg.fillRect(, startY, startX, H-startY);
//矩形D
cutData = canvasImg.getImageData(startX,
startY, e.clientX - startX, e.clientY - startY); resultImg.putImageData(cutData,,); }})
效果如圖
沒有什麼把自己的腦殘想法實現更爽的了
至此,截圖的基本功能都實現了,但還差最後一步
另一次歷史性的對話 – 圖片上傳
圖片已經截出來了,下一步就是怎麼上傳了,通過Ajax上傳,需要將圖像數據轉化為 ,而在canvas的API中自帶 函數 //詳情
var resultFile = {}theCanvas.addEventListener("mouseup", e => { resultCanvas.toBlob(blob => { resultFile = blob; console.log(blob);
//Blob(1797) } }) flag =false;})
然後就可以用Ajax上傳拉,具體怎麼上傳就需要具體問題具體分析了
至此,整個插件的思路及需要用到相關技術都捋清楚了,接下來就可以開始按照上文的需求進行開發了,而這是下一篇文章要講的事情了
能看到這的絕對很閑
這篇文章的長度讓我想起讀研時被畢業論文統治的恐懼
本來想著連同組件開發一起在一篇內寫完呢,但是實在太長還是放棄了
身體和家人都是最重要的,今年還沒過一個月就被上了很多課
更多分享,敬請關注
本文來源網路,侵立刪!


※PHP字元轉義相關函數小結
※你跟這個曾經迷茫的妹紙有過一樣的經歷嗎?
TAG:PHP技術大全 |