DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> HTML基礎知識 >> HTML5詳解 >> 通過Canvas及File API縮放並上傳圖片完整示例
通過Canvas及File API縮放並上傳圖片完整示例
編輯:HTML5詳解     
示例地址:Canvas Resize Demo 
原文作者:Dr. Tom Trenka 
原文日期: 2013年8月6日 
翻譯日期: 2013年8月8日 

Tom Trenka 能為"我"的博客寫一篇文章,對我來說是一個巨大的榮譽。Tom是Dojo框架的最初貢獻者之一,也是我在SitePen公司的良師益友.我見證了他最頂級的天才能力,並且他總是第一個以前瞻性的解決方案預見了很多棘手的問題。他總是站在局外思考,打破常規但卻又堅實可靠地解決邊緣問題。本文就是一個完美的例證。 
最近我總是被問道要創造一個用戶接口API,允許用戶上傳圖片到服務器上(伴隨其他的事情),並能在我們公司提供支持的大量網站的客戶端上使用。通常來說這都是很容易的事情——創建一個form表單,添加一個file類型的input輸入框,讓用戶從電腦裡選擇圖片,並在form標簽上設置enctype="multipart/form-data"表單屬性,然後上傳即可。非常簡單,不是嗎?事實上,這裡有一個足夠簡單的例子;點擊進入 
但是如果你想要通過某些方式預先處理一下圖片再上傳,那該怎麼辦?比如說,你必須先壓縮圖片尺寸,或者需要圖片只能是某些種類的格式,如 png 或者jpg,你怎麼辦? 
用canvas來解決! 

Canvas簡介 
canvas 是一個Html5新增的DOM元素,允許用戶在頁面上直接地繪制圖形,通常是使用JavaScript.而不同的格式標准也是不同的,比如SVG是光柵API(raster API) 而VML卻是向量API(vector API).可以考慮使用Adobe Illustrator(矢量圖)作圖與使用 Adobe Photoshop (光柵圖)作圖的區別。 

在canvas(畫布)上能做的事情就是讀取和渲染圖像,並且允許你通過JavaScript操縱圖像數據。已經有很多現存的文章來為你演示基本的圖像處理——主要關注與各種不同的圖像過濾技術( image filtering techniques)——但我們需要的僅僅是縮放圖片並轉換到特定的文件格式,而canvas完全可以做到這些事情。 

我們假定的需求,比如圖像高度不超過100像素,不管原始圖像有多高。基本的代碼如下所示: 

復制代碼代碼如下:
// 參數,最大高度 
var MAX_HEIGHT = 100; 
// 渲染 
function render(src){ 
// 創建一個 Image 對象 
var image = new Image(); 
// 綁定 load 事件處理器,加載完成後執行 
image.onload = function(){ 
// 獲取 canvas DOM 對象 
var canvas = document.getElementById("myCanvas"); 
// 如果高度超標 
if(image.height > MAX_HEIGHT) { 
// 寬度等比例縮放 *= 
image.width *= MAX_HEIGHT / image.height; 
image.height = MAX_HEIGHT; 

// 獲取 canvas的 2d 環境對象, 
// 可以理解Context是管理員,canvas是房子 
var ctx = canvas.getContext("2d"); 
// canvas清屏 
ctx.clearRect(0, 0, canvas.width, canvas.height); 
// 重置canvas寬高 
canvas.width = image.width; 
canvas.height = image.height; 
// 將圖像繪制到canvas上 
ctx.drawImage(image, 0, 0, image.width, image.height); 
// !!! 注意,image 沒有加入到 dom之中 
}; 
// 設置src屬性,浏覽器會自動加載。 
// 記住必須先綁定事件,才能設置src屬性,否則會出同步問題。 
image.src = src; 
}; 

在上面的例子中,你可以使用canvas 的 toDataURL() 方法獲取圖像的 Base64編碼的值(可以類似理解為16進制字符串,或者二進制數據流). 
注意: canvas 的 toDataURL() 獲取的URL以字符串開頭,有22個無用的數據 "data:image/png;base64,",需要在客戶端或者服務端進行過濾. 
原則上只要浏覽器支持,URL地址的長度是沒有限制的,而1024的長度限制,是老一代IE所獨有的。 

請問,如何獲取我們需要的圖像呢? 
好孩子,很高興你能這麼問。你並不能通過File 輸入框來直接處理,你從這個文件輸入框元素所能獲取的僅僅是用戶所選擇文件的path路徑。按照常規想象,你可以通過這個path路徑信息來加載圖像,但是,在浏覽器裡面這是不現實的。(譯者注:浏覽器廠商必須保證自己的浏覽器絕對安全,才能獲得市場,至少避免媒體的攻擊,如果允許這樣做,那惡意網址可以通過拼湊文件路徑來嘗試獲取某些敏感信息). 
為了實現這個需求,我們可以使用Html5的File API 來讀取用戶磁盤上的文件,並用這個file來作為圖像的源(src,source). 

File API簡介 
新的File API接口是在不違背任何安全沙盒規則下,讀取和列出用戶文件目錄的一個途徑—— 通過沙盒(sandbox)限制,惡意網站並不能將病毒寫入用戶磁盤,當然更不能執行。 
我們要使用的文件讀取對象叫做 FileReader,FileReader允許開發者讀取文件的內容(具體浏覽器的實現方式可能大不相同)。 

假設我們已經獲取了圖像文件的path路徑,那麼依賴前面的代碼,使用FileReader來加載和渲染圖像就變得很容易了: 

復制代碼代碼如下:
// 加載 圖像文件(url路徑) 
function loadImage(src){ 
// 過濾掉 非 image 類型的文件 
if(!src.type.match(/image.*/)){ 
if(window.console){ 
console.log("選擇的文件類型不是圖片: ", src.type); 
} else { 
window.confirm("只能選擇圖片文件"); 

return; 

// 創建 FileReader 對象 並調用 render 函數來完成渲染. 
var reader = new FileReader(); 
// 綁定load事件自動回調函數 
reader.onload = function(e){ 
// 調用前面的 render 函數 
render(e.target.result); 
}; 
// 讀取文件內容 
reader.readAsDataURL(src); 
}; 

請問,如何獲取文件呢? 
小白兔,要有耐心!我們的下一步就是獲取文件,當然有好多方法可以實現啦。例如:你可以用文本框讓用戶輸入文件路徑,但很顯然大多數用戶都不是開發者,對輸入什麼值根本就不了解. 
為了用戶使用方便,我們采用 Drag and Drop API接口。 

使用 Drag and Drop API 
拖拽接口(Drag and Drop)非常簡單——在大多數的DOM元素上,你都可以通過綁定事件處理器來實現. 只要用戶從磁盤上拖動一個文件到dom對象上並放開鼠標,那我們就可以讀取這個文件。代碼如下: 

復制代碼代碼如下:
function init(){ 
// 獲取DOM元素對象 
var target = document.getElementById("drop-target"); 
// 阻止 dragover(拖到DOM元素上方) 事件傳遞 
target.addEventListener("dragover", function(e){e.preventDefault();}, true); 
// 拖動並放開鼠標的事件 
target.addEventListener("drop", function(e){ 
// 阻止默認事件,以及事件傳播 
e.preventDefault(); 
// 調用前面的加載圖像 函數,參數為dataTransfer對象的第一個文件 
loadImage(e.dataTransfer.files[0]); 
}, true); 
var setheight = document.getElementById("setheight"); 
var maxheight = document.getElementById("maxheight"); 
setheight.addEventListener("click", function(e){ 
// 
var value = maxheight.value; 
if(/^/d+$/.test(value)){ 
MAX_HEIGHT = parseInt(value); 

e.preventDefault(); 
},true); 
var btnsend = document.getElementById("btnsend"); 
btnsend.addEventListener("click", function(e){ 
// 
sendImage(); 
},true); 
}; 

我們還可以做一些其他的處理,比如顯示預覽圖。但如果不想壓縮圖片的話,那很可能沒什麼用。我們將采用Ajax通過HTTP 的post方式上傳圖片數據。下面的例子是使用Dojo框架來完成請求的,當然你也可以采用其他的AJax技術來實現. 
Dojo 代碼如下: 

復制代碼代碼如下:
// 譯者並不懂Dojo,所以將在後面附上jQuery的實現 
// Remember that DTK 1.7+ is AMD! 
require(["dojo/request"], function(request){ 
// 設置請求URL,參數,以及回調。 
request.post("image-handler.PHP", { 
data: { 
imageName: "myImage.png", 
imageData: encodeURIComponent(document.getElementById("canvas").toDataURL("image/png")) 

}).then(function(text){ 
console.log("The server returned: ", text); 
}); 
}); 

jQuery 實現如下: 

復制代碼代碼如下:
// 上傳圖片,jQuery版 
function sendImage(){ 
// 獲取 canvas DOM 對象 
var canvas = document.getElementById("myCanvas"); 
// 獲取Base64編碼後的圖像數據,格式是字符串 
// "data:image/png;base64,"開頭,需要在客戶端或者服務器端將其去掉,後面的部分可以直接寫入文件。 
var dataurl = canvas.toDataURL("image/png"); 
// 為安全 對URI進行編碼 
// data%3Aimage%2Fpng%3Bbase64%2C 開頭 
var imagedata = encodeURIComponent(dataurl); 
//var url = $("#form").attr("action"); 
// 1. 如果form表單不好處理,可以使用某個hidden隱藏域來設置請求地址 
// <input type="hidden" name="action" value="receive.JSP" /> 
var url = $("input[name='action']").val(); 
// 2. 也可以直接用某個dom對象的屬性來獲取 
// <input id="imageaction" type="hidden" action="receive.JSP"> 
// var url = $("#imageaction").attr("action"); 
// 因為是string,所以服務器需要對數據進行轉碼,寫文件操作等。 
// 個人約定,所有http參數名字全部小寫 
console.log(dataurl); 
//console.log(imagedata); 
var data = { 
imagename: "myImage.png", 
imagedata: imagedata 
}; 
jQuery.AJax( { 
url : url, 
data : data, 
type : "POST", 
// 期待的返回值類型 
dataType: "JSon", 
complete : function(xhr,result) { 
//console.log(xhr.responseText); 
var $tip2 = $("#tip2"); 
if(!xhr){ 
$tip2.text('網絡連接失敗!'); 
return false; 

var text = xhr.responseText; 
if(!text){ 
$tip2.text('網絡錯誤!'); 
return false; 

var JSon = eval("("+text+")"); 
if(!JSon){ 
$tip2.text('解析錯誤!'); 
return false; 
} else { 
$tip2.text(JSon.message); 

//console.dir(JSon); 
//console.log(xhr.responseText); 

}); 
}; 

OK,搞定!你還需要做的,就是創建一個只管的用戶界面,並允許你控制圖片的大小。上傳到服務器端的數據,並不需要處理enctype為 multi-part/form-data 的情況,僅僅一個簡單的POST表單處理程序就可以了. 
好了,下面附上完整的代碼示例: 

復制代碼代碼如下:
<%@ page language="java" import="Java.util.*" pageEncoding="UTF-8"%> 
<% 
String path = request.getContextPath(); 
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; 
%> 
<!DOCTYPE Html> 
<Html> 
<head> 
<title>通過Canvas及File API縮放並上傳圖片</title> 
<meta http-equiv="pragma" content="no-cache"> 
<meta http-equiv="cache-control" content="no-cache"> 
<meta http-equiv="expires" content="0"> 
<meta http-equiv="keyWords" content="Canvas,File,Image"> 
<meta http-equiv="description" content="2013年8月8日,[email protected]"> 
<script src="http://code.jquery.com/jquery-1.7.1.min.JS"></script> 
<script> 
// 參數,最大高度 
var MAX_HEIGHT = 100; 
// 渲染 
function render(src){ 
// 創建一個 Image 對象 
var image = new Image(); 
// 綁定 load 事件處理器,加載完成後執行 
image.onload = function(){ 
// 獲取 canvas DOM 對象 
var canvas = document.getElementById("myCanvas"); 
// 如果高度超標 
if(image.height > MAX_HEIGHT) { 
// 寬度等比例縮放 *= 
image.width *= MAX_HEIGHT / image.height; 
image.height = MAX_HEIGHT; 

// 獲取 canvas的 2d 環境對象, 
// 可以理解Context是管理員,canvas是房子 
var ctx = canvas.getContext("2d"); 
// canvas清屏 
ctx.clearRect(0, 0, canvas.width, canvas.height); 
// 重置canvas寬高 
canvas.width = image.width; 
canvas.height = image.height; 
// 將圖像繪制到canvas上 
ctx.drawImage(image, 0, 0, image.width, image.height); 
// !!! 注意,image 沒有加入到 dom之中 
}; 
// 設置src屬性,浏覽器會自動加載。 
// 記住必須先綁定事件,才能設置src屬性,否則會出同步問題。 
image.src = src; 
}; 
// 加載 圖像文件(url路徑) 
function loadImage(src){ 
// 過濾掉 非 image 類型的文件 
if(!src.type.match(/image.*/)){ 
if(window.console){ 
console.log("選擇的文件類型不是圖片: ", src.type); 
} else { 
window.confirm("只能選擇圖片文件"); 

return; 

// 創建 FileReader 對象 並調用 render 函數來完成渲染. 
var reader = new FileReader(); 
// 綁定load事件自動回調函數 
reader.onload = function(e){ 
// 調用前面的 render 函數 
render(e.target.result); 
}; 
// 讀取文件內容 
reader.readAsDataURL(src); 
}; 
// 上傳圖片,jQuery版 
function sendImage(){ 
// 獲取 canvas DOM 對象 
var canvas = document.getElementById("myCanvas"); 
// 獲取Base64編碼後的圖像數據,格式是字符串 
// "data:image/png;base64,"開頭,需要在客戶端或者服務器端將其去掉,後面的部分可以直接寫入文件。 
var dataurl = canvas.toDataURL("image/png"); 
// 為安全 對URI進行編碼 
// data%3Aimage%2Fpng%3Bbase64%2C 開頭 
var imagedata = encodeURIComponent(dataurl); 
//var url = $("#form").attr("action"); 
// 1. 如果form表單不好處理,可以使用某個hidden隱藏域來設置請求地址 
// <input type="hidden" name="action" value="receive.JSP" /> 
var url = $("input[name='action']").val(); 
// 2. 也可以直接用某個dom對象的屬性來獲取 
// <input id="imageaction" type="hidden" action="receive.JSP"> 
// var url = $("#imageaction").attr("action"); 
// 因為是string,所以服務器需要對數據進行轉碼,寫文件操作等。 
// 個人約定,所有http參數名字全部小寫 
console.log(dataurl); 
//console.log(imagedata); 
var data = { 
imagename: "myImage.png", 
imagedata: imagedata 
}; 
jQuery.AJax( { 
url : url, 
data : data, 
type : "POST", 
// 期待的返回值類型 
dataType: "JSon", 
complete : function(xhr,result) { 
//console.log(xhr.responseText); 
var $tip2 = $("#tip2"); 
if(!xhr){ 
$tip2.text('網絡連接失敗!'); 
return false; 

var text = xhr.responseText; 
if(!text){ 
$tip2.text('網絡錯誤!'); 
return false; 

var JSon = eval("("+text+")"); 
if(!JSon){ 
$tip2.text('解析錯誤!'); 
return false; 
} else { 
$tip2.text(JSon.message); 

//console.dir(JSon); 
//console.log(xhr.responseText); 

}); 
}; 
function init(){ 
// 獲取DOM元素對象 
var target = document.getElementById("drop-target"); 
// 阻止 dragover(拖到DOM元素上方) 事件傳遞 
target.addEventListener("dragover", function(e){e.preventDefault();}, true); 
// 拖動並放開鼠標的事件 
target.addEventListener("drop", function(e){ 
// 阻止默認事件,以及事件傳播 
e.preventDefault(); 
// 調用前面的加載圖像 函數,參數為dataTransfer對象的第一個文件 
loadImage(e.dataTransfer.files[0]); 
}, true); 
var setheight = document.getElementById("setheight"); 
var maxheight = document.getElementById("maxheight"); 
setheight.addEventListener("click", function(e){ 
// 
var value = maxheight.value; 
if(/^/d+$/.test(value)){ 
MAX_HEIGHT = parseInt(value); 

e.preventDefault(); 
},true); 
var btnsend = document.getElementById("btnsend"); 
btnsend.addEventListener("click", function(e){ 
// 
sendImage(); 
},true); 
}; 
window.addEventListener("DOMContentLoaded", function() { 
// 
init(); 
},false); 
</script> 
</head> 
<body> 
<div> 
<h1>通過Canvas及File API縮放並上傳圖片</h1> 
<p>從文件夾拖動一張照片到下方的盒子裡, canvas 和 JavaScript將會自動的進行縮放.</p> 
<div> 
<input type="text" id="maxheight" value="100"/> 
<button id="setheight">設置圖片最大高度</button> 
<input type="hidden" name="action" value="receive.JSP" /> 
</div> 
<div id="prevIEw-row"> 
<div id="drop-target" style="width:400px;height:200px;min-height:100px;min-width:200px;background:#eee;cursor:pointer;">拖動圖片文件到這裡...</div> 
<div> 
<div> 
<button id="btnsend"> 上 傳 </button> <span id="tip2" style="padding:8px 0;color:#f00;"></span> 
</div> 
</div> 
<div><h4>縮略圖:</h4></div> 
<div id="prevIEw" style="background:#f4f4f4;width:400px;height:200px;min-height:100px;min-width:200px;"> 
<canvas id="myCanvas"></canvas> 
</div> 
</div> 
</div> 
</body> 
</Html> 

服務端頁面,receive.JSP 

復制代碼代碼如下:
<%@ page language="java" import="Java.util.*" pageEncoding="UTF-8"%> 
<%@page import="sun.misc.BASE64Decoder"%> 
<%@page import="Java.io.*"%> 
<%@page import="org.springframework.web.util.UriComponents"%> 
<%@page import="Java.Net.URLDecoder"%> 
<%! 
// 本文件:/receive.JSP 
// 圖片存放路徑 
String photoPath = "D:/blog/upload/photo/"; 
File photoPathFile = new File(photoPath); 
// references: http://blog.csdn.Net/remote_roamer/article/details/2979822 
private boolean saveImageToDisk(byte[] data,String imageName) throws IOException{ 
int len = data.length; 
// 
// 寫入到文件 
FileOutputStream outputStream = new FileOutputStream(new File(photoPathFile,imageName)); 
outputStream.write(data); 
outputStream.flush(); 
outputStream.close(); 
// 
return true; 

private byte[] decode(String imageData) throws IOException{ 
BASE64Decoder decoder = new BASE64Decoder(); 
byte[] data = decoder.decodeBuffer(imageData); 
for(int i=0;i<data.length;++i) 

if(data[i]<0) 

//調整異常數據 
data[i]+=256; 


// 
return data; 

%> 
<% 
String path = request.getContextPath(); 
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; 
%> 
<% 
//如果是IE,那麼需要設置為text/Html,否則會彈框下載 
//response.setContentType("text/Html;charset=UTF-8"); 
response.setContentType("application/JSon;charset=UTF-8"); 
// 
String imageName = request.getParameter("imagename"); 
String imageData = request.getParameter("imagedata"); 
int success = 0; 
String message = ""; 
if(null == imageData || imageData.length() < 100){ 
// 數據太短,明顯不合理 
message = "上傳失敗,數據太短或不存在"; 
} else { 
// 去除開頭不合理的數據 
imageData = imageData.substring(30); 
imageData = URLDecoder.decode(imageData,"UTF-8"); 
//System.out.println(imageData); 
byte[] data = decode(imageData); 
int len = data.length; 
int len2 = imageData.length(); 
if(null == imageName || imageName.length() < 1){ 
imageName = System.currentTimeMillis()+".png"; 

saveImageToDisk(data,imageName); 
// 
success = 1; 
message = "上傳成功,參數長度:"+len2+"字符,解析文件大小:"+len+"字節"; 

// 後台打印 
System.out.println("message="+message); 
%> 

"message": "<%=message %>", 
"success": <%=success %> 
XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved