Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

使用多种姿势上传图片 #7

Open
moxiaohe opened this issue May 27, 2016 · 0 comments
Open

使用多种姿势上传图片 #7

moxiaohe opened this issue May 27, 2016 · 0 comments

Comments

@moxiaohe
Copy link
Owner

moxiaohe commented May 27, 2016

tags: 图片上传, multipart, 截图上传, file对象, FormData, 拖拽上传图片, dataTransfer, readAsDataURL

一个如下所示的发布框,经常会出现在各种微博、社区、论坛站点上,这类发布形式虽然没有高级编辑器那样可以任意排版加工,但也满足了常见的表述观点意见的要求,它通常搭配一些表情、文件上传、分享网页视频等方式,来满足上述需求。最近我从事的一个项目中,就完成了如下图示的发布内容的需求,今天主要讲一下其中图片和附件的上传发布方式。

其实上传的方式有很多种,本文将列举需求中用到的几种上传方式。

image

1. 表单上传

也就是用传统的form表单来上传,使用form表单的input[type="file"]控件,可以打开系统的文件选择对话框,从而达到选择文件并上传的目的,它的好处是多浏览器兼容,但是在多图上传、分段上传上等高级特性上就显得力不从心。

表单的格式如下:

<form method="post" action="http://uploadUrl" enctype="multipart/form-data">
    <input name="file" type="file" accept="image/gif,image.jpg" />
    <input name="token" type="hidden" />
    <input type="submit" value="提交" /> 
</form>

我列出表单上传所需的关键几点:

  • method="post": 采用post方式提交数据
  • enctype="multipart/form-data":采用multipart格式上传文件,此时request头会显示 Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryzr34cwJ67R95KQC9
  • action:标明上传的服务端处理地址
  • type="file":使用input的file控件上传
  • accept属性是HTML5的新属性,它规定了可通过文件上传提交的文件类型
  • 上传的触发事件可以是:input[type="file"]的onChange触发,也可以由一个独立的按钮的onClick使整个表单提交,此时还可以用input[type="hidden"]带一些其它的参数,比如Token来源验证等等。

2. Ajax无刷新上传

ajax无刷新上传的方式,本质上与表单上传无异,只是把表单里的内容提出来采用ajax提交,并且由前端决定请求结果回传后的展示结果,不用像直接表单上传那样刷新和跳转页面。在这里,我们采用jQuery来作为操作DOM和创建ajax提交的js基础库。

html代码片段如下:

<form>
    <input id="file" name="file" type="file" />
    <input id="token" name="token" type="hidden" />
</form>

javascript代码片段如下:

$("#file").on("change", function(){
  var formData = new FormData();
  formData.append("file", $("#file")[0].files);
  formData.append("token", $("#token").val());
  $.ajax({
      url: "http://uploadUrl",
      type: "POST",
      data: formData,
      processData: false,
      contentType: false,
      success: function(response){
              // 根据返回结果指定界面操作
      }
  });
});

我们使用了file控件的change来触发上传事件,当然你也可以使用某个按钮来触发表单提交。提交数据时,我用到了FormData对象来发送二进制文件,FormData构造函数提供的append()方法,除了直接添加二进制文件还可以附带一些其它的参数,作为XMLHttpRequest实例的参数提交给服务端。

如下是Firefox MDN对FormData的解释。

XMLHttpRequest Level 2添加了一个新的接口FormData.利用FormData对象,我们可以通过JavaScript用一些键值对来模拟一系列表单控件,我们还可以使用XMLHttpRequest的send()方法来异步的提交这个"表单".比起普通的ajax,使用FormData的最大优点就是我们可以异步上传一个二进制文件.
https://developer.mozilla.org/zh-CN/docs/Web/API/FormData

BTW: 使用jQuery提供的ajax方法来发送二进制文件,还需要附加两个参数:

  • processData: false // 不要对data参数进行序列化处理,默认为true
  • contentType: false // 不要设置Content-Type请求头,因为文件数据是以 multipart/form-data 来编码

3. flash上传

很多时候上传的需求都不是只局限于单图上传,而且需要显示上传进度、中断上传过程、大文件分段上传等等,这时传统的表单上传无法实现这些功能,于是产生了使用Flash上传的方式,它采用Flash作为一个中间代理层,代替客户端跟服务端通信,此外,它也对客户端的文件选择方面拥有更多的控制权,比input[type="file"]要大得多。

在这里我使用了jQuery封装好的uploadify插件来进行演示,一般这类插件都自带了上传用的Flash文件,因为跟服务端回传的数据和展示跟客户端的交互,都是Flash文件的接口跟插件来对接的。

<div id="file_upload">

html部分很简单,预留一个hook后,插件会在这个节点内部创建Flash的object,并且还附带创建了上传进度、取消控件和多文件队列展示等界面。

$(function() {
  $("#file_upload").uploadify({
      auto: true,
      method: "Post",
      width: 120,
      height: 30,
      swf: './uploadify.swf',
      uploader: 'http://uploadUrl',
      formData: {
          token: $("#token").val()
      },
      fileObjName: "file",
      onUploadSuccess: function(file, data, response){
          // 根据返回结果指定界面操作
      }
  });
});

选择一个文件后,html代码如下所示:

<!--上传Flash-->
<div id="file_upload" class="uploadify" style="height: 30px; width: 120px;">
    <object id="SWFUpload_0" type="application/x-shockwave-flash" data="./uploadify.swf?preventswfcaching=1463109060389" width="120" height="30" class="swfupload" style="position: absolute; z-index: 1;">
        <param name="wmode" value="transparent">
        <param name="movie" value="./uploadify.swf?preventswfcaching=1463109060389">
        <param name="quality" value="high">
        <param name="menu" value="false">
        <param name="allowScriptAccess" value="always">
        <param name="flashvars" value="...">
    </object>
    <div id="file_upload-button" class="uploadify-button " style="height: 30px; line-height: 30px; width: 120px;">
        <span class="uploadify-button-text">SELECT FILES</span>
    </div>
</div>
<!--上传队列和文件操作、进度展示-->
<div id="file_upload-queue" class="uploadify-queue">
    <div id="SWFUpload_0_0" class="uploadify-queue-item">
        <div class="cancel">
            <a href="javascript:$('#file_upload').uploadify('cancel', 'SWFUpload_0_0')">X</a>
        </div>
        <span class="fileName">1111111.png (144KB)</span>
        <span class="data"></span>
        <div class="uploadify-progress">
            <div class="uploadify-progress-bar"><!--Progress Bar--></div>
        </div>
    </div>
</div>

更多的API和配置请参考jQuery.uploadify 官网APIs
美中不足的是,mac下会对Flash有各种各样的限制,甚至不支持,所以使用的时候也要慎重考虑。

4. 截图粘贴上传

目前只有较大的几家SNS和微博类型的站点发布框实现了这个功能,本着业界良心和用户至上的原则,我们也在内外站点开发的早期上线了此功能,此次只是对上传接口的改造,在这里简单的把上传过程和用到的内容介绍一下。

首先,截图粘贴上传的核心思想是,监听粘贴事件,然后获取剪切板中的数据,如果是一张图片,则触发上传事件。
代码片段如下:

$("textarea").on("paste", function(e){
   e.stopPropagation();
   var self = this;
   var clipboardData = e.originalEvent.clipboardData;
   if (clipboardData.items.length <= 0) {
       return;
   }
   var file = clipboardData.items[0].getAsFile();
   if (!file) {
       return;
   }
   var formData = new FormData();
   formData.append("file", file);
   formData.append("token", $("#token").val());
   $.ajax({
       url: "http://uploadUrl",
       type: "POST",
       data: formData,
   }).done(function(response) {
       // 根据返回结果指定界面操作
   });
   e.preventDefault();
});

从上面代码可以看出,上传的过程都是一样的,主要是获取文件的方式。
当进行粘贴(右键paste/ctrl+v)操作时,触发剪贴板事件'paste',从系统剪切板获取内容,而系统剪切板的数据在不同浏览器保存在不同的位置:

  • IE内核:windows.clipboardData
  • 其它:e.originalEvent.clipboardData

https://www.w3.org/TR/clipboard-apis/

5. 拖拽上传

拖拽上传的方式,支持的浏览器比较少,因为它用到了HTML5的两个新的属性(API)一个是Drag and Drop,一个是File API

上传域监听拖拽的三个事件:dragEnter、dragOver和drop,分别对应拖拽至、拖拽时和释放三个操作的处理机制,当然你也可以监听dragLeave事件。

HTML5的File API提供了一个FileList的接口,它可以通过拖拽事件的e.dataTransfer.files来传递的文件信息,获取本地文件列表信息。

File API在HTML5规范中只是草案,在 W3C 草案中,File 对象只包含文件名、文件类型和文件大小等只读属性。但部分浏览器�在草案之外提供了一个名为 FileReader 的对象,用以读取文件内容,并且可以监控读取状态,它提供的方法有: "readAsBinaryString" ,"readAsDataURL" ,"readAsText" ,"abort" 等。

参考MDN https://developer.mozilla.org/en-US/docs/Web/API/File

代码片段如下:

// dragenter
$("#textarea").on("dragenter", function(e){
    e.preventDefault();
});
// dragover
$("#textarea").on("dragover", function(e){
    e.preventDefault();
});
// drop
$("#textarea").on("drop", function(e){
    e.stopPropagation();
    e.preventDefault();
    var files = e.originalEvent.dataTransfer.files;
    _.each(files, function(file) {
        if (!/^image*/.test(file.type)) {
          return;
        }
        var fileReader = new FileReader();
        fileReader.onload = function() {
          //uploadFile(file);
        };
        fileReader.readAsDataURL(file);
    });
});

拖拽上传过程中的几个关键点:

  • 在drop事件触发后通过e.dataTransfer.files获取拖拽文件列表,在jQuery中是e.originalEvent.dataTransfer.files
  • 拖拽上传仅支持图片,文件对象中file.type标识了文件类型
  • 由于可能是多图拖拽,所以可以遍历图片上传,这里用了Underscore的each方法
  • 这里用readAsDataURL读取文件内容为二进制文件,你还可以将其转换为Base64方式上传,只是http协议里面存在对非二进制数据的上传大小限制为2M。
  • 上传的过程跟前面的方式相同,即:创建FormData对象并发起Ajax请求

总结

  1. 以上各种方式分别试用不同的场景,并无高下之分。
  2. 后面集中方式只有高级浏览器才支持,所以仅算是体验上的增强。
  3. 对于这些方式,阿里巴巴信息平台的uxcore框架已经封装了成型的组件且用在了我们日常的项目中,欢迎使用。传送门 uxcore-uploader
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant