美文网首页让前端飞Web前端之路
vue+node实现拖拽上传图片

vue+node实现拖拽上传图片

作者: Bouc | 来源:发表于2019-06-20 18:20 被阅读4次
    效果图

    首先介绍几个要用到的知识点,源码地址在最底部,不想看文字的同学可以直接拉到最底部下载。

    一、知识点

    实现拖拽上传需要用到的知识点如下:

    前端
    拖拽事件
    dataTransfer
    FileReader
    FormData
    progress

    后端
    multer
    fs.renameSync

    1.H5拖拽事件

    我们拖动图片放到一个div上时,下列事件会依次发生:

    • dragenter
    • dragover
    • dragleave或drop

    只要图片被拖动到div上,就会触发dragenter事件,类似于mouseover事件。
    紧接着是dragover事件,而且只要图片在div内移动,就会不停的触发。
    如果拖出了div的范围,dragover事件不再发生,但会触发dragleave事件。
    如果你拖着图片在div上松手了,就会触发drop事件。

    2.dataTransfer对象

    只有简单的拖放而没有数据变化是莫得用的。我们拖个图片进来的目的是啥?当然是为了获得图片数据,这样才能然后传到服务器上去。于是有了dataTransfer对象,它是事件对象的一个属性,用于从被拖动元素向放置目标传递字符串格式的数据。我们要完成拖拽上传图片就得靠这个对象,因为它是事件对象的属性,所以我们只能在事件处理程序中使用,参考以下代码:

    //在drop事件中使用dataTransfer对象
     onDrop: function(e) {
                console.log("松手");
                var dt = e.dataTransfer;
              }
    

    3.FormData

    MND文档

    具体的用法还是得阅读MDN文档好,以下是本菜鸡读了文档后对FormData的理解:

    首先明确FomeData是一个对象,由键值对组成,有个append()方法增加键值,我们可以append字符、数值或是文件

    var formData = new FormData();
    
    formData.append("name", "Ciger");
    formData.append("phone", 123456); //数字123456会被立即转换成字符串 "123456"
    // HTML 文件类型input,由用户选择
    formData.append("userfile", fileInputElement.files[0]);
    

    append后,我们可通过get()和set()操作对象的值

    formData.get('name')  // 获取值-> Ciger
    formData.set('name','Ciger2') //重置值-> Ciger2
    

    知道如何使用这个对象了,最关键的就是它有什么用?
    formData的作用有两个:

    • 用于发送表单数据,也可独立于表单使用
    • 上传文件

    独立于表单使用有点抽象,我们来看代码。

    <form  id="myForm">
      <input type="email" name="userid" placeholder="email"/>
      <input type="text" name="content" />
      <input type="submit" value="Stash the file!" />
    </form>
    
    

    代码里是一个form表单,有两个input输入框加一个提交按钮。通常来说,我们提交数据时要先获取到两个input框的数据,拼接在一起,然后通过ajax发送。
    有了FormData对象,两行代码就可以实现form对象数据的拼接

    var form =  var form=document.querySelector("#myForm");;
    var data = new FormData(form);
    

    上述代码的data都是form表单里的填的,独立于表单使用即代表我们可以不需要html元素,直接生成数据,然后发送给后端。

    来看下面的上传文件代码

    var file = e.dataTransfer.files[0]  //通过dataTransfer获取拖拽过来的文件
    
    var formData = new FormData()
    formData.append('file',file)
    
    //ajax发送formData
    ...
    xhr.send(formData)
    
    

    4.FileReader

    MDN文档

    FileReader对象是用来读取文件的,我们可以通过new FileReader(file)创建一个FileReader对象,file参数代表要读取的文件,可以来自用户在一个<nput>元素上选择文件后返回的FileList对象,也可以是拖放操作生成的DataTransfer对象

    FileReader有如下事件:

    • onabort
    • onerror
    • onload
    • onloadstart
    • onloadend
    • onprogress

    我们可以通过这些事件实现图片的预览,代码如下:

     var fr = new FileReader();
     fr.readAsDataURL(file); 
     fr.onload = function() {
      console.log(this.result)
    //这里的this指向是FileReader对象!!!
    //这里的this指向是FileReader对象!!!
    //这里的this指向是FileReader对象!!!
    
    //将图片的地址src设置为this.result即可
     }
    
    

    注意!!读取后的结果会存在onload事件中的this.result中!

    5.进度事件(Progress)

    progress事件是针对XHR操作的,会在浏览器接受新数据期间周期性的触发,而onprogress事件处理程序会接收到一个events对象,其target属性是XHR对象,但包含了三个额外的属性

    • lengthComputable 进度信息是否可用
    • position 已接收到的字节数
    • totalSize 根据Content-Length响应头部确定的预期字节数

    有了这些信息,我们就可以为用户创建一个上传进度条

    var xhr = createXHR();
    xhr.onload = function () {
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
            alert(xhr.responseText);
        } else {
            alert("Request was unsuccessful: " + xhr.status);
        }
    };
    
    //post一般用来获取上传进度
    xhr.upload.onprogress = function(e) {
        if (e.lengthComputable) {
            console.log(e.loaded / e.total * 100)
        }
    }
    
    

    二、前端代码

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta
          name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
        />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <link
          href="https://cdn.bootcss.com/bootstrap/4.0.0/css/bootstrap.min.css"
          rel="stylesheet"
        />
        <script src="https://cdn.bootcss.com/vue/2.5.13/vue.min.js"></script>
        <style>
          .dropbox {
            border: 0.25rem dashed #ddd;
            min-height: 8rem;
            display: flex;
            justify-content: flex-start;
            align-items: center;
            flex-wrap: wrap;
          }
        </style>
        <title>Document</title>
      </head>
      <body>
        <div id="app" class="m-5">
          <div class="dropbox p-3" ref="dropbox">
            <h5
              v-if="files.length===0"
              class="text-center"
              style="width:100%;color:#aaa;"
            >
              将文件拖到这里
            </h5>
            <div
              class="border m-2 d-inline-block p-4"
              style="width:15rem;flex:none;"
              v-for="(file,index) in files"
              :key="index"
            >
              <h5 class="mt-0">{{ file.name }}</h5>
              <img
                :src="file.src"
                style="width:auto;height:auto;max-width: 100%;max-height: 100%;"
              />
              <div class="progress" v-if="file.showPercentage">
                <div
                  class="progress-bar progress-bar-striped"
                  :style="{ width: file.uploadPercentage+'%' }"
                ></div>
              </div>
            </div>
          </div>
        </div>
        <script>
          new Vue({
            el: "#app",
            data: {
              files: []
            },
            methods: {
              uploadFile: function(file, url) {
                return new Promise((resolve, reject) => {
                  var fr = new FileReader();
                  var that = this;
                  var item = {};
                  fr.readAsDataURL(file);
                  fr.onload = function() {
                    item = {
                      src: this.result,
                      name: file.name,
                      uploadPercentage: 0,
                      showPercentage: true
                    };
                    that.files.push(item);
                    var fd = new FormData();
                    fd.append("file", file);
    
                    var xhr = new XMLHttpRequest();
                    xhr.open("POST", url, true);
                    xhr.upload.addEventListener(
                      "progress",
                      function(e) {
                        if (e.loaded == e.total) {
                          item.uploadPercentage = Math.round(
                            (e.loaded * 100) / e.total
                          );
                          setTimeout(() => {
                            item.showPercentage = false;
                          }, 1000);
                        } else {
                          item.uploadPercentage = Math.round(
                            (e.loaded * 100) / e.total
                          );
                        }
                      },
                      false
                    );
                    xhr.onload = function() {
                      //   alert("上传完成!");
                    };
                    xhr.send(fd);
                  };
                });
              },
              onDrag: function(e) {
                e.stopPropagation();
                e.preventDefault();
                console.log("进入");
                this.$refs.dropbox.style = "border:0.25rem dashed #007bff;";
              },
              onDragLeave: function(e) {
                e.stopPropagation();
                e.preventDefault();
                console.log("离开");
                this.$refs.dropbox.style = "border:0.25rem dashed #ddd;";
              },
              onDrop: function(e) {
                e.stopPropagation();
                e.preventDefault();
                console.log("松手");
                var url = "http://127.0.0.1:3000/upload-multiply";
                var dt = e.dataTransfer;
                for (var i = 0; i !== dt.files.length; i++) {
                  this.uploadFile(dt.files[i], url);
                }
              }
            },
            mounted: function() {
              var dropbox = document.querySelector(".dropbox");
              dropbox.addEventListener("dragenter", this.onDrag, false);
              dropbox.addEventListener("dragover", this.onDrag, false);
              dropbox.addEventListener("dragleave", this.onDragLeave, false);
              dropbox.addEventListener("drop", this.onDrop, false);
            }
          });
        </script>
      </body>
    </html>
    
    
    

    代码解析

    1. HTML部分有个ref="dropbox"的div,这个就是我们的拖拽区域
    <div id="app">
      <div class="dropbox p-3" ref="dropbox">...</div>
      ...
    </div>
    
    
    1. JS部分,mounted的时候,对dropbox添加拖拽事件的监听
     mounted: function() {
              var dropbox = document.querySelector(".dropbox");
              dropbox.addEventListener("dragenter", this.onDrag, false);
              dropbox.addEventListener("dragover", this.onDrag, false);
              dropbox.addEventListener("dragleave", this.onDragLeave, false);
              dropbox.addEventListener("drop", this.onDrop, false);
            }
    
    

    3.methods中实现这几个事件函数

    upload(){} //上传文件方法
    onDrag(){}
    onDragLeave(){}
    onDrop(){}
    //具体实现看上述代码
    

    三、后端代码

    var fs = require("fs");
    var express = require("express");
    var multer = require("multer");
    
    var app = express();
    var upload = multer({ dest: "upload/" });
    //设置跨域访问
    app.all("*", function(req, res, next) {
      res.header("Access-Control-Allow-Origin", "*");
      res.header("Access-Control-Allow-Headers", "X-Requested-With");
      res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
      res.header("X-Powered-By", " 3.2.1");
      // res.header("Content-Type", "application/json;charset=utf-8");
      next();
    });
    
    // 多图上传
    app.post("/upload-multiply", upload.array("file", 2), function(req, res, next) {
      var files = req.files;
      var fileName = "";
      if (files.length > 0) {
        files.forEach(item => {
          fileName = new Date().getTime() + "-" + item.originalname;
          fs.renameSync(item.path, __dirname + "\\upload" + "\\" + fileName);
        });
        res.send({ code: 1, url: "127.0.0.1:3000/upload/" + fileName });
      } else {
        res.send({ code: 0 });
      }
    });
    
    app.listen(3000);
    
    
    

    后端需要npm install express multer
    运行前要先创建upload文件夹用于存放文件

    四、源码与总结

    源码地址:https://github.com/C-Utopia/pratice-project.git

    之前一直对H5的拖拽事件和文件上传迷迷糊糊,所以做了这个小练习。

    遇到没做过的东西,首先上网搜索,例如我想实现拖拽上传,那就在百度搜索vue拖拽上传文件之类的关键词,先看看别人如何实现的,复制别人的代码下来看能不能运行,要是能成功运行则仔细阅读源码,源码有没见过的单词,如FormData、FileReader,直接上MDN看文档,了解清楚这个知识点之后再继续阅读源码。

    了解清楚拖拽上传的相关知识点以及实现思路,我们再动手写代码就是水到渠成的事了。

    相关文章

      网友评论

        本文标题:vue+node实现拖拽上传图片

        本文链接:https://www.haomeiwen.com/subject/syfafctx.html