開發一個本地上傳圖片控制元件你需要知道的知識點

Jade05發表於2018-01-09

更多文章請點選Jade

接了一個「常規」需求:開發一個本地上傳圖片控制元件,需要支援三種上傳方式:

  1. 支援開啟本地目錄,選擇本地圖片上傳
  2. 支援拖曳圖片上傳
  3. 支援微信截圖上傳

我們先從工程角度來看一下使用者上傳圖片的流程是怎樣子的:

  1. 使用者選擇了一張本地圖片,或者拖曳了一張圖片,或者通過微信截圖了一張圖片。這時,我們需要知道使用者所選擇圖片的資訊,比如圖片的內容、圖片的大小、圖片的型別等。
  2. 使用者上傳圖片。這時,我們需要儲存圖片。
  3. 使用者檢視圖片。這時,我們需要把第2步中儲存的圖片展示給使用者。

如何獲取到圖片資訊

通過input 開啟本地目錄,選擇本地圖片上傳

通過type為file的input標籤選擇本地圖片上傳,每次選擇不同圖片的時候會觸發onChange事件。為什麼我要強調不同圖片,因為當兩次選擇的圖片是一樣的情況下,onChange事件無法觸發。解決方法:每次處理完後手動置空input的value。

  <input id='img-input' type='file' accept='image/*' onChange={this.bindChooseEvents} />
複製程式碼
  // 置空input value
  clearImgInputValue () {
    document.getElementById('img-input').value = ''
  }
  bindChooseEvents = (e) => {
    console.log('choose a image')

    // 獲取File物件
    const file = e.target.files[0]
    let size = file.size

    if (!file.type.match('image.*')) {
      AntMessage.warning('File\'s type is not supported. Images only.')
      this.clearImgInputValue()
      return;
    }

    if (size > maxSize) {
      AntMessage.warning('The size of the image is too large')
      this.clearImgInputValue()
      return;
    }

    /* eslint-disable */
    const reader = new FileReader()  // FileReader
    /* eslint-disable */
    // 將圖片轉換為base64
    reader.readAsDataURL(file)
    reader.onload = (arg) => {
      // 獲取到base64圖片內容
      const fileStream = arg.target.result
      /**
       * overwrite do something
       * */
      this.clearImgInputValue()
    }
  }
複製程式碼

拖曳圖片上傳

監聽拖曳事件,通過拖曳相關的DataTransfer物件獲取圖片資訊。

  <div id='drop-zone' style={{width: '100px', height: '100px'}}>Drop Zone</div>
複製程式碼
  bindDragEvents = (e) => {
    const handleDragOver = (event) => {
      event.stopPropagation()
      event.preventDefault()
      event.dataTransfer.dropEffect = 'copy'
    }

    // 必須阻止dragenter和dragover事件的預設行為,這樣才能觸發 drop 事件
    const handleFileSelect = (event) => {
      event.stopPropagation()
      event.preventDefault()

      const files = event.dataTransfer.files // 檔案物件
      const file = files[0]
      const size = file.size
      const type = file.type

      if (!type.match('image.*')) {
        AntMessage.warning('File\'s type is not supported. Images only.')
        return;
      }

      if (size > maxSize) {
        AntMessage.warning('The size of the image is too large')
        return;
      }

      /* eslint-disable */
      const reader = new FileReader()
      /* eslint-disable */
      // 將圖片轉換為base64
      reader.readAsDataURL(file)
      reader.onload = (arg) => {
        // 獲取到base64圖片內容
        const fileStream = arg.target.result
        /**
         * overwrite do something
         * */
      }
    }

    const dropZone = document.getElementById('drop-zone');
    dropZone.addEventListener('dragover', handleDragOver, false);
    dropZone.addEventListener('drop', handleFileSelect, false);
  }
複製程式碼

微信截圖上傳

監聽paste事件,通過剪貼簿物件clipboardData獲取圖片資訊。

  bindClipEvents() {
    document.addEventListener('paste', (e) => {
      console.log('paste a image')
      const clipboard = e.clipboardData

      // 有無內容
      if (!clipboard.items || !clipboard.items.length) {
        AntMessage.warning('No content in the clipboard')
        return;
      }

      let item = clipboard.items[0]
      if (item.kind === 'file' && item.type.match('image.*')) {
          // 獲取圖片檔案
          let imgFile = item.getAsFile()

          if (imgFile.size > maxSize) {
            AntMessage.warning('The size of the image is too large')
            return;
          }

          const reader = new FileReader()
          // 將圖片轉換為base64
          reader.readAsDataURL(imgFile)
          reader.onload = (arg) => {
            // 獲取到base64圖片內容
            const fileStream = arg.target.result
            /**
             * overwrite do something
             * */
          }
      } else {
        AntMessage.warning('File\'s type is not supported. Images only.')
      }
    }, false)
  }
複製程式碼

如何儲存

圖片儲存一般都採用獨立圖片獨立域名伺服器,不會傻乎乎地把圖片儲存在web伺服器上也不會直接存在專案表的資料庫中。這樣做有什麼好處呢?

  1. 圖片訪問是I/O密集型操作,很消耗伺服器資源,從Web伺服器獨立出來後,能夠減少Web伺服器壓力
  2. 便於擴容、容災和資料遷移
  3. 瀏覽器有同域名下的併發策略限制
  4. 請求圖片一般並不需要cookie,但是瀏覽器發起的所有同域名請求時,http頭部都會自動帶上cookie資訊,導致浪費頻寬
  5. 方便對圖片訪問做負載均衡,可以對圖片應用各種快取策略
  6. 方便遷移CDN ...

總結&優化

流程圖:

image

不足:

專案實踐中我們雖然採用了獨立圖片伺服器,下載過程只是通過Web伺服器去資料庫拿到圖片地址,但是我們的上傳操作仍舊經過了Web伺服器,需要Web伺服器上的應用程式來處理,所以上傳過程仍舊對Web伺服器造成壓力。所幸的是,我們對圖片上傳大小進行了1M的大小限制,同時作為內部系統沒啥訪問壓力,而且圖片上傳功能也並不是很高發的行為,所以這麼做基本也不會有啥問題。但是最好的方案還是不管下載上傳都直接走獨立圖片伺服器,避免對Web伺服器造成額外的壓力。

後續,探究圖片伺服器架構 & 如何應對大檔案的上傳 & Blob Buffer Stream三者之間的關係。

本文參考資料:

  1. 理解DOMString、Document、FormData、Blob、File、ArrayBuffer資料型別
  2. 複製貼上的高階玩法
  3. 使用FileReader.readAsArrayBuffer()在瀏覽器中處理大檔案
  4. 大型網站圖片伺服器架構的演進

相關文章