图片上传

338 阅读4分钟

如何做好一个文件上传

1 DOM-fileUpload

  1. 在还没有ajax异步请求之前,通常会采用表单的input上传文件,同步上传,最核心的部分就是DOM的FileUpload对象——input[type="file"]
<form id="upload" method="post" action="/upload_file" enctype="mulipart/form-data">
    <input type="file" id="inputFile" name="file"></input>
<input type="submit" value="提交"></input>
</form>
  1. 显然,这种原生的input文件需要借助jS的submit提交,还需要设置name属性,上传的类型为文件类型,有时候图片需要base64或者blob就需要自行解决了。

  2. 如果直接上传会刷新页面的

    解决方法如下
    1. 通过给submit按钮添加 return false
    2. 阻止form的默认行为 e.preventDefault()
    
  3. 重置样式,并不是真正的隐藏

     form {
         display: inline-block;
         position: relative;
         overflow: hidden;
    }
    
     input[type="file"] {
         position: absolute;
         top: 0;
         right: 0;
         _zoom:30;
         margin: 0;
         padding: 0;
         height: 100%;
         _height:auto;
         font-size: 3000px \9;
         filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
         opacity: 0;
         cursor: pointer;
    }
    
  4. 这样同步上传文件是不利于对文件的处理的,比如JS压缩等操作,最近这段时间,接手一个老项目,确实不好处理,也没有去用压缩库

2AJAX

  1. 由于老项目需要,就采取jquery来调用ajax
  2. 可以采用等比压缩和质量压缩,只要依靠canvas
  3. 实现方法
// 由于采用了jquery,还是要支持复用的,需要传入$(this)进来,就不直接获取dom.getElementById('file').files[0]了
function compressAndUpdate(input, cb) {
    const $self = input;
    // 获取file类型
    let fileObj = $self[0].files[0]
    // 可以直接用fileobj获取fileSize等等
    let file = $self.val().split('.')
    let filetype = file[file.length-1].toLowerCase()
    let fileSize = 0;
    let browser = navigator.appName; // 浏览器类型
    
    if(!file[0]) {
        return;
    }
     // 非ie浏览器
  if (browser !== 'Microsoft Internet Explorer') {
      filesize = $self.get(0).files[0].size;
  }

      if (filesize >= 10 * 1024 * 1024) {
        $self.val('');
        toast.showError('上传文件大小不能超过10M!');
        return false;
      }

      if (filetype !== 'jpg'&& filetype !== 'jpeg' && filetype !== 'gif' && filetype !== 'png' && filetype !== 'bmp') {
        $self.val('');
        toast.showError('上传文件格式不符合要求!');
        return false;
      }
    
    // 一大波判断结束后,可以开始我们的压缩了
   	let reader = new FileReader()
    reader.readAsDataURL(fileObj)
    reader.onload = function(e) {
        let image = new Image()
        image.src = e.target.result;
        image.onload = function() {
            // 创建canvas压缩,可以判断多大才去压缩,或者宽高多大,压缩多少
            let canvas = document.createElement('canvas')
            let context = canvas.getContext('2d')
            let imageWidth = image.width / 2 // 压缩后图片的大小
            let imageHeight= image.height / 2
            let data = ''
            // 将图片大小赋值在canvas大小上
            canvas.width = imageWidth;
            canvas.height = imageHeight;
            context.drawImage(image, 0,0, imageWidth, imageHeight)
        	data = canvas.toDataURL('image/jpeg', 0.5) // 输出64, 0.5是量
            var obj = dataURLtoFile(data, fileObj.name) // 64转成file类型
            var form1 = new FormData();
            form1.append('avatar', obj)
            $.ajax({
              url: 'xxxxxxxxxxxxxxxxxxxx',
              type: 'POST',
              cache: false,
              processData: false,
              contentType: false,
              data: form1,
              success: function (response) {
                  // 成功
              }
            })
       }
    }
    if (typeof cb === 'function') {
        cb();
      }
}

function dataURLtoFile(dataurl, filename) {//将base64转换为文件
  var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1],
      bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n);
  while(n--){
      u8arr[n] = bstr.charCodeAt(n);
  }
  return new File([u8arr], filename, {type:mime});
}

3 图片格式转化

1 图片常见的格式

  1. file,blob,base64

  2. 参考 https://www.cnblogs.com/whitewen/articles/10456410.html#3

4 预览(主要针对图片预览)

普通青年的图片预览方式是待文件上传成功后,后台返回上传文件的url,然后把预览图片的img元素的src指向该url。这其实达不到预览的效果和目的。

属于文艺青年的现代浏览器又登场了:“使用HTML5的FileReader API吧!” 让我们直接上代码,直奔主题:

方法1: reader.onload回调的result指向image的src

function handleImageFile(file) {
    var previewArea = document.getElementById('previewArea');
    var img = document.createElement('img');
    var fileInput = document.getElementById("myFile");
    var file = fileInput.files[0];
    img.file = file;
    previewArea.appendChild(img);

    var reader = new FileReader();
    reader.onload = (function(aImg) {
        return function(e) {
            // 此处的e是reader, 也可以this, 将result返回给src
            aImg.src = e.target.result;
        }
    })(img);
    reader.omprogress = function(e) {
        // e.loaded/e.total
    }
    reader.readAsDataURL(file);
}

FileReader实例有以下方法:

- abort               : 中断读取操作;
- readAsArrayBuffer   : 读取文件内容到ArrayBuffer对象中;
- readAsBinaryString  : 将文件读取为二进制数据;
- readAsDataURL       : 将文件读取为data : URL格式的字符串;
- readAsText          : 将文件读取为文本;
- onprogress          : 文件读取进度;
- onload              : 文件读取加载;

使用FileReader来处理图片的异步加载,在创建新的FileReader对象之后,我们建立了onload函数,然后调用readAsDataURL()开始在后台进行读取操作。当图像文件加载后,转换成一个 data: URL 保存在result属性上,并传递到onload回调函数中设置给img的src。

方法2: 无需FileReader, 直接将file转换URL

file本来就是就继承自Blob对象,可以URL.createObjectURL()函数创建URl

var img = document.createElement("img");
// 全局函数URL生成一个网络地址————createObjectURL, revokeObjectURL
img.src = window.URL.createObjectURL(file);
img.onload = function() {
    // 明确地通过调用释放
    window.URL.revokeObjectURL(this.src);
}
previewArea.appendChild(img);

5 拖拽和裁剪的支持

  1. 拖拽上传和图片裁剪处理功能,这是高科技功能,可以帮你上传组件增加不少亮点; 其实主要是利用HTML5的drag & drop事件,我们可以很快实现对拖拽的支持。首先我们可能需要确定一个允许拖拽的区域,然后绑定相应的事件进行处理。
var dropArea;

dropArea = document.getElementById("dropArea");
dropArea.addEventListener("dragenter", handleDragenter, false);
dropArea.addEventListener("dragover", handleDragover, false);
dropArea.addEventListener("drop", handleDrop, false);

// 阻止dragenter和dragover的默认行为,这样才能使drop事件被触发
function handleDragenter(e) {
    e.stopPropagation();
    e.preventDefault();

}

function handleDragover(e) {
    e.stopPropagation();
    e.preventDefault();
}

function handleDrop(e) {
    e.stopPropagation();
    e.preventDefault();

    var dt = e.dataTransfer;
    var files = dt.files;

    // handle files ...
}
  1. 同样可以用到原生JS,mousedown mouseup
// 直接采用vue的
directives: {
    drap: {
      bind: function(el) {
        const oDiv = el;
        // 获取绑定元素的宽高
        oDiv.style.position = 'absolute';
        const width = oDiv.style.width;
        const height = oDiv.style.width;
        const minWidth = 100,
          minHeight = 100;
        oDiv.onmousedown = (e) => {
          // 获取鼠标相对位置
          e.stopPropagation();
          const pWidth = e.target.parentNode.getClientRects()[0].width;
          const pHeight = e.target.parentNode.getClientRects()[0].height;
          // console.log(e.target.parentNode.getClientRects()[0].width);
          const l = e.clientX - oDiv.offsetLeft;
          const t = e.clientY - oDiv.offsetTop;
          let left = 0,
            top = 0;
          document.onmousemove = (e) => {
            let left = e.clientX - l;
            let top = e.clientY - t;
            oDiv.style.left = `${left}px`;
            oDiv.style.top = `${top}px`;
          };
          document.onmouseup = (e) => {
            document.onmousemove = null;
            document.onmouseup = null;
          };
        };
      }
    }

这里可以把通过事件对象的dataTransfer拿到的files数组和之前相同处理,以实现预览上传等功能。有了这些事件回调,我们也可以在不同的事件给我们UI元素添加不同的class来实现更好交互效果。

裁剪

mousedown
mouseup
mousemove
canvas

未完待续...