圖片上傳知識點梳理

流霜發表於2019-02-16

在日常專案開發中,圖片上傳是一個十分常見的場景。而現在的各種UI框架都提供了自己的上傳元件,網上第三方的上傳元件也多如牛毛。可能你早已習慣了直接使用這些現成的元件,然而對於其具體的實現,卻並未深入解析。本文將通過簡單的程式碼,為你解析圖片上傳的各個知識點。

樣式自定義

既然是上傳,肯定需要使用到<input type="file">標籤了。然而,預設的input到標籤樣式不僅單一,且在各個瀏覽器下的表現也不相同,所以通常需要對input進行樣式自定義。但<input>標籤對於樣式的修改並不十分友好。解決方法很多,最常用的是將<input type="file">標籤隱藏,然後通過一個<label>標籤進行關聯,然後直接修改<label>標籤的樣式來實現。程式碼如下:

    <input type="file" id="uploadImg">
    <label for="uploadImg">點選上傳</label>

圖片校驗

在上傳之前,一般會對檔案進行各種校驗,例如檔案型別,大小,格式,尺寸等。

其中檔案型別,可通過設定<input>標籤的accept來指定檔案型別。但accept屬性並不能完全禁止使用者上傳指定型別之外的檔案。故可以通過上傳檔案的各個屬性進行校驗攔截。校驗前,我們需要通過change事件的事件物件來獲取到上傳的檔案:

    event.target.files

可以獲取到上傳檔案列表。列表中物件包含如下資訊:

    {
        lastModified: 1524153515000
        lastModifiedDate: Thu Apr 19 2018 23:58:35 GMT+0800 (中國標準時間) {}
        name: "589adfbfe821c.jpg"
        size: 1357444
        type: "image/jpeg"
        webkitRelativePath: ""
    }

從該物件中,我們可以獲取到檔案大小,檔案型別,檔名等資訊,從而可以在上傳之前對這些資訊進行校驗,從而攔截掉不合法的檔案。

然而,從file物件中,我們並不能獲取圖片的尺寸資訊。而在我們的業務中,很多場景都需要限制上傳圖片的尺寸為某一個固定值,或者是某一個比例。以減少後期顯示時的適配問題。要實現對上傳圖片尺寸對校驗,我們需要使用到FileReaderImage

FileReader物件允許Web應用程式非同步讀取儲存在使用者計算機上的檔案。

Image()函式將會建立一個新的HTMLImageElement例項。它的功能等價於document.createElement(`img`)

這裡,我們需要用到fileReaderreadAsDataURL()方法來讀取上傳檔案資訊,通過onload處理事件來獲取讀取到的檔案資訊。如下:

    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = e => {
        console.log(e.target.result)
    }

程式碼中,file為我們之前獲取到的檔案列表files中的檔案物件。e.target.result為讀取到到檔案內容。

之後通過new Image()函式建立一個新的HTMLImageElement例項,並將該例項的src賦值為fileReader讀取到到檔案內容。即可得到一個該檔案的HTMLImageElement例項,通過該例項,我們便可以讀取到該圖片的尺寸資訊。具體程式碼如下:

    const image = new Image();
    image.src = e.target.result;
    image.onload = () => {
        console.log(image.width, image.height);
    }

圖片上傳預覽

在之前的開發中,圖片上傳顯示通常會採用先將檔案上傳,預覽圖片直接展示上傳到伺服器中到圖片來實現,但這樣無法達到上傳前預覽該圖片的目的,且會造成許多垃圾圖片的上傳。

通過前面對於獲取圖片尺寸研究。相信你能很快想到一種更加優雅的圖片預覽方案,既然我們已經獲取到了該檔案的HTMLImageElement例項,那麼我們直接將該例項append到頁面的容器Dom中不久行了。或者直接將獲取到的檔案設定到已存在的image標籤的src屬性中。圖片上傳預覽就是這麼簡單。

圖片上傳與上傳進度展示

圖片的上傳,我們可以直接通過form標籤搭配表單的submit()方法來實現圖片的上傳。然而,這樣我們就無法在上傳前進行上傳檔案的校驗與攔截。同時需要使用者主動觸發提交操作。要想讓我們之前做的上傳前的攔截工作不白做,我們需要去在合適的時候,主動觸發檔案的上傳操作。

這裡,需要使用到FormData物件,來將入參物件資料轉為表單資料。

FormData物件用以將資料編譯成鍵值對,以便用XMLHttpRequest來傳送資料。其主要用於傳送表單資料,但亦可用於傳送帶鍵資料(keyed data),而獨立於表單使用。如果表單enctype屬性設為multipart/form-data ,則會使用表單的submit()方法來傳送資料,從而,傳送資料具有同樣形式。

首先我們建立一個formData物件,然後通過append() 方法來新增欄位。如下:

    const formData = new FormData();
    formData.append("file", file);

注意,formData雖然為一個物件,但通過console.log卻無法列印出其具體的值,只會得到FormData {}

接下來建立一個XMLHttpRequest物件,用來傳送ajax請求。並且通過該XMLHttpRequest物件的upload.onprogress方法,可以實時獲取到上傳資訊,並進一步獲取到上傳的進度。具體程式碼如下:

    const client = new XMLHttpRequest()
    client.open("POST", uploadUrl)
    client.upload.onprogress = function(e) {
      if (e.lengthComputable) {
        let total = e.total;
        let loaded = e.loaded;
        let percentage = parseFloat(loaded / total).toFixed(2);
      }
    }
    client.send(formData)

上面程式碼中,uploadUrl為上傳的URL。通過upload.onprogress的事件物件,可以獲取到當前進度已上傳的檔案大小以及完整檔案大小,通過這兩個大小引數,可以很容易計算出已上傳檔案的比例,之後是顯示上傳進度條、還是展示進度資料,就可以隨意操作了。

拖拽上傳

除了傳統的點選選擇上傳檔案外,拖拽檔案上傳也是一個十分常見的場景。要使用拖拽上傳,就需要使用H5的拖放方法dropdrag方法。除了這兩個主要的方法外,還有拖放的不同階段觸發的多個方法,常用的拖拽方法如下:

  • ondragstart 事件:當拖拽元素開始被拖拽的時候觸發的事件(作用物件為被拖曳元素)
  • ondrag:在元素拖動期間不停的觸發該事件,與touchmove事件類似。(作用物件為被拖曳元素)
  • ondragend 事件:當拖拽完成後觸發的事件(作用物件為被拖曳元素)
  • ondragenter 事件:當拖曳元素進入目標元素的時候觸發的事件(作用物件為目標元素)
  • ondragover 事件:拖拽元素在目標元素上移動的時候觸發的事件(作用物件為目標元素)
  • ondragleave 事件:拖拽元素在目標元素上移動的時候觸發的事件(作用物件為目標元素)
  • ondrop 事件:被拖拽的元素在目標元素上同時滑鼠放開觸發的事件(作用物件為目標元素)

拖拽的各個事件類似與touch事件的各個階段。然而需要注意的是,拖拽的各個事件,有著自己的作用物件,作用物件分為‘被拖拽元素’和‘目標元素’。被拖拽元素 為拖拽的那個Dom元素,主要使用在頁面內Dom拖拽移動的場景。目標元素為接收被拖拽元素的元素區域。當被拖拽元素進入到該區域,便會觸發目標物件的一系列事件。

在圖片拖拽上傳這個業務場景中,被拖拽元素為頁面外部的圖片檔案,故此處僅用到目標元素的各個事件。我們可以通過這些事件來修改目標區域樣式等。核心的兩個事件為ondragoverondrop事件。可能你覺得我只需要在鬆開滑鼠時獲取拖拽的檔案就行,因此只需要使用ondrop事件就行了?但是,由於瀏覽器的預設行為,ondrop事件並不會被觸發。因此,需要使用e.preventDefault(); 來阻止掉 ondropover的瀏覽器預設事件,從而保證ondrop事件的觸發。通過ondrop事件的事件物件,我們可以獲取到跟event.target.files相同的檔案列表,獲取方法為event.dataTransfer.files;然而,當你這麼寫完之後,進行拖拽之後,你會發現瀏覽器自動跳轉到了該圖片的預覽頁。這也是由於瀏覽器的預設行為導致,因此也需要使用e.preventDefault();來阻止掉瀏覽器的預設行為。這樣,便可以進行後續的檔案校驗操作來。
具體實現程式碼如下:

    <label for="uploadImg"
        onDragOver={e => {
            e.preventDefault();
        }}
        onDrop={e => {
            if (e.dataTransfer) {
                e.preventDefault();
                const file = e.dataTransfer.files[0];
                ...
            } 
        }}
    </label>

至此,圖片上傳的常用知識點以梳理完畢,歡迎補充。

相關文章