vue+node實(shí)現(xiàn)拖拽上傳圖片

效果圖

首先介紹幾個要用到的知識點(diǎn),源碼地址在最底部瞎疼,不想看文字的同學(xué)可以直接拉到最底部下載科乎。

一、知識點(diǎn)

實(shí)現(xiàn)拖拽上傳需要用到的知識點(diǎn)如下:

前端
拖拽事件
dataTransfer
FileReader
FormData
progress

后端
multer
fs.renameSync

1.H5拖拽事件

我們拖動圖片放到一個div上時(shí)贼急,下列事件會依次發(fā)生:

  • dragenter
  • dragover
  • dragleave或drop

只要圖片被拖動到div上茅茂,就會觸發(fā)dragenter事件,類似于mouseover事件太抓。
緊接著是dragover事件空闲,而且只要圖片在div內(nèi)移動,就會不停的觸發(fā)走敌。
如果拖出了div的范圍碴倾,dragover事件不再發(fā)生,但會觸發(fā)dragleave事件。
如果你拖著圖片在div上松手了跌榔,就會觸發(fā)drop事件异雁。

2.dataTransfer對象

只有簡單的拖放而沒有數(shù)據(jù)變化是莫得用的。我們拖個圖片進(jìn)來的目的是啥僧须?當(dāng)然是為了獲得圖片數(shù)據(jù)纲刀,這樣才能然后傳到服務(wù)器上去。于是有了dataTransfer對象担平,它是事件對象的一個屬性示绊,用于從被拖動元素向放置目標(biāo)傳遞字符串格式的數(shù)據(jù)。我們要完成拖拽上傳圖片就得靠這個對象驱闷,因?yàn)樗鞘录ο蟮膶傩猿芴ǎ晕覀冎荒茉谑录幚沓绦蛑惺褂茫瑓⒖家韵麓a:

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

3.FormData

MND文檔

具體的用法還是得閱讀MDN文檔好空另,以下是本菜雞讀了文檔后對FormData的理解:

首先明確FomeData是一個對象盆耽,由鍵值對組成,有個append()方法增加鍵值扼菠,我們可以append字符摄杂、數(shù)值或是文件

var formData = new FormData();

formData.append("name", "Ciger");
formData.append("phone", 123456); //數(shù)字123456會被立即轉(zhuǎn)換成字符串 "123456"
// HTML 文件類型input,由用戶選擇
formData.append("userfile", fileInputElement.files[0]);

append后循榆,我們可通過get()和set()操作對象的值

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

知道如何使用這個對象了析恢,最關(guān)鍵的就是它有什么用?
formData的作用有兩個:

  • 用于發(fā)送表單數(shù)據(jù)秧饮,也可獨(dú)立于表單使用
  • 上傳文件

獨(dú)立于表單使用有點(diǎn)抽象映挂,我們來看代碼。

<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輸入框加一個提交按鈕柑船。通常來說,我們提交數(shù)據(jù)時(shí)要先獲取到兩個input框的數(shù)據(jù)泼各,拼接在一起鞍时,然后通過ajax發(fā)送。
有了FormData對象扣蜻,兩行代碼就可以實(shí)現(xiàn)form對象數(shù)據(jù)的拼接

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

上述代碼的data都是form表單里的填的逆巍,獨(dú)立于表單使用即代表我們可以不需要html元素,直接生成數(shù)據(jù)莽使,然后發(fā)送給后端锐极。

來看下面的上傳文件代碼

var file = e.dataTransfer.files[0]  //通過dataTransfer獲取拖拽過來的文件

var formData = new FormData()
formData.append('file',file)

//ajax發(fā)送formData
...
xhr.send(formData)

4.FileReader

MDN文檔

FileReader對象是用來讀取文件的,我們可以通過new FileReader(file)創(chuàng)建一個FileReader對象芳肌,file參數(shù)代表要讀取的文件溪烤,可以來自用戶在一個<nput>元素上選擇文件后返回的FileList對象味咳,也可以是拖放操作生成的DataTransfer對象

FileReader有如下事件:

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

我們可以通過這些事件實(shí)現(xiàn)圖片的預(yù)覽,代碼如下:

 var fr = new FileReader();
 fr.readAsDataURL(file); 
 fr.onload = function() {
  console.log(this.result)
//這里的this指向是FileReader對象C枢帧2凼弧!
//這里的this指向是FileReader對象T蕖5囝怼!
//這里的this指向是FileReader對象W嵋臁H伞!

//將圖片的地址src設(shè)置為this.result即可
 }

注意V灾馈辱姨!讀取后的結(jié)果會存在onload事件中的this.result中!

5.進(jìn)度事件(Progress)

progress事件是針對XHR操作的戚嗅,會在瀏覽器接受新數(shù)據(jù)期間周期性的觸發(fā)雨涛,而onprogress事件處理程序會接收到一個events對象,其target屬性是XHR對象懦胞,但包含了三個額外的屬性

  • lengthComputable 進(jìn)度信息是否可用
  • position 已接收到的字節(jié)數(shù)
  • totalSize 根據(jù)Content-Length響應(yīng)頭部確定的預(yù)期字節(jié)數(shù)

有了這些信息替久,我們就可以為用戶創(chuàng)建一個上傳進(jìn)度條

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一般用來獲取上傳進(jìn)度
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
      
      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("進(jìn)入");
            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蚯根,這個就是我們的拖拽區(qū)域
<div id="app">
  <div class="dropbox p-3" ref="dropbox">...</div>
  ...
</div>

  1. JS部分,mounted的時(shí)候,對dropbox添加拖拽事件的監(jiān)聽
 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中實(shí)現(xiàn)這幾個事件函數(shù)

upload(){} //上傳文件方法
onDrag(){}
onDragLeave(){}
onDrop(){}
//具體實(shí)現(xiàn)看上述代碼

三胀糜、后端代碼

var fs = require("fs");
var express = require("express");
var multer = require("multer");

var app = express();
var upload = multer({ dest: "upload/" });
//設(shè)置跨域訪問
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
運(yùn)行前要先創(chuàng)建upload文件夾用于存放文件

四颅拦、源碼與總結(jié)

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

之前一直對H5的拖拽事件和文件上傳迷迷糊糊,所以做了這個小練習(xí)教藻。

遇到?jīng)]做過的東西矩距,首先上網(wǎng)搜索,例如我想實(shí)現(xiàn)拖拽上傳怖竭,那就在百度搜索vue拖拽上傳文件之類的關(guān)鍵詞,先看看別人如何實(shí)現(xiàn)的陡蝇,復(fù)制別人的代碼下來看能不能運(yùn)行痊臭,要是能成功運(yùn)行則仔細(xì)閱讀源碼,源碼有沒見過的單詞登夫,如FormData广匙、FileReader,直接上MDN看文檔恼策,了解清楚這個知識點(diǎn)之后再繼續(xù)閱讀源碼鸦致。

了解清楚拖拽上傳的相關(guān)知識點(diǎn)以及實(shí)現(xiàn)思路潮剪,我們再動手寫代碼就是水到渠成的事了庙楚。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末扰付,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子推汽,更是在濱河造成了極大的恐慌绽乔,老刑警劉巖弧蝇,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異折砸,居然都是意外死亡看疗,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進(jìn)店門睦授,熙熙樓的掌柜王于貴愁眉苦臉地迎上來两芳,“玉大人,你說我怎么就攤上這事去枷〔懒荆” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵沉填,是天一觀的道長疗隶。 經(jīng)常有香客問我,道長翼闹,這世上最難降的妖魔是什么斑鼻? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮猎荠,結(jié)果婚禮上坚弱,老公的妹妹穿的比我還像新娘。我一直安慰自己关摇,他們只是感情好荒叶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著输虱,像睡著了一般些楣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上宪睹,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天愁茁,我揣著相機(jī)與錄音,去河邊找鬼亭病。 笑死鹅很,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的罪帖。 我是一名探鬼主播促煮,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼邮屁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了菠齿?” 一聲冷哼從身側(cè)響起佑吝,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泞当,沒想到半個月后迹蛤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡襟士,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年盗飒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陋桂。...
    茶點(diǎn)故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡逆趣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嗜历,到底是詐尸還是另有隱情宣渗,我是刑警寧澤,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布梨州,位于F島的核電站痕囱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏暴匠。R本人自食惡果不足惜鞍恢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望每窖。 院中可真熱鬧帮掉,春花似錦、人聲如沸窒典。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瀑志。三九已至涩搓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間劈猪,已是汗流浹背昧甘。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留岸霹,地道東北人。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓将饺,卻偏偏與公主長得像贡避,于是被迫代替她去往敵國和親痛黎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評論 2 355