原文地址:Resize an Image Using Canvas, Drag and Drop and the File API

示例地址: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表单处理程序就可以了.

Tom Trenka 博士简介
Tom Trenka ,软件开发者,明尼苏达州首府圣保罗的音乐及其他方面的专家。
DojoX项目的前lead,也是Dojo最早的代码贡献者之一,
Ai Media Group的高级开发工程师(Senior Software Developer),从事复杂数据分析程序的开发.

好了,下面附上完整的代码示例:

<%@ 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日,renfufei@qq.com"><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/2979822private 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.substring(imageData.indexOf(",") + 1);//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 %>
}

通过Canvas及File API缩放并上传图片相关推荐

  1. html5 dom api,HTML5 DOM File API

    访问选中的文件 简单的html代码: 通过File API,我们可以在用户选取一个或者多个文件之后(如果你的程序可以让用户选择多个文件,记得要在input元素上加上multiple属性),访问到代表了 ...

  2. java上传微博图床,GitHub - echisan/wbp4j: Simple Java Api for 微博图床,使用简单的api即可完成上传图片...

    wbp4j weibo picture api for java (中二一下) 使用Java实现的微博图床API,提供简单的api即可完成上传图片到微博图床,可方便集成到自己的项目当中. 如果有兴趣或 ...

  3. 基于html5 File API文件操作

    文章来源:小青年原创  发布时间:2016-08-16  关键词:blob,File,FileReader,DataURI,URL  转载需标注本文原始地址: http://zhaomenghuan. ...

  4. php 下 html5 XHR2 + FormData + File API 上传文件

    FormData的作用: FormData对象可以帮助我们自动的打包表单数据,通过XMLHttpRequest的send()方法来提交表单.当然FormData也可以动态的append数据.FormD ...

  5. HTML5 file api读取文件的MD5码工具

    1.工具的用途:用HTML5 file api读取文件的MD5码.MD5码在文件的唯一性识别上有很重要的应用,业内常用MD5进行文件识别.文件秒传.文件安全性检查等: 2.适用性:IE.Chrome皆 ...

  6. 学习File API用于前端读取文件

    1. File API简介 File API对于某些专门的网站的不可或缺的.现在常用它实现对文件的预览等功能. File API规定怎么从硬盘上提取文件,直接交给在网页中运行中的Javascript代 ...

  7. DevExpress v18.2版本亮点——Office File API 篇

    行业领先的.NET界面控件--DevExpress v18.2版本亮点详解,本文将介绍了DevExpress Office File API v18.2 的版本亮点,新版30天免费试用!点击下载> ...

  8. php html5 api,HTML5 File API解读

    1,概述 Web应用应该具备处理广泛用户输入问题的能力,例如在Web富应用中,用户希望上传文件到服务器.File API定义了访问文件的基本操作途径,包括文件.文件列表集.错误处理等,同时,File ...

  9. Resumable.js - 基于HTML5 File API的可断点续传的文件上传插件

    http://resumablejs.com/ A JavaScript library providing multiple simultaneous, stableand resumable up ...

最新文章

  1. 语法分析器 java实验报告_词法分析器实验报告.doc
  2. 刘强东写在上市之际:京东要成为一家世界级企业 感慨吧
  3. 如何让 Mybatis 自动生成代码
  4. ServletContextListener
  5. 美团延长旅行订单免费取消保障政策至2月29日
  6. 如何在Mac上的网站上设置时间限制?
  7. Tony的口胡呼呼(。-ω-)zzz
  8. java 单例 初始化_单例数据库对象启动时参数化初始化?
  9. 【JAVA】家庭记账系统
  10. STM32个人笔记-电源管理
  11. 安装 配置BlackBerry Push Service SDK v1.1.0
  12. SD/SDHC卡下载UBOOT 的注意事项
  13. 英语语法笔记——名词性从句(三)
  14. yolov3的weights文件获取方法(yolov3-spp.weights等等)
  15. 樱桃一次吃多少合适 这些知识一定要关心
  16. mysql区分大小写嘛_Mysql区分大小写问题
  17. Android中指纹识别的使用
  18. 怎么安装Python
  19. Istio-PilotDiscovery服务的创建
  20. 关于3DMAX2012提示MaxStartUI.mun无效,并显示启动不了MAX

热门文章

  1. 2021年美容师(初级)最新解析及美容师(初级)试题及解析
  2. win7下rndis/ethernet gadget驱动安装
  3. simple-uploader前端分片上传文件
  4. 001.网络TCP/IP工程知识点
  5. 云里黑白第三回——火绒全盘扫描自动重启蓝屏
  6. 设计模式之15 - 解释器模式Interpreter
  7. 支票容错识别系统预处理的设计与实现(转载)
  8. 求一元二次方程的根(C语言)
  9. 多小区智慧物业管理系统源码
  10. 雷电模拟器下载与安装